Angular 使用可观察对象作为处理各种常用异步操作的接口。比如:
EventEmitter
类派生自Observable
。- HTTP 模块使用可观察对象来处理 AJAX 请求和响应。
- 路由器和表单模块使用可观察对象来监听对用户输入事件的响应。
EventEmitter
Angular 提供了一个 EventEmitter
类,它用来通过组件的 @Output()
装饰器 发送一些值。EventEmitter
扩展了 RxJS Subject
,并添加了一个 emit()
方法,这样它就可以发送任意值了。当你调用 emit()
时,就会把所发送的值传给订阅上来的观察者的 next()
方法。 我们来查看一下该类的定义:
export declare class EventEmitter<T extends any> extends Subject<T> { /** * Creates an instance of this class that can * deliver events synchronously or asynchronously. * * @param isAsync When true, deliver events asynchronously. * */ constructor(isAsync?: boolean); /** * 发出包含给定值的事件。 */ emit(value?: T): void; /** *注册此实例发出的事件的处理程序。 */ subscribe(generatorOrNext?: any, error?: any, complete?: any): Subscription; } 复制代码
EventEmitter 与指令@Output 一起在组件中使用以同步或异步方式发送自定义事件,并通过订阅实例来注册这些事件的处理程序。
接下来我们演示一个案例,在组件之间传递数据。
子组件 zippy.component.ts
import { Component, OnInit, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-zippy', templateUrl: ' <div class="zippy"> <div (click)="toggle()">点击该文本</div> <div [hidden]="visible"> <ng-content></ng-content> </div> </div> ' }) export class ZippyComponent implements OnInit { visible = true; // tslint:disable-next-line: no-output-native @Output() open = new EventEmitter<any>(); // tslint:disable-next-line: no-output-native @Output() close = new EventEmitter<any>(); constructor() { } ngOnInit(): void { } toggle() { this.visible = !this.visible; if (this.visible) { this.close.emit('关闭'); } else { this.open.emit('打开'); this.open.subscribe((data) => { console.log('open subscribe:' + data); }); } } } 复制代码
父组件 home4.component.ts
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-home4', templateUrl: ' <app-zippy (open)="onOpen($event)" (close)="onClose($event)">我是home组件的内容</app-zippy> ' }) export class Home4Component implements OnInit { constructor() { } ngOnInit(): void { } onOpen(e) { console.log(e); } onClose(e) { console.log(e); } } 复制代码
运行项目,在页面上点击文本内容,注意观察控制台输出的内容。如下图所示:
上文中关于 ng-content
标签的使用,感兴趣的朋友可以去阅读一下:Angular开发实践(八): 使用ng-content进行组件内容投射
HTTP
Angular 的 HttpClient
从 HTTP 方法调用中返回了可观察对象。例如,http.get(‘/api’)
就会返回可观察对象。相对于基于承诺(Promise)的 HTTP API,它有一系列优点:
- 可观察对象不会修改服务器的响应(和在承诺上串联起来的
.then()
调用一样)。反之,你可以使用一系列操作符来按需转换这些值。 - HTTP 请求是可以通过
unsubscribe()
方法来取消的。 - 请求可以进行配置,以获取进度事件的变化。
- 失败的请求很容易重试。
关于讲解数据交互那一部分内容时,对于 get、post 请求都有测试案例,现在回头看当时比较疑惑的内容,现在就豁然开朗了。
Async 管道
AsyncPipe 会订阅一个可观察对象或承诺,并返回其发出的最后一个值。当发出新值时,该管道就会把这个组件标记为需要进行变更检查的(译注:因此可能导致刷新界面)。
下面的例子把 time
这个可观察对象绑定到了组件的视图中。这个可观察对象会不断使用当前时间更新组件的视图。
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; @Component({ selector: 'app-asyncpipe', templateUrl: ' <div><code>observable|async</code>: Time: {{ time | async }}</div> ' }) export class AsyncpipeComponent implements OnInit { time: Observable<any>; constructor() { } ngOnInit(): void { this.time = new Observable(observer => { setInterval(() => { observer.next(new Date().toString()); }, 1000); }); } } 复制代码
这样写就相当于订阅了 time,会实时接收 next 过来的值,Observable 定义如上,用来逐秒打印时间,页面接收的值类型为 Observable。
页面测试效果:
observable|async: Time: Tue Apr 14 2020 09:39:46 GMT+0800 (中国标准时间) 复制代码
若是要接收 object 对象,需要这样取值。
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; @Component({ selector: 'app-asyncpipe', templateUrl: ' <ng-container *ngIf="time2 |async as tim"> <div> {{tim.date}}---{{tim.time}} </div> </ng-container> ' }) export class AsyncpipeComponent implements OnInit { time2: Observable<any>; constructor() { } ngOnInit(): void { this.time2 = new Observable(observer => { setInterval(() => { const dd = new Date(); observer.next({ date: dd.toString(), time: dd.toTimeString() }); }, 1000); }); } } 复制代码
页面测试结果:
Tue Apr 14 2020 09:49:38 GMT+0800 (中国标准时间)---09:49:38 GMT+0800 (中国标准时间) 复制代码
路由器 (router)
Router.events
以可观察对象的形式提供了其事件。 你可以使用 RxJS 中的 filter()
操作符来找到感兴趣的事件,并且订阅它们,以便根据浏览过程中产生的事件序列作出决定。 例子如下:
import { Router, NavigationStart } from '@angular/router'; import { filter } from 'rxjs/operators'; @Component({ selector: 'app-routable', templateUrl: './routable.component.html', styleUrls: ['./routable.component.css'] }) export class Routable1Component implements OnInit { navStart: Observable<NavigationStart>; constructor(private router: Router) { // Create a new Observable that publishes only the NavigationStart event this.navStart = router.events.pipe( filter(evt => evt instanceof NavigationStart) ) as Observable<NavigationStart>; } ngOnInit() { this.navStart.subscribe(evt => console.log('Navigation Started!')); } } 复制代码
ActivatedRoute 是一个可注入的路由器服务,它使用可观察对象来获取关于路由路径和路由参数的信息。比如,ActivatedRoute.url
包含一个用于汇报路由路径的可观察对象。结合 Angular基础知识学习(二)中动态路由的JS跳转,此处我们仅需要修改 product-detail.component.ts
文件:
import { Component, OnInit } from '@angular/core'; import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-product-detail', templateUrl: './product-detail.component.html', styleUrls: ['./product-detail.component.css'] }) export class ProductDetailComponent implements OnInit { constructor(public router: ActivatedRoute) { } ngOnInit(): void { this.router.url.subscribe(url => console.log('The URL changed to: ' + url)); } } 复制代码
页面测试结果为:
又比如 ActivatedRoute.queryParams
包含路由跳转传递参数的可观察对象。还是结合我之前文章的案例,修改 news.component.ts
文件:
import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-news', templateUrl: './news.component.html', styleUrls: ['./news.component.css'] }) export class NewsComponent implements OnInit { nums: any[] = []; constructor(public router: ActivatedRoute) { this.router.queryParams.subscribe((data) => { console.log(data); }); } ngOnInit(): void { } } 复制代码
页面测试结果为:
响应式表单 (reactive forms)
响应式表单具有一些属性,它们使用可观察对象来监听表单控件的值。 FormControl
的 valueChanges
属性和 statusChanges
属性包含了会发出变更事件的可观察对象。订阅可观察的表单控件属性是在组件类中触发应用逻辑的途径之一。比如:
import { FormGroup } from '@angular/forms'; @Component({ selector: 'my-component', template: 'MyComponent Template' }) export class MyComponent implements OnInit { nameChangeLog: string[] = []; heroForm: FormGroup; ngOnInit() { this.logNameChange(); } logNameChange() { const nameControl = this.heroForm.get('name'); nameControl.valueChanges.forEach( (value: string) => this.nameChangeLog.push(value) ); } }