1. 程式人生 > 其它 >Angular英雄之旅總結

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。
  • 你學會了如何使用“可觀察物件”。

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