HeroServe
使用httpClient中的get(),put(),delete(),update()方法写出之后其他xxxx.ts文件将要调用的增删改查方法。
hero.service.ts
声明httpOptions,heroesUrl(定义根目录),http(创建的HttpClient实例),messageService(记录各类信息)
以下是声明的增删改查方法
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError('getHeroes', []))
);
}
/** GET hero by id. Return `undefined` when id not found */
getHeroNo404<Data>(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/?id=${id}`;
return this.http.get<Hero[]>(url)
.pipe(
map(heroes => heroes[0]), // returns a {0|1} element array
tap(h => {
const outcome = h ? `fetched` : `did not find`;
this.log(`${outcome} hero id=${id}`);
}),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/** 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}`))
);
}
/* 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(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}
//////// Save methods //////////
/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}
/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero'))
);
}
/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero'))
);
}
还有一个处理异常和记录日志的方法
/**
* 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);
};
}
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add(`HeroService: ${message}`);
}
in-memory-data.service.ts
这个文件作为模拟web api来暂存数据
app.module.ts
在这个文件中是申明注册各个模块
HeroesComponent
heroes.component.html
显示所有的英雄,并有增删查的功能
<button (click)="add(heroName.value); heroName.value=''">
add
</button>
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
<button class="delete" title="delete hero"
(click)="delete(hero)">
x
</button>
heroes.component.ts
通过调用hero.service.ts中的方法,并将返回结果订阅至Observable对象中,以实现增删查功能,并且结果可以实时在页面上显示。
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
HeroDetailComponent
hero-detail.component.html
具体显示每个英雄信息
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
<button (click)="goBack()">go back</button><!--返回上个页面-->
<button (click)="save()">save</button><!--保存修改了的数据,以在页面上实时显示-->
</div>
[(ngModel)] 是 Angular 的双向数据绑定语法
这里把 hero.name
属性绑定到了 HTML 的 textbox 元素上,以便数据流可以双向流动:从 hero.name
属性流动到 textbox,并且从 textbox 流回到 hero.name
。
使用 *ngIf
隐藏空白的详情
但模板中的绑定表达式引用了 selectedHero
的属性(表达式为 {{selectedHero.name}}
),这必然会失败,因为你还没选过英雄呢。
修复:该组件应该只有当 selectedHero
存在时才显示所选英雄的详情。当 selectedHero
为 undefined
时,ngIf
从 DOM 中移除了英雄详情。因此也就不用担心 selectedHero
的绑定了。
当用户选择一个英雄时,selectedHero
也就有了值,并且 ngIf
把英雄的详情放回到 DOM 中。
hero-detail.component.ts
调用hero.service.ts中的方法来实现页面功能。
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
DashboardComponent
dashboard.component.html
这是一个导航栏,可以定位到任意一个英雄的详情
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
<app-hero-search></app-hero-search>
HeroSearchComponent
Hero-search.component.html
*ngFor
是在一个名叫 heroes$
的列表上迭代,而不是 heroes
。
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes$ | async" >
<a routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
</li>
</ul>
$
是一个命名惯例,用来表明 heroes$
是一个 Observable
,而不是数组。
*ngFor
不能直接使用 Observable
。 不过,它后面还有一个管道字符(|
),后面紧跟着一个 async
,它表示 Angular 的 AsyncPipe
。
AsyncPipe
会自动订阅到 Observable
,不用再在组件类中订阅了。
每当用户在文本框中输入时,这个事件绑定就会使用文本框的值(搜索词)调用 search()
函数。 searchTerms
变成了一个能发出搜索词的稳定的流。
hero-search.component.ts
实现搜索功能
search(term: string): void {
this.searchTerms.next(term);
}
比并且节省服务器资源和网络流量,并最终返回一个可及时给出英雄搜索结果的可观察对象(每次都是 Hero[]
)。
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
- 在传出最终字符串之前debounceTime(300) 将会等待,直到新增字符串的事件暂停了 300 毫秒。 你实际发起请求的间隔永远不会小于 300ms。
- distinctUntilChanged() 会确保只在过滤条件变化时才发送请求。
- switchMap() 会为每个从debounce 和distinctUntilChanged中通过的搜索词调用搜索服务。 它会取消并丢弃以前的搜索可观察对象,只保留最近的。