Fork me on GitHub

Angular英雄之旅总结

DDT·2022-03-21 22:39·148 次阅读

Angular英雄之旅总结

Angular英雄之旅总结

https://angular.cn/tutorial/toh-pt6#chaining-rxjs-operators

创建项目#

安装Angular Cli后 powershell 输入

Copy
ng new project

启动项目

Copy
cd project ng serve --open

--open 标志会打开浏览器,并访问 http://localhost:4200/

生成组件#

生成命令:

Copy
ng generate component <component>

组成

Copy
|-<component>.component.css |-<component>.component.html --用 HTML 写的 |-<component>.component.spec.ts |-<component>.component.ts --用 TypeScript 写的

组件结构#

ts模板

Copy
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模板

Copy
<-->绑定表达式中的 uppercase 位于管道操作符( | )的右边,用来调用内置管道 UppercasePipe。</--> <h2>{{hero.name | uppercase}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div><span>name: </span>{{hero.name}}</div>

双向绑定#

语法

Copy
<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声明模块

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

循环遍历#

Copy
<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事件

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

ts模板中添加方法体

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

条件渲染#

Copy
<div *ngIf="selectedHero">

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

样式绑定#

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

主从组件#

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

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

子组件输入属性

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

子组件ts模板

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

提供服务#

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

创建服务#

Copy
ng generate service <service>

生成

Copy
import { Injectable } from '@angular/core'; /* 这个新的服务导入了 Angular 的 Injectable 符号,并且给这个服务类添加了 @Injectable() 装饰器。 它把这个类标记为依赖注入系统的参与者之一。 */ @Injectable({ providedIn: 'root', }) export class HeroService { constructor() { } }

添加方法,返回模拟的英雄列表

Copy
getHeroes(): Hero[] { return HEROES; }

注入服务#

组件ts模板中

往构造函数中添加一个私有的 heroService,其类型为 HeroService

Copy
constructor(private heroService: HeroService) {}

调用服务方法

Copy
getHeroes(): void { this.heroes = this.heroService.getHeroes(); }
Copy
ngOnInit(): void { this.getHeroes(); }

可视数据#

可观察(Observable)的数据

HeroService.getHeroes() 必须具有某种形式的异步函数签名

ObservableRxJS 库中的一个关键类。

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

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

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

订阅方法#

heroes.component.ts 中

Copy
getHeroes(): void { this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes); }

添加导航#

添加路由#

在 Angular 中,最好在一个独立的顶层模块中加载和配置路由器,它专注于路由功能,然后由根模块 AppModule 导入它。

Copy
ng generate module app-routing --flat --module=app

--flat 把这个文件放进了 src/app 中,而不是单独的目录中。
--module=app 告诉 CLI 把它注册到 AppModuleimports 数组中

路由模板

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

路由出口#

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

路由链接#

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

默认路由#

Copy
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },

路由参数#

path 中的冒号(:)表示 :id 是一个占位符,它表示某个特定英雄的 id

Copy
{ path: 'detail/:id', component: HeroDetailComponent },

获取数据#

启用 HTTP#

Copy
import { HttpClientModule } from '@angular/common/http';

模拟数据#

Copy
npm install angular-in-memory-web-api --save

获取英雄#

Copy
/** 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() 来记录各种操作。

错误处理#

Copy
/** * 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); }; }

单个英雄#

Copy
/** 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}`)) ); }

修改英雄#

添加事件

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

添加方法

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

添加服务

Copy
/** 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')) ); }

删除英雄#

添加事件

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

添加方法

Copy
delete(hero: Hero): void { this.heroes = this.heroes.filter(h => h !== hero); this.heroService.deleteHero(hero.id).subscribe(); }

虽然这个组件把删除英雄的逻辑委托给了 HeroService,但仍保留了更新它自己的英雄列表的职责。 组件的 delete() 方法会在 HeroService 对服务器的操作成功之前,先从列表中移除要删除的英雄

添加服务

Copy
/** 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')) ); }

搜索服务#

Copy
/* 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 @   浩然哉  阅读(148)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
/* 看板娘 */
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示
目录