Angular Material实现typeahead简单示例

1. 前言

项目中需要一个输入提示框, 之前是使用ng-bootstrap实现的. 但是由于系统框架完全迁移到了material design. 所以所有bootstrap的依赖项都要迁移到angular material design.

如果您的web表单有一个包含许多选项的下拉列表,那么您可能希望通过提前键入来简化用户的生活。幸运的是,Angular Material组件可以让你很容易地做到这一点。在本指南中,我将向您展示如何实现这一点。

顺便说一句:如果你不熟悉typeahead(提前输入),这是一种在下拉列表中过滤结果的方法。用户刚开始在字段中键入字符,列表将缩小为仅以用户键入的字符开头的选项。
typeahead是一种用户友好的输入方式,因为用户不必在看似无穷无尽的列表中滚动才能找到他们想要的内容。

2. 创建Angular项目

1
2
3
4
# 创建项目, 跳过依赖安装 typeahead 为项目名称
ng new typeahead-app --skip-install
# 或者
ng new typeahead-app

3. 添加依赖

3.1. 添加 Angular Material 和 Angular CDK 模块

说明: cdk是 angular官方的一个 Component Dev Kit.顾名思义。他就是帮助你开发组件的。包含以下内容

1
2
3
4
5
6
cd typeahead-app
npm install --save @angular/material@~13.3.0 @angular/cdk@~13.3.0 @angular/core@~13.3.0 @angular/common@~13.3.0

## 或者
cd typeahead-app
ng add @angular/material@~13.3.0

4. 安装依赖包

1
2
3
4
5
6

npm install

##或者 如果你处在墙内, 建议使用下面这种方式安装依赖包以获得较快的下载速度
cnpm install

5. 添加预构建的Angular Material主题

当我们安装Angular Material时,一些预构建的主题会自动安装。可用的预建主题是

  1. indigo-pink
  2. deeppurple-amber
  3. purple-green
  4. pink-bluegrey

将主题添加到全局style.css

1
2
3

@import '~@angular/material/prebuilt-themes/indigo-pink.css';

6. 导入模块

首先,我们需要导入MatFormFieldModule和MatSelectModule。因此,让我们更新app.module.ts。
让我们遵循以下步骤:

src/app/app.module.ts

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 { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
MatSelectModule,
MatAutocompleteModule,
MatFormFieldModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

7. 创建typeahead input

由于我个人偏好, 我喜欢由简单到深入的讲解一个功能, 这里将整个功能的实现过程分为以下几个步骤.
第一步: 实现一个带下拉列表并具有过滤功能的输入框, 但是列表项的内容为预先hardcode在代码中的.
第二步: 将hardcode的列表项, 替换为远程服务器抓取的内容
第三步: 优化输入框, 给列表项添加更多样式.

7.1. 第一步: 实现一个带下拉列表并具有过滤功能的输入框

src/app/app.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<form class="example-form">
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Number</mat-label>
<input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>

src\app\app.component.ts

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

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
myControl = new FormControl();
options: string[] = ['One', 'Two', 'Three'];
title = 'typeahead-app';

filteredOptions: Observable<string[]>;

ngOnInit() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value)),
);
}

private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.options.filter(option => {
return option.toLowerCase().includes(filterValue)
});
}
}

说明: 这里主要涉及到两个主要的控件, 一个是input(matInput)输入框和一个autocomplete panel. input输入框通过matAutocomplete属性值”auto”关联到一个id为auto的autocomplete panel. 而input通过 formControl属性定义的formcontrol控件用于感知数据的变化. 感知到输入变化后通过filter方法来实现数据过滤, 而最改变滤autocomplete panel中的选项.

matInput是一个指令,允许浏览器原生的<input>元素与<mat-form-field>配合在一起工作

<mat-form-field>是一个组件,用于包装多个Material组件,并应用常用文本字段样式,如下划线、浮动标签和提示消息。

formControl 是来自Reactive Form的一个一个属性, Reactive Form提供了一种模型驱动的方法来处理值随时间变化的表单输入。

这里的 #auto=”matAutocomplete” 可以简写为一个#auto 即定义一个变量名为auto 的local variable, 而省略其类型matAutocomplete, 关于template variable的详细解释可以参考这篇文章

7.2. 第二步: 列表项远程获取

7.2.1. 首先快速搭建一个web api服务, 来模拟提供远程数据

  • 新建api项目 执行npm init 初始化package.json

    1
    2
    3
    mkdir webapi
    cd webapi
    npm init -y
  • 添加依赖包

    1
    npm install express cors request --save
  • 新建index.js 添加配置

    index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
      /* 引入express框架 */
    const express = require('express');
    const app = express();

    /* 引入cors */
    const cors = require('cors');
    app.use(cors());

    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));

    /* 监听端口 */
    app.listen(8585, () => {
    console.log('——————————服务已启动——————————');
    })

    app.get('/', (req, res) => {
    res.send('<p style="color:red">服务已启动</p>');
    })

    app.get('/api/getOptions', (req, res) => {
    res.json([{name: "test1", value:"value"},{name: "test2", value:"value2"}])
    })
    • 启动api server
    1
    2
    $node index.js
    ——————————服务已启动——————————
    1
    [{"name":"test1","value":"value"},{"name":"test2","value":"value2"}]
    • 至此, 一个简单的远程Api server已经完成

7.2.2. 修改app.components.ts从远程加载列表选项

修改app/app.components.ts 引入依赖httpclient, 并在构造器中注入httpclient实例.

1
2
3
4
5
6
import { HttpClient } from '@angular/common/http';

constructor(private http: HttpClient) {

}

在页面创建时加载远程获取的options.

1
2
3
4
this.http.get<Option[]>("http://localhost:8585/api/getOptions").subscribe(
data => this.options = data
);

这里远程返回的是一个option对象, 所以要定义一个option类进行接收. 并修改options和filteredOptions类型为.

所以在html template上也要做相应修改.{{option.name}}

1
2
3
4

options: Option[]=[];
filteredOptions: Observable<Option[]>;

app/option.model.ts

1
2
3
4
5

export class Option {
name: string;
value: String;
}

完整代码如下
src/app/app.component.ts

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
37
38
39
40
41
42
43
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith, } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { Option } from './option.model'

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {

myControl = new FormControl("");
options: Option[]=[];
title = 'typeahead-app';

filteredOptions: Observable<Option[]>;


constructor(private http: HttpClient) {

}

ngOnInit() {
this.http.get<Option[]>("http://localhost:8585/api/getOptions").subscribe(
data => this.options = data
);
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value)),
);
}

private _filter(value: string): Option[] {
const filterValue = value.toLowerCase();
return this.options.filter(option => {
return option.name.toLowerCase().includes(filterValue)
});
}
}

至此, 下拉选项即为远程获取的内容

7.3. 第三步: 优化输入框, 给列表项添加更多样式

7.3.1. 高亮第一个下拉选项

给 mat-autocomplete 元素添加autoActiveFirstOption 属性

7.3.2. 给选项添加图标

接下来, 我想在每个选项前显示一个图标. 当然这在本项目上没有什么实际意义, 主要是为了获得一种增强视觉效果的能力.

从后端到前台我们需要做如下修改.

  • 服务器端, 我们需要修改api接口, 让其返回的option选项中包含图标名称.
  • 前端我们修养引入引入material icon相关配置
  • 在html template上进行一些优化, 让其显示相应图标
7.3.2.1. 首先修改修改api接口, 让其返回的图标名称

回到我们的webapi项目, 修改index.js.
添加iconName

1
2
3
app.get('/api/getOptions', (req, res) => {
res.json([{name: "test1", value:"value", iconName:"home"},{name: "test2", value:"value2", iconName:"person"}])
})

重启服务让其生效

1
node index.js
7.3.2.2. 引入material icon相关模块, 并添加Material Icons样式

首先在根模块app/app.module.ts中引入MatIconModule模块并添加到imports区域.

引入material icon 样式
修改app/index.html, 添加样式单

1
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

7.3.3. 修改html template, 让其显示图标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form class="example-form">
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Number</mat-label>
<input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete autoActiveFirstOption #auto>
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.name">
<mat-icon>{{option.iconName}}</mat-icon>
<span>{{option.name}}</span>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>

8. 启动项目

进入项目主目录, 启动项目观察效果. 当然你要可以在每个阶段性任务完成后, 启动项目查看效果.

1
ng serve --open --port 4203

9. Material Design 系列文章

最新更新以及更多Material Design相关文章请访问 鹏叔的技术博客-Material Design

10. 参考文档

Angular Material: How to Add a Type-Ahead Dropdown on a Web Form

Angular Material Select Dropdown with Search Example

How TO - Autocomplete

快速搭建web服务器提供api服务