Posts
Materials
EN
Gray Wood
Posts
Materials
EN
Gray Wood

版本

Angular: 11.2.7

基础概念

模块(Module)

模块顾名思义,承担容器的作用,通过 @NgModule 装饰。一个模块的基本组成如下。

1
2
3
4
5
6
7
8
@NgModule({
  imports:      [ BrowserModule ],  // imports: 导入其他所需模块
  providers:    [ Logger ],         // providers: 导入服务(Service)
  declarations: [ AppComponent ],   // declarations:声明组件(Component)、指令(Directive)、管道(Pipe)
  exports:      [ AppComponent ],   // exports:导出能在其他模块的组件模板中使用的可声明对象
  bootstrap:    [ AppComponent ]    // bootstrap:主视图(只有根模块可以设置)
})
export class AppModule { }

一个 Angular 应用一定会有 根模块(通常命名为 AppModule),除此之外还可以引入其他模块,如常用模块 BrowserModule(使应用可在浏览器中运行),CommonModule(使 ngFor, ngIf 可被识别)等。

指令(Directive)

可以修改 DOM、组件数据模型中某些属性的类,由 @Directive 装饰。指令可分为三类:

  • 组件(由 @Component() 装饰)
  • 属性型指令(如 [...], (...)
  • 结构型指令(如 *ngIf, *ngFor

通过指令,可以控制 DOM,扩展模板语法等。

属性型指令

属性型指令看起来像是 HTML 属性,会监听与修改其他 HTML 元素和组件的行为,常见的内置属性型指令有 ngClass, ngStyle, ngModel 等。

创建新的属性型指令

通过 @Directive({ selector: [...] }) 修饰类,其中方括号 [] 是必须的,表示这是一个属性型指令选择器。在类中:

  • 引用 DOM 元素:通过 constructor(el: ElementRef)
  • 增加模板数据绑定项:使用 @Input()
  • 使自己可被数据绑定:使用 @Input(alias),alias 与 selector 方括号中的内容相同;
  • 处理事件:使用 @HostListener(eventName)

最后,该类需要在 module 的 declarations 中被声明。

官方给出了一个 hover 高亮 的例子。

结构型指令

结构型指令负责 HTML 布局,维护 DOM 结构,常用的结构型指令有 ngIf, ngFor, ngSwitch 等。

星号(*)前缀

星号前缀用于简化结构型指令中绑定的 微语法,是语法糖,如

1
<div *ngIf="hero" class="name">{{hero.name}}</div>

被简化为

1
2
3
<ng-template [ngIf]="hero">
  <div class="name">{{hero.name}}</div>
</ng-template>

微语法

Angular 微语法形式如:

1
*<prefix>="(<let> | <expression>) (';' | ',')? (<let> | <as> | <keyExp>)*"

其中,

1
2
3
<keyExp> => <key> ":"? <expression> ("as" <local>)? ";"?
<let> => "let" <local> "=" <export> ";"?
<as> = <export> "as" <local> ";"?
  • <prefix>, <key>:HTML 属性键
  • <local>:模板中使用的局部变量名
  • <export>:指令使用该名称作为导出名称
  • <expression>:Angular 标准表达式

创建新的结构型指令

通过 @Directive({ selector: [...] }) 修饰类,其中方括号 [] 是必须的,表示这是一个属性型指令选择器(上述与“创建新的属性型指令”相同)。在类中:

  • 构造函数:constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {}TemplateRef 可以访问 <ng-template> 中的内容,ViewContainerRef 是当前节点视图容器的引用);
  • 监听:使用 @Input() 装饰 setter
  • 显示子组件:使用 this.viewContainer.createEmbeddedView(this.templateRef)
  • 销毁子组件:使用 this.viewContainer.clear()

模板(Template)

  • 插值:{{ ... }}
  • 绑定:
    • 数据源到视图(属性):[...]="...", bind-...="..."
    • 视图到数据源(事件):(...)="...", on-...="..."
    • 双向:[(...)]="...", bindon-...="..."

模板引用变量

模板引用变量形如 #...,可以引用模板中的 DOM 元素、指令、元素、TemplateRef、Web 组件,作用域被 <ng-template> 分隔。

管道

管道为 pipe,在模板中使用,形式如 <exp> '|' 'pipe' (:<exp-as-arg>)*,模板中的值会经过管道转换后再输出。常用管道有 AsyncPipe, DatePipe, SlicePipe 等。

管道分为 纯的(pure) 与 非纯:

  • 纯管道:当值发生变化时(浅比较)才会重新执行
  • 非纯管道:当按键或鼠标移动时会重新执行

创建一个管道

@Pipe({ name: ..., pure: ... }) 修饰类,并实现 PipeTransform 接口,其中:

  • name:模板中管道的名称;
  • pure:指示管道是否是纯的,默认为 true

类中需要实现 transform 方法,参数即为管道的传参,返回运算结果。

组件(Component)

组件负责 暴露数据 与 处理交互逻辑,由 @Component(其继承 @Directive)装饰。组件与模板(Template)共同组成视图(View)。

数据传输

@Input():父组件传给子组件数据

  • 子组件类:通过 @Input() 修饰成员,成员可以是变量、getter/setter(此方法可通过 ngOnChanges 生命周期检测值变动);
  • 父组件模板:使用 数据绑定 传输数据;

@Output():父组件绑定子组件事件

  • 子组件类:通过 @Output() 修饰 EventEmitter 类示例,需要时通过该实例上的 emit 方法发出事件;
  • 父组件模板:使用 事件绑定 绑定事件(如 (childCompEvEmitterInstanceName)=handleEvent($event),其中 $event 是固定的);

#localVar:父组件模板访问子组件数据与方法

  • 父组件模板:在子组件标签上使用 #xxx 新建一个本地变量代表子组件,在模板中便使用形如 xxx.f1(), xxx.x1 访问子组件的数据与方法;

@ViewChild():父组件访问子组件数据

  • 父组件类:通过 @ViewChild(ChildComponentClass) 修饰子组件类,便可在类中访问子组件的数据(被注入的子组件只有在父组件视图显示后才能被访问,对应生命周期函数为 ngAfterViewInit);

服务:双向数据流

可通过父子组件共享一个服务来做到双向数据。

样式

Angular 会自动做 CSS 模块化,不需要担心类名污染的问题,同时支持 .scss, .less 等。

特殊的选择器

  • :host:用于选择宿主元素;
  • :host-context(className):用于选择视图外的条件,会沿根查找 className 直到根;

配置

  • @Component({ styleUrls }):配置 CSS 文件;
  • @Component({ style }):配置 CSS,只支持纯 CSS;
  • @Component({ template: '<style>...</style>' }):内联 CSS;
  • @Component({ template: '<link href="...">' }):配置 CSS 文件,只支持相对路径;
  • CSS @imports

动态组件

以动态广告为例:

  • 指令:新建指令 @Directive({ selector: '[adHost]' }) class AdHostDirective { constructor(public viewContainerRef: ViewContainerRef) {} }
  • 模板:插入 <ng-template adHost>
  • 类成员:@ViewChild(AdHostDirective, { static: true }) adHost: BannerDirective;,这使得类可以控制 template 的内容显示;
  • 构造函数:constructor(private componentFactoryResolver: ComponentFactoryResolver) { },其中 ComponentFactoryResolver 用于将组件类转为解析为工厂方法;
  • 动态组件实例化:导入目标组件类,通过 componentFactoryResolver.resolveComponentFactory(component) 解析为工厂方法 componentFactoryadHost.viewContainerRef.clear() 清空视图内容,adHost.viewContainerRef.createComponent<...>(componentFactory) 实例化组件并插入到视图中;

服务(Service)

封装了非 UI 逻辑的类,由 @Injectable 装饰,用于加强逻辑复用性。

其他

  • 伪指令 ngNonBindable 可以禁止子元素的插值与任何类型的绑定;

常用特性

表单

响应式表单与校验

ReactiveFormsModule, FormsModule 需要导入用于处理响应式表单逻辑。编写表单通常使用以下类:

  • FormControl 类用于创建表单控件,绑定在表单控件上(如 input);
  • FormGroup 类用于创建表单控件分组,通常绑定在 form 上;(如果使用 FormGroup,则其包含的表单控件不需要在模板上绑定);
  • FormBuilder 服务是 util 类,用于快速生成 FormGroup, FormControl, FormArray

对于表单校验,有如下选择:

  • 内置校验:通过 Validators 对象,如 Validators.require, Validators.maxlength(...) 等;
  • 自定义同步校验:通过实现 ValidatorFn 工厂(见 Demo 中 fbcv.component.ts 及对应的模板)
  • 自定义异步校验:通过实现 AsyncValidator,然后将 validate 成员函数设置在表单控件初始化处

另外,FormGroup 的 options 中可设置校验器以实现 交叉校验

动态响应式表单

动态表单实际上是由响应式表单灵活实现。

路由

一般情况下,路由机制需要考虑的问题及 Angular 的支持如下

  • 路由注册:通过 @angular/router 中的 RouterModule,编写路由列表时通过属性 component 指定组件(见 Demo 中的 app-routing.module.ts);
  • 路由路径:一般路径如 path/to;带参数路径如 path/:param;通配符 **,一般用于 404 页面(见 xxx-routing.module.ts);
  • 获取路由信息与更改路由:通过 @angular/router 中的 ActivatedRoute, Router 服务 DashBoard/Datalist);
  • 懒加载(Lazy Loading,文档中表述为 “惰性加载”):将原先的 component 封装为 module,原属性 component 换为 loadChildren 并改为 import() 动态导入模块(见 DashBoard);
  • 路由模式(History API 或 Hash):通过在 RouterModule.forRootLocationStrategy 指定为 PathLocationStrategy(History API)或 HashLocationStrategy
  • 路由守卫:通过实现 CanActivate, CanActivateChild 等接口的服务后,在路由列表的 canLoad, canActivate 等属性上注册;
  • 重定向:通过配置路由列表中的 redirectTo 属性(见 app-routing.module.ts);
  • 嵌套路由:通过配置 children 属性(见 DashBoard)

网络请求

一般情况下,网络请求需要考虑的问题及 Angular 的支持如下

  • 基础 AJAX 请求与内容解析:使用 HttpClientModule 模块,通过 http[method](url, options) 实现(其中 httpHttpClient 服务),其中 options 为如下 因此可以设置请求头,相应数据类型等。另外,这种方式返回 observable 对象,后续可做错误处理等。
  • JSONP 请求:使用 HttpClientJsonpModule 模块,通过 http.jsonp(...) 实现;
  • 错误处理:使用 rxjs/operators 中的 catchError
  • 请求重试:使用 rxjs/operators 中的 retry
  • 拦截器:使用实现 HttpInterceptor 接口的服务类实现;
  • 安全:使用 HttpClientXsrfModule 模块,配合后端完成 CSRF 攻击防护;