非常教程

AngularJS参考手册

可观察对象与RxJS

RxJS 库

响应式编程是一种面向数据流和变更传播的异步编程范式(Wikipedia)。RxJS(响应式扩展的 JavaScript 版)是一个使用可观察对象进行响应式编程的库,它让组合异步代码和基于回调的代码变得更简单 (RxJS Docs)。

RxJS 提供了一种对 Observable 类型的实现,直到 Observable 成为了 JavaScript 语言的一部分并且浏览器支持它之前,它都是必要的。这个库还提供了一些工具函数,用于创建和使用可观察对象。这些工具函数可用于:

  • 把现有的异步代码转换成可观察对象
  • 迭代流中的各个值
  • 把这些值映射成其它类型
  • 对流进行过滤
  • 组合多个流

创建可观察对象的函数

RxJS 提供了一些用来创建可观察对象的函数。这些函数可以简化根据某些东西创建可观察对象的过程,比如事件、定时器、承诺等等。比如:

Create an observable from a promise

content_copyimport { fromPromise } from 'rxjs';

// Create an Observable out of a promise
const data = fromPromise(fetch('/api/endpoint'));
// Subscribe to begin listening for async result
data.subscribe({
 next(response) { console.log(response); },
 error(err) { console.error('Error: ' + err); },
 complete() { console.log('Completed'); }
});

Create an observable from a counter

content_copyimport { interval } from 'rxjs';

// Create an Observable that will publish a value on an interval
const secondsCounter = interval(1000);
// Subscribe to begin publishing values
secondsCounter.subscribe(n =>
  console.log(`It's been ${n} seconds since subscribing!`));

Create an observable from an event

content_copyimport { fromEvent } from 'rxjs'; const el = document.getElementById('my-element'); // Create an Observable that will publish mouse movementsconst mouseMoves = fromEvent(el, 'mousemove'); // Subscribe to start listening for mouse-move eventsconst subscription = mouseMoves.subscribe((evt: MouseEvent) => {  // Log coords of mouse movements  console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);   // When the mouse is over the upper-left of the screen,  // unsubscribe to stop listening for mouse movements  if (evt.clientX < 40 && evt.clientY < 40) {    subscription.unsubscribe();  }});

Create an observable that creates an AJAX request

content_copyimport { ajax } from 'rxjs/ajax';

// Create an Observable that will create an AJAX request
const apiData = ajax('/api/data');
// Subscribe to create the request
apiData.subscribe(res => console.log(res.status, res.response));

操作符

操作符是基于可观察对象构建的一些对集合进行复杂操作的函数。RxJS 定义了一些操作符,比如 map()filter()concat()flatMap()

操作符接受一些配置项,然后返回一个以来源可观察对象为参数的函数。当执行这个返回的函数时,这个操作符会观察来源可观察对象中发出的值,转换它们,并返回由转换后的值组成的新的可观察对象。下面是一个简单的例子:

Map operator

content_copyimport { map } from 'rxjs/operators'; const nums = of(1, 2, 3); const squareValues = map((val: number) => val * val);const squaredNums = squareValues(nums); squaredNums.subscribe(x => console.log(x)); // Logs// 1// 4// 9

你可以使用管道来把这些操作符链接起来。管道让你可以把多个由操作符返回的函数组合成一个。pipe() 函数以你要组合的这些函数作为参数,并且返回一个新的函数,当执行这个新函数时,就会顺序执行那些被组合进去的函数。

应用于某个可观察对象上的一组操作符就像一个菜谱 —— 也就是说,对你感兴趣的这些值进行处理的一组操作步骤。这个菜谱本身不会做任何事。你需要调用 subscribe() 来通过这个菜谱生成一个结果。

例子如下:

Standalone pipe function

content_copyimport { filter, map } from 'rxjs/operators'; const nums = of(1, 2, 3, 4, 5); // Create a function that accepts an Observable.const squareOddVals = pipe(  filter((n: number) => n % 2 !== 0),  map(n => n * n)); // Create an Observable that will run the filter and map functionsconst squareOdd = squareOddVals(nums); // Suscribe to run the combined functionssquareOdd.subscribe(x => console.log(x));

pipe() 函数也同时是 RxJS 的 Observable 上的一个方法,所以你可以用下列简写形式来达到同样的效果:

Observable.pipe function

content_copyimport { filter, map } from 'rxjs/operators';

const squareOdd = of(1, 2, 3, 4, 5)
  .pipe(
    filter(n => n % 2 !== 0),
    map(n => n * n)
  );

// Subscribe to get values
squareOdd.subscribe(x => console.log(x));

常用操作符

RxJS 提供了很多操作符,不过只有少数是常用的。 下面是一个常用操作符的列表和用法范例,参见 RxJS API 文档。

Note that, for Angular apps, we prefer combining operators with pipes, rather than chaining. Chaining is used in many RxJS examples.

注意,对于 Angular 应用来说,我们提倡使用管道来组合操作符,而不是使用链式写法。链式写法仍然在很多 RxJS 中使用着。

类别

操作

创建

from , fromPromise , fromEvent , of

组合

combineLatest , concat , merge , startWith ,withLatestFrom , zip

过滤

debounceTime , distinctUntilChanged , filter , take ,takeUntil

转换

bufferTime , concatMap , map , mergeMap , scan ,switchMap

工具

tap

多播

share

错误处理

除了可以在订阅时提供 error() 处理器外,RxJS 还提供了 catchError操作符,它允许你在管道中处理已知错误。

假设你有一个可观察对象,它发起 API 请求,然后对服务器返回的响应进行映射。如果服务器返回了错误或值不存在,就会生成一个错误。如果你捕获这个错误并提供了一个默认值,流就会继续处理这些值,而不会报错。

下面是使用 catchError 操作符实现这种效果的例子:

catchError operator

content_copyimport { ajax } from 'rxjs/ajax';import { map, catchError } from 'rxjs/operators';// Return "response" from the API. If an error happens,// return an empty array.const apiData = ajax('/api/data').pipe(  map(res => {    if (!res.response) {      throw new Error('Value expected!');    }    return res.response;  }),  catchError(err => of([]))); apiData.subscribe({  next(x) { console.log('data: ', x); },  error(err) { console.log('errors already caught... will not run'); }});

重试失败的可观察对象

catchError 提供了一种简单的方式进行恢复,而 retry 操作符让你可以尝试失败的请求。

可以在 catchError 之前使用 retry 操作符。它会订阅到原始的来源可观察对象,它可以重新运行导致结果出错的动作序列。如果其中包含 HTTP 请求,它就会重新发起那个 HTTP 请求。

下列代码为前面的例子加上了捕获错误前重发请求的逻辑:

retry operator

content_copyimport { ajax } from 'rxjs/ajax';import { map, retry, catchError } from 'rxjs/operators'; const apiData = ajax('/api/data').pipe(  retry(3), // Retry up to 3 times before failing  map(res => {    if (!res.response) {      throw new Error('Value expected!');    }    return res.response;  }),  catchError(err => of([]))); apiData.subscribe({  next(x) { console.log('data: ', x); },  error(err) { console.log('errors already caught... will not run'); }});

不要重试登录认证请求,这些请求只应该由用户操作触发。我们肯定不会希望自动重复发送登录请求导致用户的账号被锁定。

可观察对象的命名约定

由于 Angular 的应用几乎都是用 TypeScript 写的,你通常会希望知道某个变量是否可观察对象。虽然 Angular 框架并没有针对可观察对象的强制性命名约定,不过你经常会看到可观察对象的名字以“$”符号结尾。

这在快速浏览代码并查找可观察对象值时会非常有用。同样的,如果你希望用某个属性来存储来自可观察对象的最近一个值,它的命名惯例是与可观察对象同名,但不带“$”后缀。

比如:

Naming observables

content_copyimport { Component } from '@angular/core';import { Observable } from 'rxjs'; @Component({  selector: 'app-stopwatch',  templateUrl: './stopwatch.component.html'})export class StopwatchComponent {   stopwatchValue: number;  stopwatchValue$: Observable<number>;   start() {    this.stopwatchValue$.subscribe(num =>      this.stopwatchValue = num    );  }}
AngularJS

Angular 是一个开发平台。它能帮你更轻松的构建 Web 应用。Angular 集声明式模板、依赖注入、端到端工具和一些最佳实践于一身,为你解决开发方面的各种挑战。

AngularJS目录

1.快速上手 | quick start
2.语言服务
3.安全
4.环境准备与部署
5.Service Worker
6.保持最新
7.从 AngularJS 升级
8.服务端渲染
9.Visual Studio 2015 快速上手
10.风格指南
11.国际化
12.测试
13.路由与导航
14. 教程 | Tutorial
15.架构
16.组件与模板
17.表单
18.可观察对象与RxJS
19.引导启动
20.Angular 模块
21.依赖注入
22.HttpClient
23.词汇表
24.AngularJS 应用
25.AngularJS 模块
26.AngularJS 事件
27.AngularJS HTML DOM
28.AngularJS 过滤器
29.AngularJS 控制器
30.AngularJS 指令
31.AngularJS 表达式
32.AngularJS 简介
33.AngularJS 参考手册
34.AngularJS 实例
35.AngularJS 输入验证
36.AngularJS 表单
37.AngularJS SQL
38.AngularJS 表格
39.AngularJS Http
40.AngularJS 包含
41.AngularJS Bootstrap
42.AngularJS API
43.AngularJS ng-checked 指令
44.AngularJS ng-change 指令
45.AngularJS ng-blur 指令
46.AngularJS ng-bind-template 指令
47.AngularJS ng-bind-html 指令
48.AngularJS ng-bind 指令
49.AngularJS ng-app 指令
50.AngularJS Scope(作用域)
51.AngularJS ng-model 指令
52.AngularJS ng-dblclick 指令
53.AngularJS ng-cut 指令
54.AngularJS ng-csp 指令
55.AngularJS ng-copy 指令
56.AngularJS ng-controller 指令
57.AngularJS ng-cloak 指令
58.AngularJS ng-click 指令
59.AngularJS ng-class-odd 指令
60.AngularJS ng-class-even 指令
61.AngularJS ng-class 指令
62.AngularJS ng-keyup 指令
63.AngularJS ng-keypress 指令
64.AngularJS ng-keydown 指令
65.AngularJS ng-init 指令
66.AngularJS ng-include 指令
67.AngularJS ng-if 指令
68.AngularJS ng-href 指令
69.AngularJS ng-hide 指令
70.AngularJS ng-focus 指令
71.AngularJS ng-disabled 指令
72.AngularJS ng-non-bindable 指令
73.AngularJS ng-mouseup 指令
74.AngularJS ng-mouseover 指令
75.AngularJS ng-mousemove 指令
76.AngularJS ng-mouseleave 指令
77.AngularJS ng-mouseenter 指令
78.AngularJS ng-mousedown 指令
79.AngularJS ng-model-options 指令
80.AngularJS ng-model 指令
81.AngularJS ng-list 指令
82.AngularJS ng-style 指令
83.AngularJS ng-srcset 指令
84.AngularJS ng-src 指令
85.AngularJS ng-show 指令
86.AngularJS ng-selected 指令
87.AngularJS ng-repeat 指令
88.AngularJS ng-readonly 指令
89.AngularJS ng-paste 指令
90.AngularJS ng-options 指令
91.AngularJS ng-open 指令
92.AngularJS ng-value 指令
93.AngularJS ng-switch 指令
94.AngularJS ng-submit 指令
95.AngularJS 服务(Service)
96.AngularJS Select(选择框)
97.AngularJS 动画
98.AngularJS 依赖注入