最近angular刚刚升上8.0版本,寻思着仔细阅读一下官网,查漏补缺。
官网教程——英雄指南实践 官网例子实践
使用In-memory Web API模拟数据服务器
从 npm 中安装这个内存 Web API 包
1 npm install angular-in-memory-web-api --save
导入 HttpClientInMemoryWebApiModule 和 InMemoryDataService 类(你很快就要创建它)。
1 2 3 4 5 6 7 8 HttpClientModule, // The HttpClientInMemoryWebApiModule module intercepts HTTP requests // and returns simulated server responses. // Remove it when a real server is ready to receive requests. HttpClientInMemoryWebApiModule.forRoot( InMemoryDataService, { dataEncapsulation: false } )
src/app/in-memory-data.service.ts 类是通过下列命令生成的:
1 ng generate service InMemoryData
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { InMemoryDbService } from 'angular-in-memory-web-api' ;import { Hero } from './hero' ;import { Injectable } from '@angular/core' ;@Injectable ({ providedIn: 'root' , }) export class InMemoryDataService implements InMemoryDbService { createDb() { const heroes = [ { id: 11 , name: 'Mr. Nice' }, { id: 12 , name: 'Narco' }, { id: 13 , name: 'Bombasto' }, { id: 14 , name: 'Celeritas' }, { id: 15 , name: 'Magneta' }, { id: 16 , name: 'RubberMan' }, { id: 17 , name: 'Dynama' }, { id: 18 , name: 'Dr IQ' }, { id: 19 , name: 'Magma' }, { id: 20 , name: 'Tornado' } ]; return {heroes}; } genId(heroes: Hero[]): number { return heroes.length > 0 ? Math .max(...heroes.map(hero => hero.id)) + 1 : 11 ; } }
AsyncPipe 1 <li *ngFor ="let hero of heroes$ | async" >
$ 是一个命名惯例,用来表明 heroes$ 是一个 Observable,而不是数组。
| async
表示 Angular 的 AsyncPipe, 会自动订阅到 Observable,这样你就不用再在组件类中订阅了。
核心知识 架构 NgModule 简介
NgModule 是一个带有 @NgModule() 装饰器的类。@NgModule() 装饰器是一个函数,它接受一个元数据对象,该对象的属性用来描述这个模块。其中最重要的属性如下。
declarations(可声明对象表) —— 那些属于本 NgModule 的组件、指令、管道。
exports(导出表) —— 那些能在其它模块的组件模板中使用的可声明对象的子集。
imports(导入表) —— 那些导出了本模块中的组件模板所需的类的其它模块。
providers —— 本模块向全局服务中贡献的那些服务的创建器。 这些服务能被本应用中的任何部分使用。(你也可以在组件级别指定服务提供商,这通常是首选方式。)
bootstrap —— 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性。
NgModule 和 JavaScript 的模块 NgModule 系统与 JavaScript(ES2015)用来管理 JavaScript 对象的模块系统,两者不同但互补。你可以使用它们来共同编写你的应用。
在JavaScript中,每个文件是一个模块,文件中定义的所有对象都从属于那个模块。通过export关键字,模块可以把它的某些对象声明为公共的。其它JavaScript模块可以使用import语句来访问这些公共对象。
服务与依赖注入简介 服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。
组件与模板 模板语法 HTML是Angular 模板的语言。几乎所有的 HTML 语法都是有效的模板语法。 但值得注意的例外是 <script>
元素,它被禁用了,以阻止脚本注入攻击的风险。
NgModel - 使用[(ngModel)]双向绑定到表单元素 ngModel展开形式用法
1 2 3 4 5 6 7 8 9 10 11 12 13 <input [(ngModel)]="currentHero.name" > <input [ngModel]="currentHero.name" (ngModelChange)="currentHero.name=$event" > <input [ngModel]="currentHero.name" (ngModelChange)="setUppercaseName($event)" >
带 trackBy 的 ngFor 避免ngFor刷新整个列表
ngFor指令有时候会性能较差,特别是在大型列表中。对一个条目的一丁点改动、移除或添加,都会导致级联的 DOM 操作。在 Angular 看来,它只是一个由新的对象引用构成的新列表。 如果给它指定一个 trackBy,Angular就可以避免这种折腾。往组件中添加一个方法,它会返回 NgFor应该追踪的值。
1 2 trackByHeroes(index: number , hero: Hero): number { return hero.id; }
1 2 3 4 // html <div *ngFor ="let hero of heroes; trackBy: trackByHeroes" > ({{hero.id}}) {{hero.name}} </div >
如果没有 trackBy,这些按钮都会触发完全的 DOM 元素替换。有了 trackBy,则只有修改了 id 的按钮才会触发元素替换。
非空断言操作符(!) 如果类型检查器在运行期间无法确定一个变量是 null 或 undefined,那么它也会抛出一个错误。 你自己可能知道它不会为空,但类型检查器不知道。 所以你要告诉类型检查器,它不会为空,这时就要用到非空断言操作符。
1 2 3 4 5 // 与安全导航操作符不同的是,非空断言操作符不会防止出现 null 或 undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检测"。 <div *ngIf ="hero" > The hero's name is {{hero!.name}} </div >
类型转换函数 $any ($any( <表达式> )) 有时候,绑定表达式可能会报类型错误,并且它不能或很难指定类型。要消除这种报错,你可以使用 $any 转换函数来把表达式转换成 any 类型。
1 2 3 4 5 6 7 8 9 10 11 <div > The hero's marker is {{$any(hero).marker}} </div > // $any 转换函数可以和 this 联合使用,以便访问组件中未声明过的成员。 <div > Undeclared members is {{$any(this).member}} </div >
用户输入 传入 $event 把整个 DOM 事件传到方法中,因为这样组件会知道太多模板的信息。这就违反了模板(用户看到的)和组件(应用如何处理用户数据)之间的分离关注原则。我们用模板引用变量来解决这个问题。
从一个模板引用变量中获得用户输入
1 2 3 4 5 6 7 8 @Component ({ selector: 'app-loop-back' , template: ` <input #box (keyup)="0"> <p>{{box.value}}</p> ` }) export class LoopbackComponent { }
只有在应用做了些异步事件(如击键),Angular 才更新绑定(并最终影响到屏幕)。除非你绑定一个事件,否则这将完全无法工作。
生命周期钩子
通过 setter 截听输入属性值的变化 使用一个输入属性的 setter,以拦截父组件中值的变化,并采取行动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { Component, Input } from '@angular/core' ; @Component ({ selector: 'app-name-child' , template: '<h3>"{{name}}"</h3>' }) export class NameChildComponent { private _name = '' ; @Input () set name(name: string ) { this ._name = (name && name.trim()) || '<no name set>' ; } get name(): string { return this ._name; } }
通过ngOnChanges()来截听输入属性值的变化 当需要监视多个、交互式输入属性的时候,本方法比用属性的 setter 更合适。
父组件与子组件通过本地变量互动 父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component ({ selector: 'app-countdown-parent-lv' , template: ` <h3>Countdown to Liftoff (via local variable)</h3> <button (click)="timer.start()">Start</button> <button (click)="timer.stop()">Stop</button> <div class="seconds">{{timer.seconds}}</div> // #timer代表子组件。这样父组件的模板就得到了子组件的引用,于是可以在父组件的模板中访问子组件的所有属性和方法。 <app-countdown-timer #timer></app-countdown-timer> ` , styleUrls: ['../assets/demo.css' ] }) export class CountdownLocalVarParentComponent { }
父组件调用@ViewChild() 这个本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。 如果父组件的类需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。 当父组件类需要这种访问时,可以把子组件作为ViewChild,注入到父组件里面。
1 2 3 4 5 6 7 export class CountdownViewChildParentComponent implements AfterViewInit { @ViewChild (CountdownTimerComponent) private timerComponent: CountdownTimerComponent; }
父组件和子组件通过服务来通讯 父组件和它的子组件共享同一个服务,利用该服务在组件家族内部实现双向通讯。 该服务实例的作用域被限制在父组件和其子组件内。这个组件子树之外的组件将无法访问该服务或者与它们通讯。
组件样式 特殊的选择器
:host —— 用来选择组件宿主元素中的元素(相对于组件模板内部的元素)。
1 2 3 4 // 要把宿主样式作为条件,就要像函数一样把其它选择器放在 :host 后面的括号中。 :host(.active) { border-width : 3px ; }
视图封装模式
通过在组件的元数据上设置视图封装模式,你可以分别控制每个组件的封装模式。 可选的封装模式一共有如下几种:ShadowDom、Native、Emulated、None。
Angular 元素(Elements)概览
Angular 元素就是打包成自定义元素的 Angular 组件。所谓自定义元素就是一套与具体框架无关的用于定义新 HTML 元素的 Web 标准。 自定义元素扩展了 HTML,它允许你定义一个由 JavaScript 代码创建和控制的标签。 浏览器会维护一个自定义元素(也叫 Web Components)的注册表 CustomElementRegistry,它把一个可实例化的 JavaScript 类映射到 HTML 标签上。
// todo: 这一部分可以做一个实例验证之后再写。
动态组件 // todo: 这一章是动态广告 组件的实例。可以写一下验证之后再来补充。
属性型指令
在 Angular 中有三种类型的指令:
组件 — 拥有模板的指令
结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令。(NgIf)
属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。(ngStyle)
编写属性型指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import { Directive, ElementRef, HostListener, Input } from '@angular/core' ;@Directive ({ selector: '[appHighlight]' }) export class HighlightDirective { constructor (private el: ElementRef ) { } @Input ('appHighlight' ) highlightColor: string ; @HostListener ('mouseenter' ) onMouseEnter() { this .highlight(this .highlightColor || 'red' ); } private highlight(color: string ) { this .el.nativeElement.style.backgroundColor = color; } }
1 2 // 使用属性型指令 <p appHighlight > Highlight me!</p >
结构型指令
结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,比如添加、移除或维护这些元素。结构型指令非常容易识别,星号(*)被放在指令的属性名之前。 可以在一个宿主元素上应用多个属性型指令,但只能应用一个结构型指令。
NgIf NgIf 会从 DOM 中移除它的宿主元素,而不是隐藏。
星号(*)前缀
星号是一个用来简化更复杂语法的“语法糖”。 从内部实现来说,Angular 把 *ngIf 属性 翻译成一个 元素 并用它来包裹宿主元素,代码如下:
1 2 3 4 5 <div *ngIf ="hero" class ="name" > {{hero.name}}</div > // 转化后 <ng-template [ngIf ]="hero" > <div class ="name" > {{hero.name}}</div > </ng-template >
微语法
Angular 微语法能让你通过简短的、友好的字符串来配置一个指令。 微语法解析器把这个字符串翻译成 上的属性。
// todo: 详细理解一下微语法
<ng-container>
的应用
Angular 的 是一个分组元素,但它不会污染样式或元素布局,因为 Angular 压根不会把它放进 DOM 中。
<ng-container>
的使用场景
有些 HTML 元素需要所有的直属下级都具有特定的类型。 比如,<select>
元素要求直属下级必须为 <option>
,那就没办法把这些选项包装进 <div>
或 <span>
中。
1 2 3 4 5 6 7 8 9 10 11 12 // 下拉列表为空 <div > Pick your favorite hero (<label > <input type ="checkbox" checked (change )="showSad = !showSad" > show sad</label > ) </div > <select [(ngModel )]="hero" > <span *ngFor ="let h of heroes" > <span *ngIf ="showSad || h.emotion !== 'sad'" > <option [ngValue ]="h" > {{h.name}} ({{h.emotion}})</option > </span > </span > </select >
1 2 3 4 5 6 7 8 9 10 11 12 // 下拉框工作正常 <div > Pick your favorite hero (<label > <input type ="checkbox" checked (change )="showSad = !showSad" > show sad</label > ) </div > <select [(ngModel )]="hero" > <ng-container *ngFor ="let h of heroes" > <ng-container *ngIf ="showSad || h.emotion !== 'sad'" > <option [ngValue ]="h" > {{h.name}} ({{h.emotion}})</option > </ng-container > </ng-container > </select >
管道
纯(pure)管道与非纯(impure)管道 1 2 3 4 5 @Pipe ({ name: 'flyingHeroesImpure' , pure: false })
纯管道 —— Angular只有在它检测到输入值发生了纯变更时才会执行纯管道。 纯变更是指对原始类型值(String、Number、Boolean、Symbol)的更改,或者对对象引用(Date、Array、Function、Object)的更改。会忽略(复合)对象内部的更改。(它保证了速度)
非纯管道 —— Angular会在每个组件的变更检测周期中执行非纯管道。非纯管道可能会被调用很多次,和每个按键或每次鼠标移动一样频繁。
表单 Angular 表单检测
一般来说:
响应式表单更健壮:它们的可扩展性、可复用性和可测试性更强。 如果表单是应用中的关键部分,或者你已经准备使用响应式编程模式来构建应用,请使用响应式表单。
模板驱动表单在往应用中添加简单的表单时非常有用,比如邮件列表的登记表单。它们很容易添加到应用中,但是不像响应式表单那么容易扩展。如果你有非常基本的表单需求和简单到能用模板管理的逻辑,请使用模板驱动表单。
响应式表单 部分模型更新
有两种更新模型值的方式:
使用 setValue() 方法来为单个控件设置新值。 setValue() 方法会严格遵循表单组的结构,并整体性替换控件的值。
使用 patchValue() 方法可以用对象中所定义的任何属性为表单模型进行替换。
修补(Patching)模型值
setValue() 方法的严格检查可以帮助你捕获复杂表单嵌套中的错误,而 patchValue() 在遇到那些错误时可能会默默的失败。
简单的表单验证 显示表单状态
你可以通过该 FormGroup 实例的 status 属性来访问其当前状态。
1 2 3 4 // 表单无效(验证不通过),值为INVALID <p > Form Status: {{ profileForm.status }} </p >
使用表单数组管理动态控件
FormArray 是 FormGroup 之外的另一个选择,用于管理任意数量的匿名控件。像 FormGroup 实例一样,你也可以往 FormArray 中动态插入和移除控件,并且 FormArray 实例的值和验证状态也是根据它的子控件计算得来的。 不过,你不需要为每个控件定义一个名字作为 key,因此,如果你事先不知道子控件的数量,这就是一个很好的选择。
下面的例子展示了如何在 ProfileEditor 中管理一组绰号(aliases)。
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { FormArray } from '@angular/forms' ;profileForm = this .fb.group({ firstName: ['' , Validators.required], lastName: ['' ], address: this .fb.group({ street: ['' ], city: ['' ], state: ['' ], zip: ['' ] }), aliases: this .fb.array([ this .fb.control('' ) ]) });
访问
1 2 3 4 5 6 7 8 9 get aliases() { return this .profileForm.get('aliases' ) as FormArray; } addAlias() { this .aliases.push(this .fb.control('' )); }
在模板中显示表单数组
1 2 3 4 5 6 7 8 9 10 11 <div formArrayName ="aliases" > <h3 > Aliases</h3 > <button (click )="addAlias()" > Add Alias</button > <div *ngFor ="let address of aliases.controls; let i=index" > <label > Alias: <input type ="text" [formControlName ]="i" > </label > </div > </div >
模板驱动表单 创建表单组件 调试技巧,通过getter属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Component ({ selector: 'app-hero-form' , }) export class HeroFormComponent { powers = ['Really Smart' , 'Super Flexible' , 'Super Hot' , 'Weather Changer' ]; model = new Hero(18 , 'Dr IQ' , this .powers[0 ], 'Chuck Overstreet' ); submitted = false ; onSubmit() { this .submitted = true ; } get diagnostic() { return JSON .stringify(this .model); } }
通过 ngModel 跟踪修改状态与有效性验证
NgModel 指令不仅仅跟踪状态。它还使用特定的 Angular CSS 类来更新控件,以反映当前状态。 可以利用这些 CSS 类来修改控件的外观,显示或隐藏消息。
状态
为真时的 CSS 类
为假时的 CSS 类
控件被访问过。
ng-touched
ng-untouched
控件的值变化了。
ng-dirty
ng-pristine
控件的值有效。
ng-valid
ng-invalid
css例子
1 2 3 4 5 6 7 .ng-valid [required] , .ng-valid .required { border-left : 5px solid #42A948 ; } .ng-invalid :not(form) { border-left : 5px solid #a94442 ; }
调试技巧,spy.className
1 2 3 4 5 6 // 往姓名 <input > 标签上添加名叫 spy 的临时模板引用变量, 然后用这个 spy 来显示它上面的所有 CSS 类。 <input type ="text" class ="form-control" id ="name" required [(ngModel )]="model.name" name ="name" #spy > <br > TODO: remove this: {{spy.className}}
验证器函数添加到模板驱动表单 在模板驱动表单中,你不用直接访问 FormControl 实例。所以不能像响应式表单中那样把验证器传进去,而应该在模板中添加一个指令。
自定义验证函数(略)
指令
1 2 3 4 5 6 7 8 9 10 11 12 @Directive ({ selector: '[appForbiddenName]' , providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true }] }) export class ForbiddenValidatorDirective implements Validator { @Input ('appForbiddenName' ) forbiddenName: string ; validate(control: AbstractControl): {[key: string ]: any } | null { return this .forbiddenName ? forbiddenNameValidator(new RegExp (this .forbiddenName, 'i' ))(control) : null ; } }
模板中使用
1 2 3 <input id ="name" name ="name" class ="form-control" required minlength ="4" appForbiddenName ="bob" [(ngModel )]="hero.name" #name ="ngModel" >
异步验证 异步验证总是会在同步验证之后执行,并且只有当同步验证成功了之后才会执行。如果更基本的验证方法已经失败了,那么这能让表单避免进行可能会很昂贵的异步验证过程,比如 HTTP 请求。
例子: 在异步验证结束前会pending
1 2 <input [(ngModel )]="name" #model ="ngModel" appSomeAsyncValidator > <app-spinner *ngIf ="model.pending" > </app-spinner >
1 2 3 4 5 6 7 8 9 10 11 12 13 @Injectable ({ providedIn: 'root' })export class UniqueAlterEgoValidator implements AsyncValidator { constructor (private heroesService: HeroesService ) {} validate( ctrl: AbstractControl ): Promise <ValidationErrors | null > | Observable<ValidationErrors | null > { return this .heroesService.isAlterEgoTaken(ctrl.value).pipe( map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null )), catchError(() => null ) ); } }
1 2 3 interface HeroesService { isAlterEgoTaken: (alterEgo: string ) => Observable<boolean >; }
性能问题 默认情况下,每当表单值变化之后,都会执行所有验证器。对于同步验证器,没有什么会显著影响应用性能的地方。不过,异步验证器通常会执行某种 HTTP 请求来对控件进行验证。如果在每次按键之后都发出 HTTP 请求会给后端 API 带来沉重的负担,应该尽量避免。
我们可以把 updateOn 属性从 change(默认值)改成 submit 或 blur 来推迟表单验证的更新时机。
1 2 3 4 5 6 7 // 对于模板驱动表单: <input [(ngModel )]="name" [ngModelOptions ]="{updateOn: 'blur'}" > // 对于响应式表单: new FormControl('', {updateOn: 'blur'});
NgModule 服务提供商 服务提供商作用域 providedIn 与 NgModule
1 2 3 4 5 6 7 8 9 import { Injectable } from '@angular/core' ;import { UserModule } from './user.module' ;@Injectable ({ providedIn: UserModule, }) export class UserService {}
上面的例子展示的就是在模块中提供服务的首选方式。之所以推荐该方式,是因为当没有人注入它时,该服务就可以被摇树优化掉。 如果没办法指定哪个模块该提供这个服务,你也可以在那个模块中为该服务声明一个提供商:
1 2 3 4 5 6 7 8 9 import { NgModule } from '@angular/core' ;import { UserService } from './user.service' ;@NgModule ({ providers: [UserService], }) export class UserModule {}
单例服务 提供单例服务
在 Angular 中有两种方式来生成单例服务:
声明该服务应该在应用的根上提供。
把该服务包含在 AppModule 或某个只会被 AppModule 导入的模块中。
forRoot() 如果某个模块同时提供了服务提供商和可声明对象(组件、指令、管道),那么当在某个子注入器中加载它的时候(比如路由),就会生成多个该服务提供商的实例。 而存在多个实例会导致一些问题,因为这些实例会屏蔽掉根注入器中该服务提供商的实例,而它的本意可能是作为单例对象使用的。
因此,Angular 提供了一种方式来把服务提供商从该模块中分离出来,以便该模块既可以带着 providers 被根模块导入,也可以不带 providers 被子模块导入。
在该模块上创建一个静态方法 forRoot()(习惯名称)。
把那些服务提供商放进 forRoot 方法中,参见下面的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 constructor (@Optional () config: UserServiceConfig ) { if (config) { this ._userName = config.userName; } } static forRoot(config: UserServiceConfig): ModuleWithProviders { return { ngModule: CoreModule, providers: [ {provide: UserServiceConfig, useValue: config } ] }; } import { CoreModule } from './core/core.module' ;@NgModule ({ imports: [ ... CoreModule.forRoot({userName: 'Miss Marple' }), ], }) export class AppModule { }
防止重复导入 CoreModule 1 2 3 4 5 6 7 8 9 10 constructor (@Optional () @SkipSelf () parentModule: CoreModule ) { if (parentModule) { throw new Error ( 'CoreModule is already loaded. Import it in the AppModule only' ); } }
依赖注入 Angular注入依赖 那些需要其它服务的服务 可选依赖,@Optional
1 2 3 4 5 6 7 import { Optional } from '@angular/core' ;constructor (@Optional () private logger: Logger ) { if (this .logger) { this .logger.log(some_message); } }
依赖提供商 Provider 对象字面量 1 2 3 4 providers: [Logger] [{ provide: Logger, useClass: Logger }]
代替类提供商 带依赖的类提供商
1 2 3 4 5 6 7 8 9 @Injectable ()export class EvenBetterLogger extends Logger { } [ UserService, { provide: Logger, useClass: EvenBetterLogger }]
别名类提供商
1 2 3 4 5 6 7 8 9 10 11 [ NewLogger, { provide: OldLogger, useClass: NewLogger}] [ NewLogger, { provide: OldLogger, useExisting: NewLogger}]
值提供商 1 [{ provide: Logger, useValue: silentLogger }]
非类依赖
注入字符串、函数或对象。
1 [{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
另一个为非类依赖选择提供商令牌的解决方案是定义并使用 InjectionToken 对象。
1 2 3 4 5 6 7 8 9 10 export const APP_CONFIG = new InjectionToken<AppConfig>('app.config' );providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }] constructor (@Inject (APP_CONFIG) config: AppConfig ) { this .title = config.title; }
工厂提供商 有时候你需要动态创建依赖值,创建时需要的信息你要等运行期间才能拿到。 比如,你可能需要某个在浏览器会话过程中会被反复修改的信息,而且这个可注入服务还不能独立访问这个信息的源头。
1 2 3 4 5 export let heroServiceProvider = { provide: HeroService, useFactory: heroServiceFactory, deps: [Logger, UserService] };
HttpClient 获取 JSON 数据
1 2 3 4 getConfig() { return this .http.get<Config>(this .configUrl); }
1 2 3 4 5 getConfigResponse(): Observable<HttpResponse<Config>> { return this .http.get<Config>( this .configUrl, { observe: 'response' }); }
错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console .error('An error occurred:' , error.error.message); } else { console .error( `Backend returned code ${error.status} , ` + `body was: ${error.error} ` ); } return throwError( 'Something bad happened; please try again later.' ); };
1 2 3 4 5 6 getConfig() { return this .http.get<Config>(this .configUrl) .pipe( catchError(this .handleError) ); }
高级用法 请求的防抖(debounce)
1 2 3 4 5 6 7 8 9 10 // 如果每次击键都发送一次请求就太昂贵了。 最好能等到用户停止输入时才发送请求。 <input (keyup )="search($event.target.value)" id ="name" placeholder ="Search" /> <ul > <li *ngFor ="let package of packages$ | async" > <b > {{package.name}} v.{{package.version}}</b > - <i > {{package.description}}</i > </li > </ul >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withRefresh = false ; packages$: Observable<NpmPackageInfo[]>; private searchText$ = new Subject<string >(); search(packageName: string ) { this .searchText$.next(packageName); } ngOnInit() { this .packages$ = this .searchText$.pipe( debounceTime(500 ), distinctUntilChanged(), switchMap(packageName => this .searchService.search(packageName, this .withRefresh)) ); } constructor (private searchService: PackageSearchService ) { }
拦截请求和响应
编写拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { Injectable } from '@angular/core' ;import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http' ; import { Observable } from 'rxjs' ;@Injectable ()export class NoopInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any >, next: HttpHandler): Observable<HttpEvent<any >> { return next.handle(req); } }
提供拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true }, import { HTTP_INTERCEPTORS } from '@angular/common/http' ;import { NoopInterceptor } from './noop-interceptor' ;export const httpInterceptorProviders = [ { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true }, ];
拦截器顺序
Angular 会按照你提供它们的顺序应用这些拦截器。 如果你提供拦截器的顺序是先 A,再 B,再 C,那么请求阶段的执行顺序就是 A->B->C,而响应阶段的执行顺序则是 C->B->A。
小结 基本上比较细致的浏览了一遍angular官网上的内容,记录了一些在平时的运用中较少接触到的内容。至于RxJS,animation,i18n,库的开发,可以看作比较独立的内容,再此就不多赘述了。
为正常使用来必力评论功能请激活JavaScript