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。
- 你学会了如何使用“可观察对象”。
《英雄之旅》教程结束了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!