填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

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

    You can define custom events that send observable output data from a child to a parent component.

  • 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.

在元件之間傳遞資料

Transmitting data between components

Angular 提供了一個 EventEmitter 類別,它用來透過元件的 @Output() 裝飾器 傳送一些值。EventEmitter 擴充套件了 RxJS Subject,並添加了一個 emit() 方法,這樣它就可以傳送任意值了。當你呼叫 emit() 時,就會把所傳送的值傳給訂閱上來的觀察者的 next() 方法。

Angular provides an EventEmitter class that is used when publishing values from a component through the @Output() decorator. EventEmitter extends RxJS Subject, 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 in the EventEmitter documentation. Here is the example component that listens for open and close events:

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

元件的定義如下:

Here is the component definition:

EventEmitter
      
      @Component({
  selector: 'app-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);
    }
  }
}
    

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.

Using async pipe
      
      @Component({
  selector: 'async-observable-pipe',
  template: `<div><code>observable|async</code>:
       Time: {{ time | async }}</div>`
})
export class AsyncObservablePipeComponent {
  time = new Observable<string>(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:

Router events
      
      import { Router, NavigationStart } from '@angular/router';
import { filter } from 'rxjs/operators';

@Component({
  selector: 'app-routable',
  template: 'Routable1Component template'
})
export class Routable1Component implements OnInit {

  navStart: Observable<NavigationStart>;

  constructor(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(() => console.log('Navigation Started!'));
  }
}
    

ActivatedRoute 是一個可注入的路由器服務,它使用可觀察物件來獲取關於路由路徑和路由引數的資訊。比如,ActivatedRoute.url 包含一個用於彙報路由路徑的可觀察物件。例子如下:

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

ActivatedRoute
      
      import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-routable',
  template: 'Routable2Component template'
})
export class Routable2Component implements OnInit {
  constructor(private activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    this.activatedRoute.url
      .subscribe(url => console.log('The URL changed to: ' + url));
  }
}
    

響應式表單 (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:

Reactive forms
      
      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)
    );
  }
}