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

这里我将使用响应式编程和Angular中的自定义管道来实现。
响应式编程(Rxjs)
概念
响应式编程是面向数据流和变化传播的一种编程范型。它与其他编程范型混合后,产生了 面向对象的响应式编程(OORP),函数响应式编程(FRP)等一些”新”概念。
响应式编程是以观察者模式为核心的,这里简单介绍一下用法。之后应该会专门总结归纳出一篇文章来了解Rxjs
思维导图

如图所示。代码第一行的数组就是一个可观察对象,它也可以是一组事件。
在Rxjs里面,没有明确的对象,它是一个回调函数的集合,图中.subscribe()的内容就是一个观察者。
订阅一个可观察对象的本质可以理解为左侧图中注册那一步的动作,当然,也可以用.unsubscribe()来取消订阅。
代码部分中的.filter()、.map()就是操作符,它们的作用是处理可观察对象中的数据。
总结:响应式编程就是异步数据流编程。
在Angular项目中引入Rxjs
Angular的响应式编程依赖并集成于Rxjs,所以在开发时不需要额外安装。
还是以上图为例,第一行代码中Observable就是Rxjs中的对象。组件中,通过import {Observable} from 'rxjs';可以直接引入。
回到需求中来。当每次搜索框输入后,都会执行一个搜索的请求。这个请求在实际的项目中很可能是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 .subcribe(value => console.log(value))
|
注意,这个.debounceTime()方法来自rxjs,需要在组件顶部引入
自定义管道
在第一章时,介绍过几种常用管道的用法,这显然是不够的,我们还可以自定义管道的名称和规则。
生成管道
使用命令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
|
<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(); return itemFiledValue.indexOf(keyword) >= 0; }) }
}
|
解析
组件部分中定义了一个keyWords变量来存放输入框的值:每当输入框的值超过500ms没有改变时,它的值就会赋值给keyWords。
模板的核心部分是这一句
1
| <tr *ngFor="let item of userData | filter:'username':keyWords;let i=index">
|
让列表中的每个item作为管道的输入值,让字符串username和keyWords的值作为管道的参数。
在管道的规则中,处理了参数为空的情况,先提取出item的username值,再跟keyWords的值做对比,如果匹配程度大于等于零,就返回这个item。
其它思路
直接使用Rxjs
在官方的英雄编辑器教程中的搜索模块,并没有使用FormControl这个模块,而是全部使用的Rxjs模块,并附上了详细说明。这样也是可以实现的,但个人觉得不适合新手,我第一次跟着做的时候感觉一头雾水,新东西太多了。
双向绑定
我们还可以使用Angular表单提供的双向数据绑定,然后处理通过管道处理这个数据。示例:
1 2 3 4 5 6 7 8 9 10 11 12
| import { FormsModule } from '@angular/forms'; @NgModule({ ... imports: [ ... FormsModule ], ... }) export class AppModule { }
|
1
| <input [(ngModel)]="nameFilter">
|
其它
条条大路通罗马,如果有其它或者更好的方法,也欢迎点击博客下方的链接与我交流。