2024-02-25
Angular
00
请注意,本文编写于 107 天前,最后修改于 100 天前,其中某些信息可能已经过时。

目录

Angular概念简介
NgModule
Angular CLI
Angular 组件
定义组件
组件的生命周期
模版语法
组件交互
管道
内容投影
Service
依赖注入
AJAX
指令
路由
生态 与 工具
生态
工具
ngrx状态管理
脏值检测
脏值检测原理
脏值检测注意事项
脏值检测性能瓶颈
ngZone
Angular17
Rxjs
Observable
Observer
Operator 操作符
Subject
Scheduler
深度融合Angular

Angular1.0angular.js诞生于2010年底,在2014-2016这几年大火,由于其双向数据绑定带来的开发便利,迅速淘汰了当初因MVC思想红极一时的BackboneJS。但是它的脏检查机制以及重量级框架带来的性能问题广为诟病。然后前端有开始流行以ReactJSVueJS为主的轻量级框架。面对市场份额不断被蚕食,Angular团队于2015年推出了2.0版本,即现在的Angular。由于改动很大,引入TS和模块化等,框架由重量级调整为轻量级,API变动也很大,可以看作是两个框架。尽管有着昔日的辉煌,但流行度却再也赶不上ReactJSVueJS了,,,然而它仍在前端江湖排行榜前三,所以仍旧不可忽视。

Angular虽然在国内用的比较少,但在国外却很流行,很多外企都是使用Angular,结合当下大环境,国内萧条,国外繁荣、外企招人,所以是时候学习Angular了,这个不能拖了。(注意文本中的内容不包含Angular1.0

不过我还是推荐你先去官方学习,先把照官方教程的三个案例、理解Angular篇 和 开发指南篇都学完,再来看我这篇笔记。

Angular概念简介

Angular是一个基于TypeScript构建的开发平台。它包括

  1. 一个基于组件的框架,用于构建可伸缩的Web应用
  2. 一组完美集成的库,蕴含各种功能,包括路由、表单管理、客户端-服务器通信等
  3. 一套开发工具,可以帮助你开发、构建、测试和更新代码

总之Angular可以理解为“保姆级框架”,只是通过模块化的重构可以插拔式集成使用了。

  • Angular的基本构造块时NgModule它为组件提供了编译的上下文环境
  • NgModule会把相关代码收集到一些功能集中。Angular应用就是由一组NgModule定义出的,应用至少会有一个用于引导应用的根模块,通常还会有很多特性模块

服务提供者作为依赖被注入到组件中,这能使你的代码更加模块化、更加可复用、更加高效

模块、组件和服务都是使用装饰器的类,这些装饰器会标出它们的类型并提供元数据,以告知 Angular 该如何使用它们。

NgModule

NgModule 与 ES2015的模块不同而且有一定的互补性。NgModule作为一个组件集声明了编译的上下文环境,它专注于某个应用领域、某个工作流或一组紧密相关的能力。NgModule可以将其组件和一组相关代码(如服务)关联起来,形成功能单元。

ts
@ngModel({ decarations: [组件、指令或管道,], imports: [模块,], providers: [service,], bootstrap: [启动的组件,] }) export class YouModule {}
  • decarations 模块拥有的组件、指令或管道。注意每一个只能在一个模块中声明
  • providers 模块中需要使用的服务
  • imports 导入本模块所依赖的模块
  • exports 暴露给其他模块使用的组件、指令或管道

Angular 需要知道如何把应用程序的各个部分组合到一起,以及该应用需要哪些其它文件和库。这些信息被称为元数据(metadata)。 (注解中的内容)

模块的坑

  1. 导入其它模块时,需要知道使用该模块的目的
    • 如果是组件,那么需要在每一个需要的模块都进行导入
    • 如果是服务,那么一般来说在根模块中导入一次即可
  2. 需要在每个需要的快快中进行导入的
    • CommonModule 提供绑定、*ngIF *ngFor等基础指令,
    • FormsModule / ReactiveFormsModule 表单模块需要在每个需要的模块导入
    • 提供组件指令或管道的模块
  3. 只在跟模块导入一次的
    • HttpClientModule / BrowserAnimationsModule / NoopAnimationsModule
    • 只提供服务的模块

image.png

Angular CLI

Angular CLI是开发Angular应用程序的最快、直接和推荐的方式。Angular CLI能简化许多任务。

创建一个项目

  • 全局安装CLI npm install -g @angular/cli
  • 创建项目 ng new my-app
  • 启动服务 cd my-app && ng serve --open

ng new 做了哪些事情?

  • 提示你选择项目的特性
  • 安装npm依赖

ng serve 做了哪些事情?

  • 构建本应用

  • 启动开发服务器

  • 监听源文件,发生变化时构建本应用

  • ng build 把Angular应用编译到一个输出目录中

  • ng server 构建你的应用并启动开发服务器,当有文件变化时就重新构建

  • ng generate 基于schematic 生成或修改某些文件

    • ng generate component compName
    • ng generate service serName
    • ng generate person
    • ng generate module app-routing --flat --module=app
    • 后面可以接一些参数 --skip-tests --standalone --inline-template
    • 简写 ng g c compName
  • ng test 在指定的项目上运行单元测试。

  • ng e2e 构建一个Angular应用并启动开发服务器,然后运行端到端的测试。

image.png

Angular 组件

定义组件

@Component() 中的主要功能如下:

  • selector(app-product-alerts)用于标识组件。按照惯例,Angular 组件选择器以前缀 app- 开头,后跟组件名称。
  • 模板和样式文件名引用了组件的 HTMLCSS
ts
import {component} from '@angular/core'; @component({ selector: 'app-xx', // 用于标识组件 以前缀 `app-` 开头 templateUrl: './xx.html', stylesUrls: ['./xx.css'], standalone: true, // 描述组件是否需要 ngModule。 template: '<h1>Hello World!</h1>', styles: ['h1 { font-weight: normal; }'] encapsulation: ViewEncapsulation.Emulated }) export class Xx {}

image.png

组件的生命周期

当 Angular 实例化组件类并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需要更新视图和组件实例。当Angular销毁组件实例并从DOM中移除它渲染的模版时,生命周期就结束了。
当Angular创建、更新、销毁实例时,指令就有了类似的生命周期.
你的应用可以使用生命周期钩子方法来触发组件或指令生命周期中的关键事件,以初始化新势力需要时启动变更检测,在变更检测过程中响应更新并在删除势力之前进行清理。

生命周期的顺序

  • constructor 构造函数永远首先被调用
  • ngOnChanges()
    • props更改时 触发, 所以不一定触发
  • ngOnInit() 组件初始化时被调用
    • 推荐在这里获取数据而不是constructor
  • ngDoCheck() 脏值检测时被调用
  • ngAfterContentInit() 当内容投影(插槽)完成时调用(在子组件中)
  • ngAfterContentChecked() 检测投影内容时调用(多次)(在子组件中)
  • ngAfterViewInit() 当组件视图(子视图)初始化完成时
  • ngAfterViewChecked() 当检测时图变化时(多次)
  • ngOnDestroy() 当组件销毁时
    • 取消订阅可观察对象和DOM事件
    • 停止interval计时器
    • 反注册该指令在全局或应用服务中注册过的所有回调

注: Init 钩子 只调用一次

DestoryRef 钩子,用于 替代 ngOnDestroy() 优化代码逻辑

ts
@Component(...) class Counter { count = 0; constructor() { const id = setInterval(() => this.count++, 1000); const destroyRef = inject(DestroyRef); destroyRef.onDestroy(() => clearInterval(id)); } }

takeUntilDestroyed 参考这篇文章

模版语法

  • {{}}
  • [attr]="attr" attr={{}}
  • (click)="onclick()"
  • [(ngModel)]="input" 双向绑定 需要引入FormsModule
    • <input type='text' [ngModel]='username' (ngModelChange)="username= $event">
    • <input type='text' [value]='username' (input)="username= $event.target.value">
    • 组件双向数据绑定案例
    • image.png
  • 条件*ngIf='isLogin'
html
<div *ngIf="条件表达式 else elseContent"> 条件为真显示的内容 <div> <ng-template #elseContent> 条件为假显示的内容 </ng-template> <div *ngIf="条件表达式; then thenTemplate; else elseTemplate"> </div> <ng-template #thenTemplate> 条件为真显示的内容 </ng-template> <ng-template #elseTemplate> 条件为假显示的内容 </ng-template>
  • 分支*ngSwitchngSwitchCasengSwitchDefault
html
<div [ngSwitch]="conditionExpression"> <ng-template [ngSwitchCase]="case1Exp">...</ng-template> <ng-template ngSwitchCase="case2LiteralString">...</ng-template> <ng-template ngSwitchDefault>...</ng-template> </div>
  • 循环 *ngFor="let item of arr"

    • 获取索引 *ngFor="let item of arr; let i = index"
    • 获取第一个元素和最后一个元素 同上; let first=first; let last=last"
    • 获取奇数偶数(数组的索引) 同上; let odd=odd; let even=even
    • 性能 同上; trackBy: trackElement
  • 获取元素的值 <input type='text' #Input (input)="onInput(Input.value)">

  • 获取元素的值(在JS中) @ViewChild()

    • @ViewChild('eleRef', {static: true}) static的意思是没有在那个 If或ngFor当中
  • <ng-content> 内容投影(插槽)

  • 样式绑定

html
<div [class.className]="条件表达式">...</div> <div [class.className2]="条件表达式">...</div> <div [ngClass]="{'One': true, 'Two': false}"></div> <div [ngStyle]="{'color': someColor, 'font-size': fontSize}"></div>

组件交互

  • [attr]="attr" 父组件属性传递给子组件, 子组件@Input()接收
  • 组件通过取值取getter和存值取setter监听数据变更,当然也可以通过ngOnChanges周期钩子
  • 父组件监听子组件的事件 (event)="pEvent($event)",子组件@Output() event = new EventEmitter() 再通过 event.emit('xxx') 触发
  • 父子组件通过本地变量互动
ts
// 父子组件通过本地变量互动 @component({ //... template: `<button type="button" (click)="child.start()">Start</button> <app-child #child>` }) export class Parent{ } // 父组件本身的代码对子组件没有访问权。
  • 父组件调用@ViewChild(), 类似vue或react中的ref
ts
// 父级调用 @ViewChild() @component({ //... template: `<app-child>` }) export class Parent { @ViewChild(ChildComponent) private childComponent!: ChildComponent; }
  • 父子组件通过服务来通信

管道

这里在Angular1.0和Vue中称为过滤器 比如 1234.88 => ¥1,234.88

  • {{obj | json}}
  • {{data | data: 'MM-dd'}}
  • {{price | currency: 'CNY' : 'symbol' : '0.0-2'}}
  • {{data | slice:1:3}}

自定义管道

ts
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'appAgo' }) export class AgoPipe implements PipeTransform { transform(value: any, ...args: any[]): any { if(value) { const second = (Date.now() - (new Date(value)).getTime())/1000 if(second < 30) { return '刚刚' } return new Date(value).toLocaleDateString(); } } }

在 module中注入AgoPipe ,然后可以在模版中使用了 {{date | appAgo}}

内容投影

<ng-content select="样式类/html标签/指令"></ng-content>

内容投影 可类比 Vue的插槽,(Vue的插槽有 默认插槽、具名插槽、作用域插槽),但是angular好像没有作用域插槽一说,个人理解需要service模拟

  • 单槽内容投影
  • 多槽内容投影
  • 条件内容投影 不建议*ngIf,书写起来比较麻烦

条件内容投影示例

  • 父组件
ts
<app-child> <ng-template appExampleZippyContent> It depends on what you do with it. </ng-template> </app-child>
  • 子组件
ts
@Directive({ selector: '[appExampleZippyContent]', }) export class ZippyContentDirective { constructor(public templateRef: TemplateRef<unknown>) {} } @Component({ selector: 'app-child', template: ` <div *ngIf="expanded"> <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container> </div> `, }) export class ChildComponent { @Input() expanded = false; @ContentChild(ZippyContentDirective) content!: ZippyContentDirective; }
  • <ng-container>不增加节点

Service

组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。

两种用法

  1. construct注入
  2. 通过inject注入

依赖注入

image.png

ts
@Injectable({ providedIn: 'root' // 这是什么意思? })

这其实是按需引入的意思, 如果你没有使用这个service 就不会参与打包

AJAX

todo ajax示例

todo ajax拦截器

指令

允许您以声明性和可充用的方式向元素添加新行为。

  • *ngIf="hasPrivileges"
  • *ngFor="let task of taskList"

自定义Angular指令可以通过指令后缀 如 my-custom-name.directive.ts

ts
@Directive({ selector: '[appHighlight]', }) export class HighLightDirective { private el = inject(ElementRef); constructor(private el: ElementRef, private rd2: Renderer2) { this.el.nativeElement.style.backgroundColor = 'yellow' // rd2.setStyle(el.nativeElement, 'display', 'grid'); } } // <p appHighLight>Look</p>

Angular中的指令分为三类

  1. 组件是一种特殊的指令 (带模版)
  2. 结构型指令 改变宿主文档结构
    • ngIf ngFor ngSwitch
  3. 属性型指令 改变宿主行为
    • ngClass ngStyle ngModel

指令也有生命周期 比组件少

路由

  • .forRoot
  • .forChild
  • 跳转
    • <a routerLink="/home/1">
    • constructor(private router: Router){} + this.router.navigate(['home', tab.id])
    • this.router.navigate(['home', tab.id, {name: 'zs'}]) 对应 /home/1;name=zs 通过paramsMap取值
    • this.router.navigate(['home', {queryParams: {name: 'zs'}}]) 对应 /home?name=zs 通过queryParams取值
    • <a [routerLink]="['/home']" [queryParams]="{name: 'val1'}">
  • 读取动态路由参数
    • this.router.paramMap.subscribe(params => {...})
    • this.router.queryParamMap.subscribe(params => {...})
  • Active样式 routerLinkActive="active" + .active{}
ts
const routes: Routes = [{ path: 'home', component: HomeContainerComponent, children: [ // 嵌套路由 // 重定向路由 {path: '', redirectTo: 'hot', pathMatch: 'full'}, // 动态路由 {path: ':tabLink', component: HomeDetailComponent} ] }, { path: '**', component: 404Component }]
  • 嵌套路由需要标签 <router-outlet> 配合使用
    • 可以有多个 <router-outlet name='first'> <===> {path: 'xx', component: Comp, outlet: 'first'} 这就是辅助路由, 辅助路由可以很奇怪如http://xxx.x/xx(grand//second:aux)?name=zs 了解即可

懒加载

  • app-routing.module.ts
ts
const routes: Routes = [ {path: '', redirectTo: 'home', pathMatch: 'full'}, {path: 'my', loadChildren: () => import('./my').then(m => m.MyModule)} ];

生态 与 工具

生态

标题
Angular Router基于Angular组件的路由机制。支持惰性加载、嵌套路由、自定义路径匹配规则等
Angular Forms统一的表单填报与验证体系
Angular HttpClient健壮的HTTP客户端库,支持更高级的客户端-服务器通讯
Angular Animations丰富的动画体系,用于驱动基于用户状态的动画
Angular PWA一些用于构建渐进式Web应用PWA的工具
Angular Schematice一些搭建脚手架、重构和升级的自动化工具
  • Angular内置了 HTTPClient,以流的形式提供给你的应用
    • import {HttpClientModule} from '@angular/common/http'
  • @angular/core
  • @angular/forms
  • @angular/common/http
  • @angular/router

工具

  • 浏览器插件
  • VsCode插件

VSCode Angular插件 Angular Extension Pack

ngrx状态管理

  • 状态管理

脏值检测

Q: 什么是脏值检测?
A: 当数据改变时更新视图(DOM)

脏值检测原理

Q: 什么时候会触发脏值检测?
A: 有三类
1 浏览器事件(如click、mouseover、keyup等)
2 setTimeout() 和 setInterval()
3 HTTP请求

Q:如何进行检测?
A:比较当前状态和新状态的值

Q:Angular内部如何得知组件内部变化需要更新视图的呢?
A:Angular在读取视图模板时进行绑定
image.png
对于每个视图有多个绑定,angular是一个视图的树状结构,
当触发脏值检测时会 同步检查单向数据流
image.png

脏值检测注意事项

Q:组件的生命周期和脏检测有什么关系
A: image.png

在钩子AfterViewCheckedAfterViewInit中是不能改变有绑定关系的数据值,否则会造成无限循环的脏值检测

脏值检测性能瓶颈

这个检查过程是十分高效的,但是当树的结构太大(几万几十万个节点),仍旧会出现性能瓶颈,优化方案是OnPush策略

image.png

默认策略是,不管任何时候发生数据变化,都会检测整棵树,
OnPush策略是, 只对组件当中的有input注解的属性进行检测,这个注解的属性发生改变,我们会进行一次脏值检测,如果它不改变,我们就不检测,而且** **。

当然这种优化的方案带来的问题是,有很多时候需要手动操作进行脏值检测

所以推荐笨组件(无状态组件或者只接收prop自身无state的组件),业务逻辑放在父组件处理

Q: 为什么脏值检测进行两次? A: 第一次是全量检查整棵树, 第一遍检查完立即进行第二遍检查, 第二次并不是完整的检查,而是看第一遍的值有没有新变化,如果有变化,为避免死循环,Angular会抛出异常

ngZone

有些情况(秒杀、限时抢购)必须在钩子AfterViewCheckedAfterViewInit中更新视图,怎么办?这就需要用到ngZone,但是在学习ngZone之前,我们先来了解下什么是Zone?

Zone其实是一个第三方的类库 Zone.js, 它是一个JS的运行时,我们可以把浏览器划分为若干个区域,每个区域独立运行自身程序,彼此互不干扰, angular程序本身运行在一个Zone中,如果我们把某些更新放在另一个Zone中,这样就绕过了angular的脏值检测。

案例 毫秒级倒计时

代码如下

ts
import { formatDate } from '@angular/common'; import { AfterViewChecked, ChangeDetectionStrategy, Component, ElementRef, NgZone, Renderer2, ViewChild } from '@angular/core'; @Component({ selector: 'app-ngzone', template: `<span #timeRef></span>`, changeDetection: ChangeDetectionStrategy.OnPush }) export class NgzoneComponent implements AfterViewChecked { @ViewChild('timeRef', {static: true}) timeRef: ElementRef| null = null; constructor(private ngZone: NgZone, private rd: Renderer2) { } ngAfterViewChecked(): void { this.ngZone.runOutsideAngular(() => { setInterval(() => { this.rd.setProperty( this.timeRef!.nativeElement, 'innerText', formatDate(Date.now(), 'HH:mm:ss:SSS', 'en-US') ) }, 100) }) } }

ChangeDetectionStrategy.OnPush告知angualar 它只接收父组件的状态变化,自身不维护状态不会触发脏检查。

那如果我们既想用OnPush,又需要在某个方法更新视图怎么办?

ts
import { ActivatedRoute } from '@angular/router'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core"; @Component({ selector: 'app-handle', template: `...`, changeDetection: ChangeDetectionStrategy.OnPush }) export class NgzoneComponent implements OnInit{ constructor( private route: ActivatedRoute, private cd: ChangeDetectorRef) { } selectedTab = 'aa'; ngOnInit(): void { this.route.paramMap.subscribe(params => { this.selectedTab = params.get('xx') as string this.cd.markForCheck() }) } }

Angular17

  1. 内置控制流循环,大幅提升运行性能
  2. 引入了可延迟的试图,提升性能和开发体验
  3. 混合渲染和客户端渲染,大幅提升构建速度
  4. 新生命周期钩子 afterRender afterNextRender
  5. 新项目默认使用 Viteesbuild
  6. 其他: 新官网、新@angular/ssr

image.png

Rxjs

rxjs ==> Reactive Extensions For Javascript

  • RxJS 是一个编写异步和基于事件的程序的库。
  • RxJS 是一个使用可观察序列编写异步和基于事件的程序的库。
  • 它提供了一种核心类型,即 Observable、一些周边类型(ObserverSchedulerSubjects)和类似于 Array 方法(mapfilterreduceevery 等)的操作符,以便将异步事件作为集合进行处理。

学习rxjs,思维观念要转变:rx要把事件或数据看成一个流,所谓的响应式编程即随着流中的元素变化随之做出相应的动作

  • 流的种类:无限、有限、单个、空
  • 流的状态: next error complate
  • 所有的操作都是异步的。

可以将 RxJS 视为处理事件的 Lodash

ReactiveX 将观察者模式迭代器模式使用集合的函数式编程相结合,以便让你更好地管理事件序列。

RxJS 中解决异步事件管理的基本概念有:

  • Observable(可观察者):表示未来(future)值或事件的可调用集合的概念。
  • Observer(观察者):是一个回调集合,它知道如何监听 Observable 传来的值。
  • Subscription(订阅):表示 Observable 的一次执行,主要用于取消执行。
  • Operator(操作符):是纯函数,可以使用 map、filter、concat、reduce 等操作来以函数式编程风格处理集合。
  • Subject(主体):相当于一个 EventEmitter,也是将一个值或事件多播到多个 Observers 的唯一方式。
  • Scheduler(调度器):是控制并发的集中化调度器,允许我们在计算发生时进行协调,例如 setTimeout 或 requestAnimationFrame 或其它。

Q: 前端有哪些处理异步的方式?

  1. callback
  2. addEventListener
  3. EventEmmitter Tapable 其他发布订阅模式
  4. 框架的状态管理库 如 redux vuex pinia
  5. Promise
  6. generator
  7. rxjs
  8. async await

Observable

ts
import { Observable } from 'rxjs'; // 1. 创建Observers const observable = new Observable( /* subscribe函数 */ (subscriber) => { subscriber.next(1); subscriber.next(2); // subscriber.next(3 + (12n as any)); // 报错触发error(),后续不在执行 setTimeout(() => { subscriber.complete(); // complete之后不再会触发next subscriber.next(4); }, 1000); } ); console.log('just before subscribe'); // 订阅 observable.subscribe({ next(x) { console.log('got value ' + x); }, error(err) { console.error('something wrong occurred: ' + err); }, complete() { console.log('done'); }, }); console.log('just after subscribe'); // Observable是惰性求值, 而EventEmmitter是急性执行 // Observable和普通函数的区别是,可以随着时间的推移返回多个值 // 与事件处理器的区别是,它不会注册监听器,所以不需要销毁
ts
const myObservable = new Observable((subscriber) => { const timerId = setInterval(() => { console.log('---'); subscriber.next('hello'); }, 1000); return function unsubscribe() { console.log('unsubscribe', arguments); clearInterval(timerId); }; }); const mySubscription = myObservable.subscribe(console.log); setTimeout(() => { mySubscription.unsubscribe(); }, 3400);

Observer

ts
const observer = { next: (x) => console.log('Observer got a next value: ' + x), error: (err) => console.error('Observer got an error: ' + err), complete: () => console.log('Observer got a complete notification'), };
  • next() error() comple()是可选的

  • observable.subscribe(fn); fn默认是next()

Operator 操作符

操作符是函数。有两种操作符

  • 可联入管道的操作符,不改变现有的Observable实例,而是返回新的Observable实例。
  • 创建操作符

obs.pipe(op1, op2);op2(op1(obs))

Subject

  • Subject 是一种特殊类型的 Observable。
  • 它允许将值多播到多个 Observer

image.png

除了将单播Observer转换为多播,Subject 类型还有一些特化: BehaviorSubjectReplaySubjectAsyncSubject

BehaviorSubject

ts
const subject = new BehaviorSubject(0); // 0 is the initial value subject.subscribe({ next: (v) => console.log(`observerA: ${v}`), }); setTimeout(() => { subject.next(1); setTimeout(() => { subject.subscribe({ next: (v) => console.log(`observerB: ${v}`), }); setTimeout(() => { subject.next(2); }, 1000); }, 2000); }, 1000); // 打印 observerA:0 // 1s后 打印 observerA:1 // 再隔2s后打印 observerB: 1 //  不会打印 observerB: 0 // 再隔2s后打印 observerA: 2 observerB: 2 /* 如果改成 Subject 1. 不能携带初始化参数 2. 不可以将旧值发给订阅者 效果: 先打印 observerA:1 隔2s后打印 observerA: 2 observerB: 2 */

ReplaySubject

与BehaviorSubject类似,ReplaySubject会记录自Observable执行的多个值,并将他们重播给新订阅者

除了设置缓存区大小外, 还可以指定窗口时间(毫秒)

AsyncSubject

仅将Observable执行的最后一个值发送给器Observer,并且仅在执行完成时发送。

Scheduler

调度器控制某个订阅何时开始,以及何时传送通知。它由三部分组成

  • 调度器是一种数据结构
  • 调度器是一个执行上下文
  • 调度器有一个虚拟时钟

使用调度器 .pipe(observeOn(asyncScheduler))

调度器的类型

image.png

深度融合Angular

看一个示例

image.png

由上图可知rxjs不仅仅能简化代码而且还优化代码,有以下两点

  1. 不需要销毁订阅
  2. 如果你的组件使用了OnPush, 则也不需要使用this.cd.markForCheck(), Angular内部已经协调好问题。

再来看一个倒计时的案例

ts
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core'; import { Observable, interval } from 'rxjs'; import { map, takeWhile } from 'rxjs/operators'; @Component({ selector: 'app-count-down', template: `<div>{{ countDown$ | async }}</div>`, changeDetection: ChangeDetectionStrategy.OnPush }) export class CountDownComponent implements OnInit { @Input() startDate = new Date(); @Input() futureDate: Date; private _MS_PER_SECOND = 1000; countDown$: Observable<string>; constructor() {} ngOnInit() { this.countDown$ = this.getCountDownObservable( this.startDate, this.futureDate ); } private getCountDownObservable(startDate: Date, futureDate: Date) { return interval(1000).pipe( map(elapse => this.diffInSec(startDate, futureDate) - elapse), takeWhile(gap => gap >= 0), map(sec => ({ day: Math.floor(sec / 3600 / 24), hour: Math.floor((sec / 3600) % 24), minute: Math.floor((sec / 60) % 60), second: Math.floor(sec % 60) })), map(({ hour, minute, second }) => `${hour}:${minute}:${second}`) ); } private diffInSec = (start: Date, future: Date): number => { const diff = future.getTime() - start.getTime(); return Math.floor(diff / this._MS_PER_SECOND); } }

如果两个流(异步)有依赖关系怎么处理?

首先不需要嵌套, rxjs提供了高级流

ts
this.data1$ = this.service.getData1().pipe(...) this.data2$ = this.data1$.pipe( switchMap(data1 => this.service.getData2(data1)), // ... your operator )

本文作者:郭郭同学

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!