文章目录
  1. 1. 需求演示
  2. 2. 响应式编程(Rxjs)
    1. 2.1. 概念
    2. 2.2. 思维导图
    3. 2.3. 在Angular项目中引入Rxjs
    4. 2.4. FormControl
    5. 2.5. 代码示例
  3. 3. 自定义管道
    1. 3.1. 生成管道
    2. 3.2. 定义规则
    3. 3.3. 使用管道
  4. 4. 综合运用(代码实例)
    1. 4.1. 全部代码
    2. 4.2. 解析
  5. 5. 其它思路
    1. 5.1. 直接使用Rxjs
    2. 5.2. 双向绑定
    3. 5.3. 其它

需求演示

Angular常用模板语法一文中,我实现了一个简单的angular表单(angular表单演示),现在,我想给它添加一个搜索的功能,效果是这样的:

使用搜索功能时,只有框架名称符合关键字的情况下,此列表项才会显示

这里我将使用响应式编程和Angular中的自定义管道来实现。

响应式编程(Rxjs)

概念

响应式编程是面向数据流和变化传播的一种编程范型。它与其他编程范型混合后,产生了 面向对象的响应式编程(OORP),函数响应式编程(FRP)等一些”新”概念。

响应式编程是以观察者模式为核心的,这里简单介绍一下用法。之后应该会专门总结归纳出一篇文章来了解Rxjs

思维导图

如图所示。代码第一行的数组就是一个可观察对象,它也可以是一组事件。

在Rxjs里面,没有明确的对象,它是一个回调函数的集合,图中.subscribe()的内容就是一个观察者。

订阅一个可观察对象的本质可以理解为左侧图中注册那一步的动作,当然,也可以用.unsubscribe()来取消订阅。

代码部分中的.filter().map()就是操作符,它们的作用是处理可观察对象中的数据。

总结:响应式编程就是异步数据流编程。

在Angular项目中引入Rxjs

Angular的响应式编程依赖并集成于Rxjs,所以在开发时不需要额外安装。

还是以上图为例,第一行代码中Observable就是Rxjs中的对象。组件中,通过import {Observable} from 'rxjs';可以直接引入。

FormControl

回到需求中来。当每次搜索框输入后,都会执行一个搜索的请求。这个请求在实际的项目中很可能是http请求,如果不作任何处理的话将会无端消耗大量的服务器资源。所以一般的搜索功能在开发时,都会设置一个时间间隔,当每隔多少毫秒不再输入时,才回去执行搜索的请求。

如果用普通的js代码来写,代码大概是这样的:

  • 首先需要声明一个变量,来存放每次keyup事件之间的间隔
  • 然后在keyup事件的回调方法里判断这个值
  • 还需要用setTimeOut设置一个超时时间
  • 根据某些条件来重置这个超时事件
  • 不停地递归调用

ReactiveFormsModule是Angular里类似于Rxjs的响应式编程模块,在这个模块里,提供了一个对象FormControl,这个对象是Angular进行表单处理时常用的一个类。

每个Angular表单都有自己的一个FormControl对象,每当表单中的值改变时,它就会发射一个valueChange事件,从而组成一个可订阅的流(可观察对象)。请看下面的代码部分。

代码示例

首先需要在app.module.ts中引入ReactiveFormsModule

1
2
3
4
5
6
7
8
9
10
11
import { ReactiveFormsModule } from "@angular/forms";
@NgModule({
declarations: [
...
],
imports: [
...
ReactiveFormsModule
],
...
})

组件模板中的formControl是一个指令,我们利用这个指令把这个标签绑定到了后台的searchInput变量中,然后在在组件中声明这个变量,它的type属性为FormControl。

1
2
3
4
5
6
7
8
9
10
11
import { FormControl } from "@angular/forms";
@Component({
...
template: `<input [formControl]="searchInput" type="text">`,
...
})
export class AppComponent{
...
private searchInput:FormControl = new FormControl();
...
}

每当input的值改变时,后台的searchInput都会发射一个valueChange事件,我们需要订阅这个事件:

1
2
3
4
5
6
7
8
9
export class AppComponent{
...
private searchInput:FormControl = new FormControl();
...
constructor() {
this.searchInput.valueChanges
.subcribe(value => console.log(value))
}
}

这样就完成了一个最基础的响应式编程。我们还需要设置一个超时时间,避免请求过于频繁的问题。在中间插入一行代码就行了:

1
2
3
this.searchInput.valueChanges
// .debounceTime(500)
.subcribe(value => console.log(value))

注意,这个.debounceTime()方法来自rxjs,需要在组件顶部引入

1
import "rxjs/Rx";

自定义管道

第一章时,介绍过几种常用管道的用法,这显然是不够的,我们还可以自定义管道的名称和规则。

生成管道

使用命令ng g p filter可以自动生成一个名为FilterPipe的管道模块,并自动在app.module.ts引入。这个模块初始内容是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'filter' // 管道的名字,在模板中使用,可自定义
})
export class FilterPipe implements PipeTransform {

transform(value: any, args?: any): any {
return null
}

}

这个管道模块实现了一个PipeTransform接口,接口里只有一个transform方法,用来定义管道的规则。

transform方法接收一个输入值value,后边的args是可选参数。

定义规则

我们将实现一个简单的规则:使管道输出的数值等于输入数值的n倍,这个n是可选参数,默认为1倍。

1
2
3
4
transform(value: number, args?: number): any {
if(!args) args = 1;
return value * args;
}

使用管道

在组件或者模板中使用这个管道

1
2
3
4
5
6
7
8
9
10
11
...
@Component({
...
template: `<p>试一试这个管道:{{size | filter:'3'}}</p>`,
...
})
export class AppComponent{
...
private size:number = 8;
...
}

输出的值是
试一试这个管道:24

综合运用(代码实例)

全部代码

学会了上面的知识,就可以进行实际的开发了。以下是代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from "@angular/forms"; // 手动引入

import { AppComponent } from './app.component';
import { FilterPipe } from './filter.pipe';

@NgModule({
declarations: [
AppComponent,
FilterPipe
],
imports: [
BrowserModule,
ReactiveFormsModule // 手动添加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

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
// 组件部分
import { Component, OnInit } from '@angular/core';
import { FormControl } from "@angular/forms";
import "rxjs/Rx";

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent{
private nameFilter:FormControl = new FormControl();
private keyWords:string;
public userData:any = [
{username: "Angular",age: "4.X"},
{username: "BootstrapCss",age: "3.3.7"},
{username: "ngx-bootstrap-modal",age: "1.0"},
{username: "Jquery",age: "none"}
]
ngOnInit() {
this.nameFilter.valueChanges
.debounceTime(500)
.subscribe(value => this.keyWords = value);
}
}

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
34
35
36
<!-- 组件的模板 -->
<!-- 这里直接截取了第一章angular-form的模板 -->
<div class="box-tools pull-right" style="margin-top:7px;margin-right:10px">
<div class="has-feedback">
<input [formControl]="nameFilter" type="text" class="form-control input-sm" placeholder="搜索">
<span class="glyphicon glyphicon-search form-control-feedback"></span>
</div>
</div>
<table class="table table-bordered table-hover">
<caption class="h3 text-info text-center">用户信息</caption>
<tr class="text-danger">
<th class="text-center">序号</th>
<th class="text-center">框架</th>
<th class="text-center">版本</th>
<th class="text-center">操作</th>
</tr>
<tr class="text-center" *ngFor="let item of userData | filter:'username':keyWords;let i=index">
<td>{{i+1}}</td>
<td>{{item.username}}</td>
<td>{{item.age}}</td>
<td>
<button class="btn btn-danger btn-sm"
(click)="nowIndex = i" (click)="showConfirm()">删除</button>
</td>
</tr>
<tr v-show="myData.length != 0">
<td colspan="4" class="text-right">
<button class="btn btn-danger btn-sm"
(click)="nowIndex = -1" (click)="showConfirm()">删除全部</button>
</td>
</tr>
<tr class="text-center text-muted" *ngIf="!userData.length">
<td colspan="4">没有用户信息</td>
</tr>
</table>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {

transform(list: any[], filed: string, keyword: string): any {
if (!filed || !keyword) {
return list;
}
return list.filter(item => {
// 选出列表项中所须属性的值
let itemFiledValue = item[filed].toLowerCase();
// 通过查询,只返回符合keyword的值的列表项
return itemFiledValue.indexOf(keyword) >= 0;
})
}

}

解析

组件部分中定义了一个keyWords变量来存放输入框的值:每当输入框的值超过500ms没有改变时,它的值就会赋值给keyWords。

模板的核心部分是这一句

1
<tr *ngFor="let item of userData | filter:'username':keyWords;let i=index">

让列表中的每个item作为管道的输入值,让字符串usernamekeyWords的值作为管道的参数。

在管道的规则中,处理了参数为空的情况,先提取出item的username值,再跟keyWords的值做对比,如果匹配程度大于等于零,就返回这个item。

其它思路

直接使用Rxjs

在官方的英雄编辑器教程中的搜索模块,并没有使用FormControl这个模块,而是全部使用的Rxjs模块,并附上了详细说明。这样也是可以实现的,但个人觉得不适合新手,我第一次跟着做的时候感觉一头雾水,新东西太多了。

双向绑定

我们还可以使用Angular表单提供的双向数据绑定,然后处理通过管道处理这个数据。示例:

1
2
3
4
5
6
7
8
9
10
11
12
// app.module.ts
import { FormsModule } from '@angular/forms';
@NgModule({
...
imports: [
...
FormsModule
],
...
})
export class AppModule { }

1
<input  [(ngModel)]="nameFilter">

其它

条条大路通罗马,如果有其它或者更好的方法,也欢迎点击博客下方的链接与我交流。

文章目录
  1. 1. 需求演示
  2. 2. 响应式编程(Rxjs)
    1. 2.1. 概念
    2. 2.2. 思维导图
    3. 2.3. 在Angular项目中引入Rxjs
    4. 2.4. FormControl
    5. 2.5. 代码示例
  3. 3. 自定义管道
    1. 3.1. 生成管道
    2. 3.2. 定义规则
    3. 3.3. 使用管道
  4. 4. 综合运用(代码实例)
    1. 4.1. 全部代码
    2. 4.2. 解析
  5. 5. 其它思路
    1. 5.1. 直接使用Rxjs
    2. 5.2. 双向绑定
    3. 5.3. 其它