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()
必須具有某種形式的非同步函式簽名。
Observable
是 RxJS 庫中的一個關鍵類。
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 把它註冊到AppModule
的imports
陣列中
路由模板
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。
- 你學會了如何使用“可觀察物件”。
《英雄之旅》教程結束了。