Angular英雄之旅总结


Angular英雄之旅总结

https://angular.cn/tutorial/toh-pt6#chaining-rxjs-operators

创建项目

安装Angular Cli后 powershell 输入

ng new project

启动项目

cd project
ng serve --open

--open 标志会打开浏览器,并访问 http://localhost:4200/

生成组件

生成命令:

ng generate component 

组成

|-.component.css
|-.component.html --用 HTML 写的
|-.component.spec.ts
|-.component.ts  --用 TypeScript 写的

组件结构

ts模板

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
/*
@Component 是个装饰器函数,用于为该组件指定 Angular 所需的元数据。
*/
@Component({
  selector: 'app-heroes',// 组件的选择器
  templateUrl: './heroes.component.html',//组件模板文件的位置。
  styleUrls: ['./heroes.component.css']//组件私有 CSS 样式表文件的位置
})
export class HeroesComponent implements OnInit {

//把组件的 hero 属性的类型重构为 Hero。 然后以 1 为 id、以 “Windstorm” 为名字初始化它。
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
  constructor() { }

  ngOnInit(): void {
  }

}

html模板

<-->绑定表达式中的 uppercase 位于管道操作符( | )的右边,用来调用内置管道 UppercasePipe。

{{hero.name | uppercase}} Details

id: {{hero.id}}
name: {{hero.name}}

双向绑定

语法

[(ngModel)] 是 Angular 的双向数据绑定语法。

它属于一个可选模块 FormsModule,你必须自行添加此模块才能使用该指令。

Angular CLI 在创建项目的时候就在 src/app/app.module.ts 中生成了一个 AppModule 类。 这里也就是你要添加 FormsModule 的地方。

AppModule声明模块

import { HeroesComponent } from './heroes/heroes.component';
declarations: [
  AppComponent,
  HeroesComponent
],

循环遍历

My Heroes

  • {{hero.id}} {{hero.name}}

*ngFor 是一个 Angular 的复写器(repeater)指令。 它会为列表中的每项数据复写它的宿主元素。

事件绑定

添加click事件

  • ts模板中添加方法体

    selectedHero?: Hero;
    onSelect(hero: Hero): void {
      this.selectedHero = hero;
    }
    

    条件渲染

    当用户选择一个英雄时,selectedHero 也就有了值,并且 ngIf 把英雄的详情放回到 DOM 中。

    样式绑定

    [class.selected]="hero === selectedHero"
    

    主从组件

    外部组件绑定子组件输入属性

    
    

    子组件输入属性

        
    

    子组件ts模板

    @Input() hero?: Hero;  //导入属性
    

    提供服务

    组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。

    创建服务

    ng generate service 
    

    生成

    import { Injectable } from '@angular/core';
    /*
    这个新的服务导入了 Angular 的 Injectable 符号,并且给这个服务类添加了 @Injectable() 装饰器。 它把这个类标记为依赖注入系统的参与者之一。
    */
    @Injectable({
      providedIn: 'root',
    })
    export class HeroService {
    
      constructor() { }
    
    }
    

    添加方法,返回模拟的英雄列表

    getHeroes(): Hero[] {
      return HEROES;
    }
    

    注入服务

    组件ts模板中

    往构造函数中添加一个私有的 heroService,其类型为 HeroService

    constructor(private heroService: HeroService) {}
    

    调用服务方法

    getHeroes(): void {
      this.heroes = this.heroService.getHeroes();
    }
    
    ngOnInit(): void {
      this.getHeroes();
    }
    

    可视数据

    可观察(Observable)的数据

    HeroService.getHeroes() 必须具有某种形式的异步函数签名

    Observable 是 RxJS 库中的一个关键类。

    src/app/hero.service.ts中改造的getHeroes方法

    getHeroes(): Observable {
      const heroes = of(HEROES);
      return heroes;
    }
    

    of(HEROES) 会返回一个 Observable,它会发出单个值,这个值就是这些模拟英雄的数组。

    订阅方法

    heroes.component.ts 中

    getHeroes(): void {
      this.heroService.getHeroes()
          .subscribe(heroes => this.heroes = heroes);
    }
    

    添加导航

    添加路由

    在 Angular 中,最好在一个独立的顶层模块中加载和配置路由器,它专注于路由功能,然后由根模块 AppModule 导入它。

    ng generate module app-routing --flat --module=app
    

    --flat 把这个文件放进了 src/app 中,而不是单独的目录中。
    --module=app 告诉 CLI 把它注册到 AppModuleimports 数组中

    路由模板

    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    import { HeroesComponent } from './heroes/heroes.component';
    
    const routes: Routes = [
      { path: 'heroes', component: HeroesComponent }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    

    路由出口

    {{title}}

    路由链接

    {{title}}

    默认路由

    { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
    

    路由参数

    path 中的冒号(:)表示 :id 是一个占位符,它表示某个特定英雄的 id

    { path: 'detail/:id', component: HeroDetailComponent },
    

    获取数据

    启用 HTTP

    import { HttpClientModule } from '@angular/common/http';
    

    模拟数据

    npm install angular-in-memory-web-api --save
    

    获取英雄

    /** GET heroes from the server */
    getHeroes(): Observable {
      return this.http.get(this.heroesUrl)
        .pipe(
          tap(_ => this.log('fetched heroes')),
          catchError(this.handleError('getHeroes', []))
        );
    }
    

    使用 pipe() 方法来扩展 Observable 的结果, catchError() 操作符会拦截失败的 Observable。 它把错误对象传给错误处理器错误处理器会处理这个错误。使用 tap() 来记录各种操作。

    错误处理

    /**
     * Handle Http operation that failed.
     * Let the app continue.
     *
     * @param operation - name of the operation that failed
     * @param result - optional value to return as the observable result
     */
    private handleError(operation = 'operation', result?: T) {
      return (error: any): Observable => {
    
        // TODO: send the error to remote logging infrastructure
        console.error(error); // log to console instead
    
        // TODO: better job of transforming error for user consumption
        this.log(`${operation} failed: ${error.message}`);
    
        // Let the app keep running by returning an empty result.
        return of(result as T);
      };
    }
    

    单个英雄

    /** GET hero by id. Will 404 if id not found */
    getHero(id: number): Observable {
      const url = `${this.heroesUrl}/${id}`;
      return this.http.get(url).pipe(
        tap(_ => this.log(`fetched hero id=${id}`)),
        catchError(this.handleError(`getHero id=${id}`))
      );
    }
    

    修改英雄

    添加事件

    
    

    添加方法

    save(): void {
      if (this.hero) {
        this.heroService.updateHero(this.hero)
          .subscribe(() => this.goBack());
      }
    }
    

    添加服务

    /** PUT: update the hero on the server */
    updateHero(hero: Hero): Observable {
      return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
        tap(_ => this.log(`updated hero id=${hero.id}`)),
        catchError(this.handleError('updateHero'))
      );
    }
    

    删除英雄

    添加事件

        
    

    添加方法

    delete(hero: Hero): void {
      this.heroes = this.heroes.filter(h => h !== hero);
      this.heroService.deleteHero(hero.id).subscribe();
    }
    

    虽然这个组件把删除英雄的逻辑委托给了 HeroService,但仍保留了更新它自己的英雄列表的职责。 组件的 delete() 方法会在 HeroService 对服务器的操作成功之前,先从列表中移除要删除的英雄

    添加服务

    /** DELETE: delete the hero from the server */
    deleteHero(id: number): Observable {
      const url = `${this.heroesUrl}/${id}`;
    
      return this.http.delete(url, this.httpOptions).pipe(
        tap(_ => this.log(`deleted hero id=${id}`)),
        catchError(this.handleError('deleteHero'))
      );
    }
    

    搜索服务

    /* GET heroes whose name contains search term */
    searchHeroes(term: string): Observable {
      if (!term.trim()) {
        // if not search term, return empty hero array.
        return of([]);
      }
      return this.http.get(`${this.heroesUrl}/?name=${term}`).pipe(
        tap(x => x.length ?
           this.log(`found heroes matching "${term}"`) :
           this.log(`no heroes matching "${term}"`)),
        catchError(this.handleError('searchHeroes', []))
      );
    }
    

    如果没有搜索词,该方法立即返回一个空数组。 剩下的部分和 getHeroes() 很像。 唯一的不同点是 URL,它包含了一个由搜索词组成的查询字符串。

    小结

    旅程即将结束,不过你已经收获颇丰。

    • 你添加了在应用程序中使用 HTTP 的必备依赖。
    • 你重构了 HeroService,以通过 web API 来加载英雄数据。
    • 你扩展了 HeroService 来支持 post()put()delete() 方法。
    • 你修改了组件,以允许用户添加、编辑和删除英雄。
    • 你配置了一个内存 Web API。
    • 你学会了如何使用“可观察对象”。

    《英雄之旅》教程结束了。