Angular基础
一、创建应用
首先需要系统中有node环境 其次安装npm
安装angular-cli npm install @angular/cli -g
创建应用 ng new angular-test --minimal --inline-template false
--skipGit=true
--minimal=true 创建没有单元测试的简易脚手架
--skip-install
--style=css
--routing=false
--inlineTemplate
--inlineStyle
--prefix
运行应用 ng serve
--open=true 应用构建完成后在浏览器中运行
--hmr=true 开启热更新
hmrWarning=false 禁用热更新警告
--port 更改应用运行端口
二、组件
命令创建模板
ng g c <component-name>
// ng g c user// 一个模板一般拥有以下内容import { Component } from'@angular/core';
@Component({
selector: 'app-user',
template: `
<h1>
user works!
</h1>
`,
styles: ['h1 { font-weight: normal; }']
})
exportclassUserComponent {
}
// template 为单独文件时 改为templateUrl: ''// styles 为单个或多个文件时 styleUrls: []
组件生命周期(按以下顺序执行)
constructor 实例化组件类的构造函数,用于接收注入服务
ngOnChanges() 输入属性值发生变化时调用 ngOnInit()之前会调用一次
ngOnInit() 只调用一次,第一次显示数据绑定和设置指令/组件输入属性之后,初始化指令/组件
ngDoCheck() 紧跟ngOnChanges和首次执行ngOnInit()后调用
ngAfterContentInit() 当angular把外部内容投影进组件视图或指令所在的视图后调用(只调用一次)
ngAfterContentChecked() 当检查完被投影到组件或指令中的内容之后 ngAfterContentInit() 和每次 ngDoCheck() 之后调用。
ngAfterViewInit() 初始化完组件视图及其子视图或指令视图后 第一次 ngAfterContentChecked() 之后调用,只调用一次。
ngAfterViewChecked() ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。
ngOnDestory()
ngOnInit() 执行http请求 获取初始化数据 设置指令 绑定数据等操作
ngOnDestory() 取消订阅可观察对象和Dom事件 停止计时器 反注册该指令在全局或应用服务中注册过的所有回调。
@ViewChild(ChildViewComponent) viewChild!: ChildViewComponent; 获取子视图的组件实例
@ContentChild(ChildComponent) contentChild!: ChildComponent; 获取子组件内容投影的实例
视图封装
Component 的装饰器提供了 encapsulation 选项
ViewEncapsulation.ShadowDom:Angular 使用浏览器内置的 Shadow DOM API 将组件的视图包含在 ShadowRoot(用作组件的宿主元素)中,并以隔离的方式应用所提供的样式。
ViewEncapsulation.Emulated 只应用于组件的视图
ViewEncapsulation.None 全局应用样式
组件交互
父子组件传值
子组件接受父组件的传值
@Input('master') masterName = '';
通过setter截听输入属性的变化
import { Component, Input } from'@angular/core';
@Component({
selector: 'app-name-child',
template: '<h3>"{{name}}"</h3>'
})
exportclassNameChildComponent {
@Input()
getname(): string { returnthis._name; }
setname(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
private _name = '';
}
通过onChanges()监听输入属性的变化
ngOnChanges(changes: SimpleChanges) {}
父组件传值
<child [master]="value"></child>
父组件接收子组件发出的通知
// 子组件@Output() voted = newEventEmitter<boolean>();
vote(agreed: boolean) {
this.voted.emit(agreed);
}
// 父组件
<child (vote)="onVote($event)"></child>
onVoted(agreed: boolean) {}
父组件获取子组件的数据方法
1.组件模板中获取
<app-countdown-timer #timer>
import { Component } from'@angular/core';
import { CountdownTimerComponent } from'./countdown-timer.component';
@Component({
selector: 'app-countdown-parent-lv',
template: `
<h3>Countdown to Liftoff (via local variable)</h3>
<button type="button" (click)="timer.start()">Start</button>
<button type="button" (click)="timer.stop()">Stop</button>
<div class="seconds">{{timer.seconds}}</div>
<app-countdown-timer #timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export classCountdownLocalVarParentComponent { }
2.本地变量的方法 (组件类获取 ViewChild)
@ViewChild(CountdownTimerComponent) private timerComponent!: CountdownTimerComponent;
start() { this.timerComponent.start(); }
stop() { this.timerComponent.stop(); }
3.通过服务进行通讯
// 在构造函数中注入服务constructor(private missionService: MissionService) {}
组件样式
:host
:host-context
:host ::ng-deep h3 {}
内容投影
单插槽内容投影
<ng-content></ng-content>
多插槽内容投影
<ng-contentselect="[question]"></ng-content>
//父组件:
<child><pquestion></p></child>
或者
<ng-contentselect=".question"></ng-content><child><pclass="question"></p></child>
有条件的内容投影
动态组件
先创建一个指令
// ad.directive.ts// viewContainerRef 获取对容器视图的访问权import { Directive, ViewContainerRef } from'@angular/core';
@Directive({
selector: '[adHost]',
})
exportclassAdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
加载组件
// ad-banner.component.ts
template: `
<div class="ad-banner-example">
<h3>Advertisements</h3>
<ng-template adHost></ng-template>
</div>
`
export classAdBannerComponentimplementsOnInit, OnDestroy {@Input() ads: AdItem[] = [];
currentAdIndex = -1;
@ViewChild(AdDirective, {static: true}) adHost!: AdDirective;
interval: number|undefined;
ngOnInit(): void {
this.loadComponent();
this.getAds();
}
ngOnDestroy() {
clearInterval(this.interval);
}
loadComponent() {
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
const adItem = this.ads[this.currentAdIndex];
const viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent<AdComponent>(adItem.component);
componentRef.instance.data = adItem.data;
}
getAds() {
this.interval = setInterval(() => {
this.loadComponent();
}, 3000);
}
}
三、模板
文本插值
<p>{{title}}</p>
<div><img alt="item" src="{{itemImageUrl}}"></div
模板语句
模板上下文中的名称优先于组件上下文中的名称
<button type="button" (click)="deleteHero($event)">Delete hero</button>
绑定
属性绑定:<p [attr.attribute-you-are-targeting]="expression">
类和样式绑定:
绑定到单个类:[class.sale]="onSave" onSave为真时添加类
绑定到多个类: [class]="classExpression" classExpression: 数组,对象,字符串(空格分割)
绑定到单一样式: [style.background-color]="expression"
绑定到多个样式: [style]="styleExpression"
样式的字符串列表,比如 "width: 100px; height: 100px; background-color: cornflowerblue;"。
一个对象,其键名是样式名,其值是样式值,比如 {width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}。
事件绑定:(click)="eventHandler" (keydown.shift.t)="eventHandler"
属性绑定: [src]="imgSrc" [disabled]="isDisabled"
双向绑定: [(size)] = "fontSize"
双向绑定的原理:@OutPut()属性的名称必须遵循inputChange模式,其中input是@Input()属性的名称,比如@Input()属性为size,则@Output属性必须为sizeChange
管道
使用管道去转换字符串,货币金额,日期等数据
DatePipe:根据本地环境中的规则格式化日期值。
UpperCasePipe:把文本全部转换成大写。
LowerCasePipe :把文本全部转换成小写。
CurrencyPipe :把数字转换成货币字符串,根据本地环境中的规则进行格式化。
DecimalPipe:把数字转换成带小数点的字符串,根据本地环境中的规则进行格式化。
PercentPipe :把数字转换成百分比字符串,根据本地环境中的规则进行格式化。
JsonPipe 格式化json数据
{{ birthday | date:'YYYY-MM-DD' }} 多个参数用:分割
{{ birthday | date | uppercase}}
自定义管道
指定字符串长度不能超过规定长度
// summary.pipe.tsimport { Pipe, PipeTransform } from'@angular/core';
@Pipe({
name: 'summary'
})
exportclassSummaryPipeimplementsPipeTransform {
transform(value: string, ...args: number[]): unknown {
if (!value) returnnull;
let actualLimit = args.length > 0 ? args[0] : 20;
return value.substring(0, actualLimit) + '...';
}
}
// app.module.tsimport { SummaryPipe } from'./summary.pipe'@NgModule({
declarations: [
SummaryPipe
]
});
模板引用变量
<input #phoneplaceholder="phone number" /><buttontype="button" (click)="callPhone(phone.value)">Call</button>
如果在组件上声明变量,该变量就会引用该组件实例。
如果在标准的 HTML 标记上声明变量,该变量就会引用该元素。
如果你在 元素上声明变量,该变量就会引用一个 TemplateRef 实例来代表此模板
如果该变量在右侧指定了一个名字,比如 #var="ngModel",那么该变量就会引用所在元素上具有这个 exportAs 名字的指令或组件。
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)"><labelfor="name">Name</label><inputtype="text"id="name"class="form-control"name="name"ngModelrequired /><buttontype="submit">Submit</button></form><div [hidden]="!itemForm.form.valid"><p>{{ submitMessage }}</p></div>
模板输入变量
<ng-template ngFor let-hero let-i="index" [ngForOf]="heroes">
<li>Hero number {{i}}: {{hero.name}}
</ng-template>
SVG作为模板
可生成动态交互式图形
四、指令
组件
带有模板的指令。这种指令类型是最常见的指令类型。
属性型指令
更改元素、组件或其他指令的外观或行为的指令
NgClass 添加和删除一组CSS类
添加或删除单个类使用类绑定
This div is special
NgStyle 添加和删除一组HTML样式
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
NgModel 将双向绑定数据添加到HTML表单
<input [(ngModel)]="currentItem.name" id="example-ngModel">
结构型指令
通过添加和删除 DOM 元素来更改 DOM 布局
*ngIf *ngFor *ngSwitch [hidden]
<div *ngIf="nullCustomer">Hello, <span>{{nullCustomer}}</span></div><div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div><li
*ngFor="
let item of list;
let i = index;
let isEven = even;
let isOdd = odd;
let isFirst = first;
let isLast = last;
"
></li><div [hidden]="data.length == 0">课程列表</div><div [ngSwitch]="currentItem.feature"><app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item><app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item><app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item><app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item><!-- . . . --><app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div>
// *ngFor和ngFor的对比
<div
*ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
[class.odd]="odd">
({{i}}) {{hero.name}}
</div><ng-templatengForlet-hero [ngForOf]="heroes"let-i="index"let-odd="odd" [ngForTrackBy]="trackById"><div [class.odd]="odd">
({{i}}) {{hero.name}}
</div></ng-template>
当没有单个元素承载指令时,可以使用 。
自定义指令
// hightlight.directive.tsimport { Directive, ElementRef, HostListener, Input } from'@angular/core';
@Directive({
selector: '[appHighlight]'
})
exportclassHighlightDirective {
@Input() appHighlight = '';
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight || 'red');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
privatehighlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
// <p [appHighlight]="color">Highlight me!</p>// 与组件一样,你可以将指令的多个属性绑定添加到宿主元素上
指令组合API
@Component({
selector: 'admin-menu',
template: 'admin-menu.html',
hostDirectives: [{
directive: MenuBehavior,
inputs: ['menuId'], // 别名 ['menuId: id'],outputs: ['menuClosed'],
}],
})
export class AdminMenu { }
<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()">
五、依赖注入
依赖注入简称DI, 是面向对象编程中的一种设计原则,用来减少代码之间的耦合度。 在 Angular 的 DI 框架中有四个核心概念:
Dependency:组件要依赖的实例对象,服务实例对象
Token:获取服务实例对象的标识
Injector:注入器,负责创建维护服务类的实例对象并向组件中注入服务实例对象。
Provider:配置注入器的对象,指定创建服务实例对象的服务类和获取实例对象的标识。
在组件中:
@Component({
selector: 'hero-list',
template: '...',
providers: [HeroService]
})
在模块中
@NgModule({
declarations: [HeroListComponent]
providers: [HeroService]
})
注入依赖项
@Component({ … })classHeroListComponent{
constructor(private service: HeroService) {}
}
六、服务
创建服务
ng g s xxx/xxx
服务的作用域
在根注入器中注册服务,所有模块使用同一个服务实例对象。
@Injectable({
providedIn: 'root', //root表示所有模块使用
})
在模块级别注册服务 providedIn: CarModule 或 @NgModule({ providers: [CarListService], })
在组件级别注入服务 providers: [ CarListService ]
七、表单
模板驱动
引入依赖模块FormsModule
将DOM表单转换为ngForm <form #f="ngForm" (submit)="onSubmit(f)"></form>
声明表单字段为ngModel <input type="text" name="username" ngModel>
获取表单字段值
onSubmit(form: NgForm) {
console.log(form.value)
}
表单分组
<form #f="ngForm" (submit) = "onSubmit(f)"><divngModelGroup="user"><inputtype="text"name="username"ngModel /></div><divngModelGroup="contact"><inputtype="text"name="phone"ngModel /></div></form>
表单验证
required 必填字段
minlength 字段最小长度
maxlength 字段最大长度
pattern 验证正则 例如:pattern="\d" 匹配一个数值
模型驱动
概念
1. FormControl: 表单项
2. FormGroup: 表单组
3. FormArray: 表单列表
快速上手
引入 ReactiveFormsModule
在组件类中创建FormGroup contactForm: FormGroup = new FormGroup({ name: new FormControl('xxx')})
关联组件模板中的表单
<form [formGroup]="contactForm"><inputformControlName="name"/></form>
获取表单值 this.cntactForm.value
表单验证器
内置表单验证器 Validators.required, minLength等 name: new FormControl('xxx', [Validators.required]) 自定义同步表单验证器
import { AbstractControl, ValidationErrors } from"@angular/forms"
export class NameValidators {
// 字段值中不能包含空格
static cannotContainSpace(control: AbstractControl): ValidationErrors | null {
// 验证未通过
if (/\s/.test(control.value)) return { cannotContainSpace: true }
// 验证通过
return null
}
}
import { NameValidators } from"./Name.validators"
contactForm: FormGroup = newFormGroup({
name: newFormControl("", [
Validators.required,
NameValidators.cannotContainSpace
])
})
自定义异步表单验证器
import { AbstractControl, ValidationErrors } from"@angular/forms"
export class NameValidators {
static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors |
null> {
returnnewPromise(resolve => {
if (control.value == "admin") {
resolve({ shouldBeUnique: true })
} else {
resolve(null)
}
})
}
}
contactForm: FormGroup = newFormGroup({
name: newFormControl("",
[Validators.required],
NameValidators.shouldBeUnique
)
})
FormBuilder 创建表单的快捷方式
this.fb.control :表单项
this.fb.group :表单组,表单至少是一个 FormGroup
this.fb.array :用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray 中有一项没通过,整体没通过。
import { FormBuilder, FormGroup, Validators } from "@angular/forms"
export classAppComponent{
contactForm: FormGroup
constructor(private fb: FormBuilder) {
this.contactForm = this.fb.group({
fullName: this.fb.group({
firstName: ["😝", [Validators.required]],
lastName: [""]
}),
phone: []
})
}
}
方法:
patchValue:设置表单控件的值(可以设置全部,也可以设置其中某一个,其他不受影响)
setValue:设置表单控件的值 (设置全部,不能排除任何一个)
valueChanges:当表单控件的值发生变化时被触发的事件
reset:表单内容置空
八、路由
路由是以模块为单位的,每个模块都可以有自己的路由
快速上手
创建组件
创建路由规则
// app.module.ts
import { Routes } from "@angular/router"
const routes: Routes = [
{
path: "home",
component: HomeComponent
},
{
path: "about",
component: AboutComponent
}
]
引入路由模块
// app.module.ts
import { RouterModule, Routes } from "@angular/router"
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
})
export class AppModule {}
添加路由插座
<router-outlet></router-outlet>
导航组件中定义链接
<a routerLink="/home">首页</a>
<a routerLink="/about">关于我们</a>
匹配规则
重定向:
{
path: "",
// 重定向
redirectTo: "home",
// 完全匹配
pathMatch: "full"
}
匹配符
{
path: "**",
component: NotFoundComponent
}
路由传参
查询参数
<a routerLink="/about" [queryParams]="{ name: 'kitty' }">关于我们</a>
import { ActivatedRoute } from "@angular/router"
export class AboutComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.queryParamMap.subscribe(query => {
query.get("name")
})
}
}
动态参数
// 在路由参数上体现:
path: "about/:name",
<a [routerLink]="['/about', 'zhangsan']">关于我们</a>
import { ActivatedRoute } from "@angular/router"
export class AboutComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
params.get("name")
})
}
}
导航路由
// app.component.ts
import { Router } from "@angular/router"
export class HomeComponent {
constructor(private router: Router) {}
jump() {
this.router.navigate(["/about/history"], {
queryParams: {
name: "Kitty"
}
})
}
}
路由懒加载
// app-routing.module.ts
const routes: Routes = [
{
path: "user",
loadChildren: () => import("./user/user.module").then(m => m.UserModule)
}
]
路由守卫
路由可以应用多个守卫,所有守卫方法都允许,路由才被允许访问,有一个守卫方法不允许,则路由不
允许被访问
canActivate
检查用户是否可以访问某一个路由
创建路由守卫 ng g guard guards/auth
import { Injectable } from "@angular/core"
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree,
Router } from "@angular/router"
@Injectable({providedIn: "root"})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean | UrlTree {
// 用于实现跳转
return this.router.createUrlTree(["/user/login"])
// 禁止访问目标路由
return false
// 允许访问目标路由
return true
}
}
canActivateChild
检查用户是否方可访问某个子路由。
创建路由守卫: ng g guard guards/admin 注意:选择 CanActivateChild
import { Injectable } from "@angular/core"
import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree
} from "@angular/router"
@Injectable({providedIn: "root"})
export class AdminGuard implements CanActivateChild {
canActivateChild(): boolean | UrlTree {
return true
}
canDeactivate
检查用户是否可以退出路由。比如用户在表单中输入的内容没有保存,用户又要离开路由,此时可以调
用该守卫提示用户。
import { Injectable } from "@angular/core"
import {
CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree
} from "@angular/router"
import { Observable } from "rxjs"
export interface CanComponentLeave {
canLeave: () => boolean
}
@Injectable({
providedIn: "root"
})
export class UnsaveGuard implements CanDeactivate<CanComponentLeave> {
canDeactivate(component: CanComponentLeave): boolean {
if (component.canLeave()) {
return true
}
return false
}
}
Resolve
允许在进入路由之前先获取数据,待数据获取完成之后再进入路由
ng g resolver <name>
import { Injectable } from "@angular/core"
import { Resolve } from "@angular/router"
type returnType = Promise<{ name: string }>
@Injectable({
providedIn: "root"
})
export class ResolveGuard implements Resolve<returnType> {
resolve(): returnType {
return new Promise(function (resolve) {
setTimeout(() => {
resolve({ name: "张三" })
}, 2000)
})
}
}
// 获取到的数据在组件中使用
export class HomeComponent {
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
console.log(this.route.snapshot.data.user)
}
}
在路由表中,路由守卫的写法:
{
path: "about",
component: AboutComponent,
canActivate: [AuthGuard],
canActivateChild: [AdminGuard],
canDeactivate: [UnsaveGuard],
resolve: {
user: ResolveGuard
},
children: [
{
path: 'test',
...
}
]
}
九、RxJS
RxJS 是一个用于处理异步编程的 JavaScript 库,目标是使编写异步和基于回调的代码更容易。
基本概念
Observable:可观察对象,是一个可调用的未来值或事件的集合
Observe:观察者,一个回调函数的集合,它知道如何去监听由Observable提供的值
Subscription:订阅,主要用于取消Observable的执行
Operators: 操作符,采用函数式编程风格的纯函数
Subject:主体,相当于EventEmitter,并且是将值或事件多路推送给多个Observer的唯一方式
Schedulers:调度器,用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,比如setTimeout,requestAnimationFrame等
可观察对象
Observable
Observable对象内部可多次调用next方法向外发送数据
可使用complete方法停止数据的发送
可使用error方法发送错误消息
可观察对象是惰性的,只有被订阅才会执行
可观察对象可以有很多的订阅者
const observable = new Observable(observer => {
let index = 0;
let timer = setInterval(() => {
observer.next(index++);
if(index === 3) {
observer.complete()
// 发生错误 发送错误消息
// observer.error('xxx') clearInterval(timer)
}
}, 1000)
})
const observer = {
next: function(value) {
console.log(value)
},
complete: function() { // 数据发送完成}
}
observable.subscribe(observer)
Subject
创建空的可观察对象,在订阅后不会立即执行,next方法可以在可观察对象外部调用
每个 Subject 都是观察者
每个 Subject 都是 Observable 。
这个 Subject 可能有多个订阅者,然而普通的 “单播 Observable” 只发送通知给单个观察者。
import { Subject } from "rxjs"
const demoSubject = new Subject()
demoSubject.subscribe({next: function (value) {console.log(value)}})
demoSubject.subscribe({next: function (value) {console.log(value)}})
setTimeout(function () {
demoSubject.next("hahaha")
}, 3000)
BehaviorSubject
拥有Subject的全部功能,但是在创建Observable对象时可传默认值,观察者订阅后可以直接拿到默认值
import { BehaviorSubject } from "rxjs"
const demoBehavior = new BehaviorSubject("默认值")
demoBehavior.subscribe({next: function (value) {console.log(value)}})
demoBehavior.next("Hello")
ReplaySubject
功能类似 Subject,但有新订阅者时两者处理方式不同,Subject 不会广播历史结果,而 ReplaySubject
会广播所有历史结果。
import { ReplaySubject } from "rxjs"
const rSubject = new ReplaySubject()
rSubject.subscribe(value => {
console.log(value)
})
rSubject.next("Hello 1")
rSubject.next("Hello 2")
setTimeout(function () {
rSubject.subscribe({next: function (value) {console.log(value)}})
}, 3000)
辅助方法
of:参数列表作为数据流返回
of("a", "b", [], {}, true, 20).subscribe(v => console.log(v))
form:将Array,Promise,Iterator转为Observable对象
import { from } from "rxjs"
function p() {
return new Promise(function (resolve) {
resolve([100, 200])
})
}
from(p()).subscribe(v => console.log(v))
// [100, 200]
interval,timer
timer:间隔时间过去以后发出数值,行为终止,或间隔时间发出数值后,继续按第二个参数的时间间
隔继续发出值 timer(1000,2000)
zip 组合数据
将多个 Observable 中的数据流进行组合。和将来最新的进行组合。
import { zip, of } from "rxjs"
import { map } from "rxjs/operators"
let age = of(27, 25, 29)
let name = of("Foo", "Bar", "Beer")
let isDev = of(true, true, false)
zip(name, age, isDev)
.pipe(map(([name, age, isDev]) => ({ name, age, isDev })))
.subscribe(console.log)
// { name: 'Foo', age: 27, isDev: true }
// { name: 'Bar', age: 25, isDev: true }
// { name: 'Beer', age: 29, isDev: false }
forkJoin 类似Promise.all
forkJoin 是 Rx 版本的 Promise.all(),即表示等到所有的 Observable 都完成后,才一次性返回值。
import axios from "axios"
import { forkJoin, from } from "rxjs"
axios.interceptors.response.use(response => response.data)
forkJoin({
goods: from(axios.get("http://localhost:3005/goods")),
category: from(axios.get("http://localhost:3005/category"))
}).subscribe(console.log)
retry:重试次数
如果 Observable 对象抛出错误,则该辅助方法会重新订阅 Observable 以获取数据流,参数为重新订
阅次数。
import { interval, of, throwError } from "rxjs"
import { mergeMap, retry } from "rxjs/operators"
interval(1000)
.pipe(
mergeMap(val => {
if (val > 2) {
return throwError("Error!")
}
return of(val)
}),
retry(2)
)
.subscribe({
next: console.log,
error: console.log
})
race
接收并同时执行多个可观察对象,只将最快发出的数据流传递给订阅者。
import { race, timer } from "rxjs"
import { mapTo } from "rxjs/operators"
const obs1 = timer(1000).pipe(mapTo("fast one"))
const obs2 = timer(3000).pipe(mapTo("medium one"))
const obs3 = timer(5000).pipe(mapTo("slow one"))
race(obs3, obs1, obs2).subscribe(console.log)
fromEvent
将事件转换为 Observable。
import { fromEvent } from "rxjs"
const btn = document.getElementById("btn")
// 可以将 Observer 简写成一个函数,表示 next
fromEvent(btn, "click").subscribe(e => console.log(e))
操作符
当操作符被调用时,它们不会改变已经存在的 Observable 实例。相反,它们返回一个新的 Observable
操作符本质上是一个纯函数 (pure function),它接收一个 Observable 作为输入,并生成一个新的 Observable 作为输出。
操作符分类
创建操作符
ajax
转换操作符
过滤操作符
组合操作符
多播操作符
错误处理操作符
工具操作符
finally
let
条件布尔操作符
数学和聚合操作符
十、HttpClientModule
快速开始
// 引入模块
import { httpClientModule } from '@angular/common/http';
imports: [
httpClientModule
]
//注入http服务在component里
// app.component.ts
import { HttpClient } from '@angular/common/http';
export class AppComponent {
constructor(private http: HttpClient) {}
}
// 发送请求
import { HttpClient } from "@angular/common/http"
export class AppComponent implements OnInit {
constructor(private http: HttpClient) {}
ngOnInit() {
this.getUsers().subscribe(console.log)
}
getUsers() {
return this.http.get("https://jsonplaceholder.typicode.com/users")
}
}
请求方法
this.http.get(url [, options]);
this.http.post(url, data [, options]);
this.http.delete(url [, options]);
this.http.put(url, data [, options]);
请求参数
使用HttpParams类
import { HttpParams } from '@angular/common/http';
let params = new HttpParams({ fromObject: {name: "zhangsan", age: "20"}})
params = params.append("sex", "male")
let params = new HttpParams({ fromString: "name=zhangsan&age=20"})
// 类定义
export declare class HttpParams {
constructor(options?: HttpParamsOptions);
has(param: string): boolean;
get(param: string): string | null;
getAll(param: string): string[] | null;
keys(): string[];
append(param: string, value: string): HttpParams;
set(param: string, value: string): HttpParams;
delete(param: string, value?: string): HttpParams;
toString(): string;
}
declare interface HttpParamsOptions {
fromString?: string;
fromObject?: {
[param: string]: string | ReadonlyArray<string>;
};
encoder?: HttpParameterCodec;
}
请求头
使用HttpHeaders类
let headers = new HttpHeaders({ test: "Hello" })
// 类定义
export declare class HttpHeaders {
constructor(headers?: string | {
[name: string]: string | string[];
});
has(name: string): boolean;
get(name: string): string | null;
keys(): string[];
getAll(name: string): string[] | null;
append(name: string, value: string | string[]): HttpHeaders;
set(name: string, value: string | string[]): HttpHeaders;
delete(name: string, value?: string | string[]): HttpHeaders;
}
响应内容
declare type HttpObserve = 'body' | 'response';
// response 读取完整响应体
// body 读取服务器端返回的数据
this.http.get(
"https://jsonplaceholder.typicode.com/users",
{ observe: "body" }
).subscribe(console.log)
拦截器
拦截器是 Angular 应用中全局捕获和修改 HTTP 请求和响应的方式。(Token、Error)
拦截器将只拦截使用 HttpClientModule 模块发出的请求。
ng g interceptor <name>
请求拦截器
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor() {}
// 拦截方法
intercept(
// unknown 指定请求体 (body) 的类型
request: HttpRequest<unknown>,
next: HttpHandler
// unknown 指定响应内容 (body) 的类型
): Observable<HttpEvent<unknown>> {
// 克隆并修改请求头
const req = request.clone({
setHeaders: {
Authorization: "Bearer xxxxxxx"
}
})
// 通过回调函数将修改后的请求头回传给应用
return next.handle(req)
}
}
响应拦截器
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor() {}
// 拦截方法
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<any> {
return next.handle(request).pipe(
retry(2),
catchError((error: HttpErrorResponse) => throwError(error))
)
}
}
拦截器注入
import { AuthInterceptor } from "./auth.interceptor"
import { HTTP_INTERCEPTORS } from "@angular/common/http"
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
Proxy
// 在项目的根目录下创建 proxy.conf.json 文件并加入如下代码
{
"/api/*": {
"target": "http://localhost:3070",
"secure": false,
"changeOrigin": true
}
}
// secure:如果服务器端 URL 的协议是 https,此项需要为 true
// changeOrigin:如果服务器端不是 localhost, 此项需要为 true
// 指定proxy配置文件
"scripts": {
"start": "ng serve --proxy-config proxy.conf.json",
}
// 或
"serve": {
"options": {
"proxyConfig": "proxy.conf.json"
},
NgRx状态管理器
1. 下载 NgRx
npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/router-store
@ngrx/store-devtools @ngrx/schematics
2. 配置 NgRx CLI
ng config cli.defaultCollection @ngrx/schematics
// angular.json
"cli": {
"defaultCollection": "@ngrx/schematics"
}
3. 创建 Store
ng g store State --root --module app.module.ts --statePath store --stateInterface
AppState
import { isDevMode } from '@angular/core';
import {
ActionReducer,
ActionReducerMap,
createFeatureSelector,
createSelector,
MetaReducer
} from '@ngrx/store';
import * as fromList from './reducers/list.reducer';
export interface AppState {
[fromList.listFeatureKey]: fromList.State;
}
export const reducers: ActionReducerMap<AppState> = {
[fromList.listFeatureKey]: fromList.reducer,
};
export const metaReducers: MetaReducer<AppState>[] = isDevMode() ? [] : [];
4. 创建 Action
ng g action store/actions/counter --skipTests
import { createAction, props } from '@ngrx/store';
export const addList = createAction('addList', props<{ value: string}>());
export const deleteList = createAction('deleteList', props<{ id: string }>());
5. 创建 Reducer
ng g reducer store/reducers/counter --skipTests --reducers=../index.ts
import { createReducer, on } from '@ngrx/store';
import { addList, deleteList } from '../actions/list.actions';
import { v4 as uuidv4} from 'uuid';
export interface ToDo {
id: string,
value: string
}
export interface State {
list: ToDo[]
}
export const listFeatureKey = 'list';
export const initialState: State = {
list: [
{
id: '111', value: '122'
},
{ id: 'ssd', value: 'dsds'}
]
};
export const reducer = createReducer(
initialState,
on(addList, (state, action) => {
return {
...state,
list: [
...state.list,
{
id: uuidv4(),
value: action.value
}
]
}
}),
on(deleteList, (state, action) => {
const index = state.list.findIndex(i => i.id === action.id);
let newList = [...(state.list)]
newList.splice(index, 1)
return {
...state,
list: newList
}
})
);
6.创建 Selector
ng g selector store/selectors/counter --skipTests
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { AppState } from '..';
import { listFeatureKey, State } from '../reducers/list.reducer';
export const selectList = createFeatureSelector<AppState, State>(listFeatureKey)
export const selectLists = createSelector(selectList, state => state.list)
7.组件使用
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { fromEvent, Observable } from 'rxjs';
import { AppState } from 'src/app/store';
import { selectLists } from 'src/app/store/selectors/list.selectors';
import { filter, map } from 'rxjs/operators'
import { addList, deleteList } from 'src/app/store/actions/list.actions';
import { slide, todoAnimations } from 'src/app/animations';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styles: [
],
animations: [slide, todoAnimations]
})
export class ListComponent implements AfterViewInit {
@ViewChild('todoInput') todoInput!: ElementRef;
todos: Observable<{id: string, value: string}[]>
constructor(private store: Store<AppState>) {
this.todos = this.store.pipe(select(selectLists))
}
removeItem(id: string) {
console.log('id', id);
this.store.dispatch(deleteList({ id }))
};
ngAfterViewInit(): void {
fromEvent<KeyboardEvent>(this.todoInput.nativeElement, "keyup").pipe(
filter(event => event.key === "Enter"),
map(event => (<HTMLInputElement>event.target).value),
map(value => value.trim()),
filter(value => value !== "")
).subscribe(value => {
console.log('input', this.todoInput)
this.store.dispatch(addList({ value: value }))
this.todoInput.nativeElement.value = ''
})
}
}
8.模板
<div class="container">
<h2>TODO</h2>
<div class="form-group">
<input #todoInput type="text" class="form-control" placeholder="add todo" />
</div>
<ul class="list-group">
<li
@slide
*ngFor="let item of todos | async; let i = index;"
(click)="removeItem(item.id)"
class="list-group-item"
>
{{ item.value }}
</li>
</ul>
</div>
动画
定义
:enter | void => * 进入动画
:leave | * => void离开动画
trigger 方法用于创建动画,指定动画名称
transition 方法用于指定动画的运动状态,出场动画或者入场动画,或者自定义状态动画。
style 方法用于设置元素在不同的状态下所对应的样式
animate 方法用于设置运动参数,比如动画运动时间,延迟事件,运动形式
keyFrames 关键帧动画
transition(":leave", [
animate(
600,
keyframes([
style({ offset: 0.3, transform: "translateX(-80px)" }),
style({ offset: 1, transform: "translateX(100%)" })
])
)
])
query查询元素
group 动画并行
stagger 在多个元素同时执行同一个动画时,让每个元素动画的执行依次延迟。
transition(":enter", [
group([
query("h2", [
style({ transform: "translateY(-30px)" }),
animate(300)
]),
query("@slide", stagger(200, animateChild()))
])
])
state 定义状态
trigger("expandCollapse", [
// 使用 state 方法定义折叠状态元素对应的样式
state(
"collapsed",
style({
height: 0,
overflow: "hidden",
paddingTop: 0,
paddingBottom: 0
})
),
// 使用 state 方法定义展开状态元素对应的样式
state("expanded", style({ height: "*", overflow: "auto" })),
// 定义展开动画
transition("collapsed => expanded", animate("400ms ease-out")),
// 定义折叠动画
transition("expanded => collapsed", animate("400ms ease-in"))
])
举例
和NgRx例子一样,这里给出动画代码
// animation.ts
import {
animate,
animateChild,
animation,
group,
keyframes,
query,
style,
transition,
trigger,
useAnimation,
stagger,
} from "@angular/animations";
const slideUp = animation([
style({ opacity: 0, transform: "translateY(40px)" }),
animate(250),
]);
const slideOut = animation([
// animate(
// 600,
// keyframes([
// style({ offset: 0.3, transform: "translateX(-80px)" }),
// style({ offset: 1, transform: "translateX(100%)" }),
// ])
// ),
animate(600, style({ opacity: 0, transform: "translateX(100%)" })),
]);
export const slide = trigger("slide", [
transition(":enter", useAnimation(slideUp)),
transition(":leave", useAnimation(slideOut)),
]);
export const todoAnimations = trigger("todoAnimations", [
transition(":enter", [
group([
query("h2", [style({ transform: "translateY(-30px)" }), animate(300)]),
query("@slide", stagger(200, animateChild()), { optional: true }),
]),
]),
]);