Angular 中的可观察对象

Observables in Angular

Angular 使用可观察对象作为处理各种常用异步操作的接口。比如:

Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example:

  • EventEmitter 类派生自 Observable

    The EventEmitter class extends Observable.

  • HTTP 模块使用可观察对象来处理 AJAX 请求和响应。

    The HTTP module uses observables to handle AJAX requests and responses.

  • 路由器和表单模块使用可观察对象来监听对用户输入事件的响应。

    The Router and Forms modules use observables to listen for and respond to user-input events.

事件发送器 EventEmitter

Event emitter

Angular 提供了一个 EventEmitter 类,它用来从组件的 @Output() 属性中发布一些值。EventEmitter 扩展了 Observable,并添加了一个 emit() 方法,这样它就可以发送任意值了。当你调用 emit() 时,就会把所发送的值传给订阅上来的观察者的 next() 方法。

Angular provides an EventEmitter class that is used when publishing values from a component through the @Output() decorator. EventEmitter extends Observable, adding an emit() method so it can send arbitrary values. When you call emit(), it passes the emitted value to the next() method of any subscribed observer.

这种用法的例子参见 EventEmitter 文档。下面这个范例组件监听了 openclose 事件:

A good example of usage can be found on the EventEmitter documentation. Here is the example component that listens for open and close events:

<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>

组件的定义如下:

Here is the component definition:

@Component({ selector: 'zippy', template: ` <div class="zippy"> <div (click)="toggle()">Toggle</div> <div [hidden]="!visible"> <ng-content></ng-content> </div> </div>`}) export class ZippyComponent { visible = true; @Output() open = new EventEmitter<any>(); @Output() close = new EventEmitter<any>(); toggle() { this.visible = !this.visible; if (this.visible) { this.open.emit(null); } else { this.close.emit(null); } } }
EventEmitter
      
      
  1. @Component({
  2. selector: 'zippy',
  3. template: `
  4. <div class="zippy">
  5. <div (click)="toggle()">Toggle</div>
  6. <div [hidden]="!visible">
  7. <ng-content></ng-content>
  8. </div>
  9. </div>`})
  10.  
  11. export class ZippyComponent {
  12. visible = true;
  13. @Output() open = new EventEmitter<any>();
  14. @Output() close = new EventEmitter<any>();
  15.  
  16. toggle() {
  17. this.visible = !this.visible;
  18. if (this.visible) {
  19. this.open.emit(null);
  20. } else {
  21. this.close.emit(null);
  22. }
  23. }
  24. }

HTTP

Angular 的 HttpClient 从 HTTP 方法调用中返回了可观察对象。例如,http.get(‘/api’) 就会返回可观察对象。相对于基于承诺(Promise)的 HTTP API,它有一系列优点:

Angular’s HttpClient returns observables from HTTP method calls. For instance, http.get(‘/api’) returns an observable. This provides several advantages over promise-based HTTP APIs:

  • 可观察对象不会修改服务器的响应(和在承诺上串联起来的 .then() 调用一样)。反之,你可以使用一系列操作符来按需转换这些值。

    Observables do not mutate the server response (as can occur through chained .then() calls on promises). Instead, you can use a series of operators to transform values as needed.

  • HTTP 请求是可以通过 unsubscribe() 方法来取消的。

    HTTP requests are cancellable through the unsubscribe() method.

  • 请求可以进行配置,以获取进度事件的变化。

    Requests can be configured to get progress event updates.

  • 失败的请求很容易重试。

    Failed requests can be retried easily.

Async 管道

Async pipe

AsyncPipe 会订阅一个可观察对象或承诺,并返回其发出的最后一个值。当发出新值时,该管道就会把这个组件标记为需要进行变更检查的(译注:因此可能导致刷新界面)。

The AsyncPipe subscribes to an observable or promise and returns the latest value it has emitted. When a new value is emitted, the pipe marks the component to be checked for changes.

下面的例子把 time 这个可观察对象绑定到了组件的视图中。这个可观察对象会不断使用当前时间更新组件的视图。

The following example binds the time observable to the component's view. The observable continuously updates the view with the current time.

@Component({ selector: 'async-observable-pipe', template: `<div><code>observable|async</code>: Time: {{ time | async }}</div>` }) export class AsyncObservablePipeComponent { time = new Observable(observer => setInterval(() => observer.next(new Date().toString()), 1000) ); }
Using async pipe
      
      @Component({
  selector: 'async-observable-pipe',
  template: `<div><code>observable|async</code>:
       Time: {{ time | async }}</div>`
})
export class AsyncObservablePipeComponent {
  time = new Observable(observer =>
    setInterval(() => observer.next(new Date().toString()), 1000)
  );
}
    

路由器 (router)

Router

Router.events以可观察对象的形式提供了其事件。 你可以使用 RxJS 中的 filter() 操作符来找到感兴趣的事件,并且订阅它们,以便根据浏览过程中产生的事件序列作出决定。 例子如下:

Router.eventsprovides events as observables. You can use the filter() operator from RxJS to look for events of interest, and subscribe to them in order to make decisions based on the sequence of events in the navigation process. Here's an example:

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 the 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!')); } }
Router events
      
      
  1. import { Router, NavigationStart } from '@angular/router';
  2. import { filter } from 'rxjs/operators';
  3.  
  4. @Component({
  5. selector: 'app-routable',
  6. templateUrl: './routable.component.html',
  7. styleUrls: ['./routable.component.css']
  8. })
  9. export class Routable1Component implements OnInit {
  10.  
  11. navStart: Observable<NavigationStart>;
  12.  
  13. constructor(private router: Router) {
  14. // Create a new Observable the publishes only the NavigationStart event
  15. this.navStart = router.events.pipe(
  16. filter(evt => evt instanceof NavigationStart)
  17. ) as Observable<NavigationStart>;
  18. }
  19.  
  20. ngOnInit() {
  21. this.navStart.subscribe(evt => console.log('Navigation Started!'));
  22. }
  23. }

ActivatedRoute 是一个可注入的路由器服务,它使用可观察对象来获取关于路由路径和路由参数的信息。比如,ActivateRoute.url 包含一个用于汇报路由路径的可观察对象。例子如下:

The ActivatedRoute is an injected router service that makes use of observables to get information about a route path and parameters. For example, ActivateRoute.url contains an observable that reports the route path or paths. Here's an example:

import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-routable', templateUrl: './routable.component.html', styleUrls: ['./routable.component.css'] }) export class Routable2Component implements OnInit { constructor(private activatedRoute: ActivatedRoute) {} ngOnInit() { this.activatedRoute.url .subscribe(url => console.log('The URL changed to: ' + url)); } }
ActivatedRoute
      
      
  1. import { ActivatedRoute } from '@angular/router';
  2.  
  3. @Component({
  4. selector: 'app-routable',
  5. templateUrl: './routable.component.html',
  6. styleUrls: ['./routable.component.css']
  7. })
  8. export class Routable2Component implements OnInit {
  9. constructor(private activatedRoute: ActivatedRoute) {}
  10.  
  11. ngOnInit() {
  12. this.activatedRoute.url
  13. .subscribe(url => console.log('The URL changed to: ' + url));
  14. }
  15. }

响应式表单 (reactive forms)

Reactive forms

响应式表单具有一些属性,它们使用可观察对象来监听表单控件的值。 FormControlvalueChanges 属性和 statusChanges 属性包含了会发出变更事件的可观察对象。订阅可观察的表单控件属性是在组件类中触发应用逻辑的途径之一。比如:

Reactive forms have properties that use observables to monitor form control values. The FormControlproperties valueChanges and statusChanges contain observables that raise change events. Subscribing to an observable form-control property is a way of triggering application logic within the component class. For example:

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) ); } }
Reactive forms
      
      
  1. import { FormGroup } from '@angular/forms';
  2.  
  3. @Component({
  4. selector: 'my-component',
  5. template: 'MyComponent Template'
  6. })
  7. export class MyComponent implements OnInit {
  8. nameChangeLog: string[] = [];
  9. heroForm: FormGroup;
  10.  
  11. ngOnInit() {
  12. this.logNameChange();
  13. }
  14. logNameChange() {
  15. const nameControl = this.heroForm.get('name');
  16. nameControl.valueChanges.forEach(
  17. (value: string) => this.nameChangeLog.push(value)
  18. );
  19. }
  20. }