Fork me on GitHub

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>

组成

|-<component>.component.css
|-<component>.component.html --用 HTML 写的
|-<component>.component.spec.ts
|-<component>.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。</-->
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div><span>name: </span>{{hero.name}}</div>

双向绑定

语法

<div>
  <label for="name">Hero name: </label>
  <input id="name" [(ngModel)]="hero.name" placeholder="name">
</div>

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

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

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

AppModule声明模块

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

循环遍历

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

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

事件绑定

添加click事件

<li *ngFor="let hero of heroes" 
(click)="onSelect(hero)">

ts模板中添加方法体

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

条件渲染

<div *ngIf="selectedHero">

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

样式绑定

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

主从组件

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

<app-hero-detail [hero]="selectedHero"></app-hero-detail>

子组件输入属性

    <input id="hero-name" [(ngModel)]="hero.name" placeholder="name">

子组件ts模板

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

提供服务

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

创建服务

ng generate service <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() 必须具有某种形式的异步函数签名

ObservableRxJS 库中的一个关键类。

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

getHeroes(): Observable<Hero[]> {
  const heroes = of(HEROES);
  return heroes;
}

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

订阅方法

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 { }

路由出口

<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>

路由链接

<h1>{{title}}</h1>
<nav>
  <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>

默认路由

{ 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<Hero[]> {
  return this.http.get<Hero[]>(this.heroesUrl)
    .pipe(
      tap(_ => this.log('fetched heroes')),
      catchError(this.handleError<Hero[]>('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<T>(operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {

    // 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<Hero> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.get<Hero>(url).pipe(
    tap(_ => this.log(`fetched hero id=${id}`)),
    catchError(this.handleError<Hero>(`getHero id=${id}`))
  );
}

修改英雄

添加事件

<button (click)="save()">save</button>

添加方法

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

添加服务

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

删除英雄

添加事件

    <button class="delete" title="delete hero"
      (click)="delete(hero)">x</button>

添加方法

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<Hero> {
  const url = `${this.heroesUrl}/${id}`;

  return this.http.delete<Hero>(url, this.httpOptions).pipe(
    tap(_ => this.log(`deleted hero id=${id}`)),
    catchError(this.handleError<Hero>('deleteHero'))
  );
}

搜索服务

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

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

小结

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

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

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

posted @ 2022-03-21 22:39  浩然哉  阅读(162)  评论(0)    收藏  举报
/* 看板娘 */
浏览器标题切换
浏览器标题切换end