angular快速上手

文章目录

  • 前言
  • 一、模块(一组功能的集合)
  • 二、组件(是用来描述用户界面的)
  • 三、服务(为了解耦组件类中的代码)
  • 四、共享模块(是一个ngMoudle)
  • 五、组件模板
  • 六、动态为元素添加类名及行内样式
  • 七、事件绑定
  • 八、获取原生DOM对象
  • 九、内容投影
  • 十、双向数据绑定
  • 十一、数据绑定容错处理
  • 十二、引入全局样式的三种方式
  • 十三、指令(在angular中去操作DOM)
  • 十四、自定义指令
  • 十五、angular管道
  • 十六、组件通讯
  • 十七、angular中的组件生命周期函数
    • 《1》挂载阶段:
    • 《2》更新阶段:
    • 《3》卸载阶段:
  • 十八、依赖注入
  • 十九、provider的使用
  • 二十、服务的创建与注入
  • 二十一、服务的作用域
    • 《1》 根注入器
    • 《2》模块级别注入器
    • 《3》组件级别注入器
  • 二十二、模板驱动表单用法
    • 《1》模板驱动表单
    • 《2》模型驱动表单
  • 二十三、使用formArray动态创建表单
  • 二十四、内置表单验证器(模型驱动)
  • 二十五、FormBuilder
  • 二十六、路由的基本使用
  • 二十七、路由传递参数的两种形式
    • 《1》查询参数
    • 《2》动态参数
  • 二十八、路由嵌套(定义子孙级路由)
  • 二十九、路由命名插座
  • 三十、导航路由(如何实现javascript的方法去实现页面之间的跳转)
  • 三十一、路由模块
  • 三十二、实现路由模块懒加载(按需加载)
  • 三十三、路由守卫 CanActivate(用来保护路由组件)
  • 三十四、路由守卫CanActiveChild
  • 三十五、CanDeactivate
  • 三十六、路由守卫 Resolve
  • 三十七、RXJS
  • 三十八、可观察对象
    • 《1》在Observable对象内部可以多次调用next方法向外发送数据
    • 《2》当所有数据发送完成之后,可以调用complete方法终止数据发送
    • 《3》当Observable内部逻辑发生错误时 可以调用error方法将失败信息发送给订阅者 Observable终止
    • 《4》可观察对象是惰性的 只有被订阅后才会执行
    • 《5》可观察对象可以有n多个订阅者observer 每次被订阅时都会得到执行
  • 三十九、使用Subject构造函数创建可观察对象(在订阅后不会立即执行广播)
  • 四十、使用BehaviorSubject创建可观察对象(在被订阅后可以立即执行)
  • 四十一、ReplaySubject
  • 四十二、数据流 操作符
  • 四十三、辅助方法
    • 《1》from:返回的是可观察对象
    • 《2》forkJoin:forkJoin是Rx版本的Promise.all() 即表示等到所有的Observable都完成之后 才一次性返回值
    • 《3》辅助方法fromEvent和操作符pluck 辅助方法返回是一个Observable对象
    • 《4》辅助方法interval和操作符switchMap
  • 四十四、操作符
  • 四十五、操作符节流和防抖
  • 四十六、辅助方法——of 方法返回的是一个Observable
  • 四十七、案例(元素拖拽)
  • 四十八、案例(搜索案例)
  • 四十九、串联请求
  • 五十、HttpClientModule
    • 《1》引入HttpClientModule模块
    • 《2》注入HttpClient服务实例对象,用于发送请求
    • 《3》发送请求
    • 《4》请求方法
  • 五十一、请求参数
  • 五十二、在发送数据的时候 在请求头中添加字段
  • 五十三、设置响应内容
  • 五十四、拦截器
    • 《1》请求拦截:
    • 《2》响应拦截
    • 《3》拦截器注入
  • 五十五、Angular Proxy (代理)
  • 五十六、NgRx
  • 五十七、NgRx基本
  • 五十八、Action Payload
  • 五十九、MetaReducer(中间件)
  • 六十、Effect(处理副作用)
  • 六十一、Entity
  • 六十二、Router Store
  • 六十三、动画
  • 六十四、入手
  • 六十五、定义动画时的两个注意事项
  • 六十六、定义帧动画
  • 六十七、动画回调
  • 六十八、创建可重用动画


前言

Angular学习

课程链接


一、模块(一组功能的集合)

  1. angular内部是typescript编写的 内部是模块化开发
  2. 可以使用angular中的 CLI方式创建 简便
  3. 在angular中有很多的ngModule 这样去组织代码的 也就是模块
  4. 一个ngModule可以使用另一个ngModule导出的功能 并不是相互独立的

二、组件(是用来描述用户界面的)

  1. 组件类 组件模板 组件样式
组件类:component.ts 
组件模板:component.html 
组件样式:component.scss
  1. 组件必须要属于一个ngModule的
  2. 同一个组件不能同时属于多个模块的 要想使用了呢?
其它模块依赖当前的模块 前提 当前模块要导出该组件

三、服务(为了解耦组件类中的代码)

场景:组件和组件之间可以共享的数据 angular不希望都写一遍 可以放置在服务中
服务类的实例对象 由angular帮忙创建 直接在组件类中通过construct的形参的方式
服务是单例模式


四、共享模块(是一个ngMoudle)

  1. 首先创建一个共享模块以及共享组件并导出
  2. 其次在根模块导入共享模块
  3. 在根组件中使用共享模块的组件

注意:将组件放在哪个模块的文件夹下 CLI会自动地识别 组件是属于哪一个module

数据绑定:

就是将组件类中的数据显示在组件模板中
即组件类和组件模板进行绑定
数据驱动DOM

组件中可以套用组件


五、组件模板

  1. 数据绑定

差值表达式的使用 在差值表达式中不能够写if else的 显示字符串时 需要加上单引号

<!-- 数据展示 -->
<div>
  {{ message }}
</div>
<div>
  {{ getInfo() }}
</div>
<div [innerHTML]="htmlString"></div>
<div>{{ 1 === 1 ? '相等' : '不相等' }}</div>
<div>{{ 'Angular' }}</div>```
  1. 属性绑定

DOM对象属性绑定 HTML标记属性和DOM都有 使用DOM对象属性绑定=“”
HTML标记属性绑定 attr.绑定的是自己的标记属性 DOM中没有的 不加这个字段会报错

<!-- 属性绑定的两种形式 -->
<!-- 1 HTML标记属性绑定 -->
<div [attr.data-test]="title"></div>

六、动态为元素添加类名及行内样式

绑定类名:

  1. [class.添加的类名]=“boolean”
  2. [ngClass]=“{类名:”“boolean,…}”
<!-- 为元素添加类名 不会改变原有的类 -->
<div class="a" [class.active]="true"></div>
<div class="b" [ngClass]="{ active: true, error: true }"></div>

行内样式和类名 相似
<!-- 行内样式 -->
<div [style.width]="'200px'"></div>
<div
  [ngStyle]="{ width: '200px', height: '200px', backgroundColor: 'red' }"

></div>

七、事件绑定

为元素绑定事件

<button (click)="onClick($event)">button</button>
<input type="text" (keyup.enter)="onKeyup()" />

八、获取原生DOM对象

  1. 在组件模板中获取 使用模板变量
  2. 在组件类中获取 ViewChild获取一个元素 ViewChildren获取一组元素

模板变量 ?. 对于程序的一种保护 当后者不存在时 便不会去访问


九、内容投影

  1. 根组件中只有一个div或者其他元素时 只需要在另一个组件中写一个ng-content进行占位即可
  2. 当有多个时 需要在ng-content指定select =“.类名”
    小知识:如果不想要外面的div 可以直接在根组件中使用ng-container

十、双向数据绑定

  1. 表单模块提供的–名字是formsMoudle
  2. 在input中使用[(ngModel)]去绑定组件类中的某一个属性

十一、数据绑定容错处理

  1. 方式一:直接通过ngIf去判断 存在的时候 再往下取值
  2. 方式二:使用?.

十二、引入全局样式的三种方式

  1. 在style.css文件中

@import “路径”

  1. 在index.html文件中
  2. angular.json文件

十三、指令(在angular中去操作DOM)

  1. 属性指令:修改现有元素的外观或行为 使用[]包围

  2. 结构指令:增加 或者删除 DOM节点以修改布局 使用*作为指令前缀

内置指令:

《1》*ngIf 根据条件渲染DOM节点或者移除DOM节点 值是boolean 《2》[hidden]
《3》*ngFor 遍历数据生成HTML结构


十四、自定义指令

通过自定义指令来操作DOM
创建自定义指令:ng g d 指令的路径以及名字


十五、angular管道

pipe 管道的作用是格式化组件模板数据

angular内置常用管道:

 // pipe的使用
  date = new Date();
  money = 33333;

  test: {
    person: {
      name: '张三';
      age: 20;
    };
  };
<!-- pipe使用 -->
<br />
<br />
<div>{{ date | date: 'YYYY-MM-dd' }}</div>
<div>{{ money | currency: '¥' }}</div>
<div>{{ message | uppercase }}</div>
<br />
<br />
<br />
<div>
  <pre> {{ test | json }}</pre>
</div>
//自定义管道
ng g p

十六、组件通讯

  1. 向组件内部传递数据 name age

使用装饰器
@Input(“name”) myName : string = “”
@Input(“age”) myAge : string = “”

  1. 组件向外部传递数据(将子组件中的数据传递给父组件)
<app-person></app-person>

十七、angular中的组件生命周期函数

挂载 更新 卸载

《1》挂载阶段:

a:
constructor
angular在实例化组件类时执行 可以用来接受angular注入的服务实例对象
b:
ngOnInit
在首次接收到输入属性值后执行 在此处可以执行请求操作
//从组件外部传入到组件内部的
c:
ngAfterContentInit
当内容投影初始渲染完成后调用
d:
ngAfterViewInit
当组件视图渲染完成后调用
//代码解释

《2》更新阶段:

ngOnChanges
a:
当输入属性值发生变化时执行,初始设置时也会执行一次,顺序优于ngOninit
b:
不论多少输入属性同时变化,钩子函数只会执行一次,变化的值会同时存储在参数中
c:
参数类型为SimpleChanges,

《3》卸载阶段:

ngOnDestroy
当组件被销毁之前调用,用于清理操作
组件之间相互切换 A切换到B A被销毁
export class HomeComponent implements OnDestroy {
	ngOnDestro(){
		consle.log("组件被卸载")
	}
}

十八、依赖注入

解决代码之间耦合度高的问题

angular中的DI框架(依赖注入框架)
《1》Dependency 组件要依赖的实例对象 即服务实例对象
《2》Token 获取服务实例对象的标识
《3》Injector 注入器 负责创建维护服务类的实例对象并向组件中注入服务实例对象
《4》Provider 配置注入器的对象 指定创建服务实例对象的服务类和获取实例对象的标识

使用:
《1》从@angular/core中引入 ReflectiveInjector
《2》创建注入器对象 injector = ReflectiveInjector.resolveAndCreate()
《3》通过注入器对象的get方法来获得实例对象

服务实例对象为单例模式 注入器在创建服务实例后会对其进行缓存

不同的注入器返回不同的服务实例对象

父级下面的字级注入器

服务实例的查找类似函数作用域链 当前级别可以找到就使用当前的级别 当前级别找不到去父级中查找


十九、provider的使用

  1. 配置注入器的对象 指定了创建实例对象的服务类和访问服务实例对象的标识
const injector = ReflectiveInjector.resolveAndCreate([
{ provide: MailService , useClass : MailService }
})
//provide 获取实例对象的唯一标识
  1. 访问依赖对象的标识也可以是字符串类型
const injector = ReflectiveInjector.resolveAndCreate({
{ provide: mail , useClass : MailService }
})
const mailService  = injector.get("mail")
//好处:将实例对象和外部的引用建立了松耦合关系 外部通过标识获取实例对象 只要标识/字符串 保持不变 内部代码怎么变都不会影响到外部
1 使用注入器存储实例对象 
《2 对象
const injector = ReflectiveInjector.resolveAndCreate({
{
provide : "Config",
useValue: Object.freeze({
APIKEY:""
APISCRET:""
})
}
])
const Config = injector.get("Config")
//freeze 只是允许访问 不允许更改

二十、服务的创建与注入

//创建服务:
import {Injectable} from '@angular/core'

@Injectable({
provideIn:'root'
})

export class TestService { }

export class AppComponent {
 constructor (private testService : TestService) { }
}

二十一、服务的作用域

使用服务可以轻松实现跨模块跨组件共享数据
三种级别的注入器 放置的位置不同 作用的范围是不同的

《1》 根注入器

import {Injectable} from '@angular/core';

@Injectable({
provideIn:'root'
})
export class CardListService { }

《2》模块级别注入器

import {Injectable} from '@angular/core';
import {CarModule} from './car.module';

@Injectable({
provideIn:'CarModule'
}),

export class CardListService { }

两种实现的形式

import {CardListService} from './car-list.service';

@NgModule({
providers : [CarListService],
}
export class CarModule {}

《3》组件级别注入器

在组件级别注册服务 该组件及其子组件使用同一个服务实例对象

import {Component} from '@angular/core';
import {CarListService} from '../car-list.service.ts';

@Component({
selector : 'app-car-list',
templateUrl:'./car-list.component.html',
providers: [CarListService]
})

二十二、模板驱动表单用法

两种方式:

《1》模板驱动表单

表单的逻辑写在模板中 适合简单的表单类型

  1. 引入依赖模块FormsMoudle
import {FormsModule} from "@angular/forms"

@NgModule({
imports : [FormsModule],
)}
export class AppModule{}

  1. 将DOM表单转换为ngForm(方便使用一些angular中的一些方法)
<form #f="ngForm" (submit) = "onSubmit(f)"></form>
  1. 声明表单字段为ngModel
<form #f="ngForm" (submit) = "onSubmit(f)">
<input type = "text" name="username" ngModel/>
<button>提交</button>
</form>
  1. 获取表单字段的值
import {ngForm} from "@angular/forms"

export class AppComponent {
onSubmit(form: NgForm){
	console.log(form.value)
}
}
  1. 表单分组
    将有关联的表单项存储在一个对象中
<form #f="ngForm" (submit)="onSubmit(f)">
  <div ngModelGroup="user"><input type="text" name="username" ngModel /></div>
  <div ngModelGroup="contact"><input type="text" name="phone" ngModel /></div>
  <button>提交</button>
</form>
  onSubmit(form: NgForm) {
    console.log(form.value.user.username);
    console.log(form.value.contact.phone);
  }

表单验证

required 必填字段
minlength 字段最小长度
maxlength 字段最大长度
pattern 验证正则 例如:pattern=“\d” 匹配一个数值

<form #f="ngForm" (submit) = "onSubmit(f)">
<input type="text" name="username" ngModel required pattern="\d"/>
<button>提交</button>
</form>

export class AppComponent{
	onSubmit(form:NgForm){
	console.log(form.valid)
}
}
//表单整体未通过验证时禁用提交按钮
<button type="submit" [disable]="f.invalid">提交</button>
//在组件模板中显示表单项未通过时的错误信息
<form #f="ngForm" (submit) = "onSubmit(f)">
<input #username="ngModel"/>
<div *ngIf = "username.touched && !username.valid && username.errors">
<div *ngIf="username.errors.required">请填写用户名</div>
<div *ngIf = "username.errors.pattern">不符合正则规则</div>
</div>
</form>

指定表单项未通过时验证的样式

input.ng-touched.ng-invalid{
border : 2px solid red;
}

//最大的字符输入校验 新版本的angular已经支持 不需要校验

字符数目也可以动态获取

《2》模型驱动表单

<1>表单的逻辑写在组件类中,对验证逻辑拥有更多的控制权 适合复杂的表单的类型

<2>在模型驱动表单中 表单字段需要是FormControl类的实例 实例对象可以验证表单字段中的值 值是否被修改过等等

<3>一组表单字段构成整个表单 整个表单需要是FormGroup类的实例 它可以对表单进行整体验证

  1. FormControl:表单组中的一个表单项
  2. FormGroup:表单组,表单至少是一个FormGroup
  3. FormArray:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray中有一项没有通过 整体不通过

//前面是概念 下面是一些用法
《1》引入ReactiveFormsModle

import {ReactiveFormsModule} from "@angular/forms"

@NgModule({
imports:{ReactiveFormsModule}
})
 export class AppModule{}

《2》在组件类中创建FormGroup表单控制对象

import {ReactiveFormsModule} from "@angular/forms"
 export class AppComponent{
contactForm : FormGroup = new FormGroup({
name: new FormControl(),
phone : new FormControl()
}}

《3》关联组件模板中的表单

<form [formGroup] = "contactForm" (submit)="onSubmit()">
<input type="text" formControlName="name"/>
<input type="text" formControlName="phone"/>
<button>提交</button>
</form>

《4》获取表单的值

export class AppComponent{
onSubmit(){
console.log(this.contactForm.value)
}

《5》设置表单默认值

contactForm : FormGroup = new FormGroup({
name: new FormControl("默认值"),
phone : new FormControl(12345678910)
}

《6》表单分组

contactForm : FormGroup = new FormGroup({
fullName: new FormGroup({
 firstName: new FormControl(),
lastName : new FormControl()
}),
phone : new FormControl()
}<form [formGroup] = "contactForm" (submit)="onSubmit()">
<div formGroupName="fullName">
<input type="text" formControlName="firstName"/>
<input type="text" formControlName="lastName"/>
</div>
<input type="text" formControlName="phone"/>
<button>提交</button>
</form>

onSubmit(){
console.log(this.contactForm.value.fullName.firstName)
console.log(this.contactForm.get(["fullName","firstName"])?.value)
}


二十三、使用formArray动态创建表单

需求:在页面中默认显示一组联系方式,通过点击按钮可以添加更多联系方式组

一步步去绑定

// 经典


二十四、内置表单验证器(模型驱动)

《1》使用内置验证器提供的验证规则验证表单字段

import {FormControl,FormGroup,Validators } from "@angular/forms"

contactForm : FormGroup = new FormGroup({
name : new FormControl("默认值",[
Validators.required,
Validators.minLength(2)
])
})

《2》获取整体表单是否通过

onSubmit(){
console.log(this.contactForm.valid)
}
//表单整体验证未通过时禁用表单按钮
<button [disabled] = "contactForm.invalid">提交</button>

《3》在组件模板中显示未验证通过时的错误信息

get name (){
return this.contactForm.get("name")
}
//在组件类中 添加了一个name属性 用于获取name
和模板驱动类似

自定义同步表单验证器(模型驱动)

《1》自定义验证器的类型是TypeScript类——新建一个ts文件 写类
《2》类中包含的具体方法 验证方法必须为静态方法
《3》验证方法中有一个参数control 类型为AbstractControl 其实就是FormControl类的实例对象的类型
《4》如果验证成功,返回null
《5》如果验证失败 返回对象 对象中的属性即为验证标识 值为true 标识该项验证失败
《6》验证方法的返回值为ValidationErrors | null

import {AbstractControl,ValidationErrors} from "angular/forms"

export class NameValidators {
static cannotContaomSpace (control:AbstractControl) : ValidationErrors | null{
//验证未通过
if(/\s/.test(control.value))  return {cannotContaomSpace:true}
//验证通过
return null
}
}

import {NameValidators} from "./Name.validators"

contactForm: FormGroup = new FormGroup({
name : new FormControl("",[
Validators.required,
NameValidators.cannotContaomSpace
])
})

<div *ngIf="name.touched &&name.invalid &&name.errors"></div>

自定义异步表单验证器(模型驱动)
当用户在文本框输入了内容 离开焦点时 看输入的内容是否是唯一的

//比如说可以向服务器端发送请求来证实这个事情
static shouldBeUnique(
control: AbstractControl
):Promise<ValidationErrors | null>{
return new Promise(function (resolve)){
setTimeout(function(){
if(control.value === "admin"){
resolve({shouldBeUnique:true})
}else{
resolve(null)
}
},2000)
}
}

在name内的数组写的验证规则都是同步的

name : new FormControl("",[
Validators.required,
NameValidators.cannotContaomSpace
])

还可以加第三个参数

name : new FormControl("",[
Validators.required,
NameValidators.cannotContaomSpace
],NameValidators.shouldBeUnique)

在验证的过程中给用户一些提示 显示一些状态信息
在验证的过程中会有一个字段pending 为true

<div *ngIf="name.errors.shouldBeUnique">名字已经存在</div>
<div *ngIf="name.pending">正在验证...</div>

二十五、FormBuilder

创建表单的快捷方式

  1. this.fb.control:表单项
  2. this.fb.group:表单组 表单至少是一个FormGroup
  3. this.fb.array: 用于复杂表单 可以动态添加表单项或表单组 在表单验证时,FormArray中有一项没通过 整体没通过
import {FormBuilder ,FormGroup, Validators} from "@angular/forms"

export class AppComponent{
	contactFrom : FormGroup
	constructor(private fb : FormBuilder){
	  this.contactForm = this.fb.group({
	     fullName: this.fb.group({
		firstName:["",[Validators.required]],
		lastName: [""]
}),
phone:[]
})
}
}

//可以将group和array中的内容给替换掉 数组是一种简写的形式
//表单组里面包含表单项
this.fb.group
this.fb.array

组件模板:

<form [formGroup]="contactForm" (submit)="onSubmit()">
<div formGroupName="fullName">
	<input type="text" formControlName="firstName"/>
	<input type="text" formControlName="lastName"/>
</div>
<button>提交</button>
</form>

//练习

  1. 获取一组复选框中选中的值
<form [formGroup]="form" (submit)="onSubmit()">
	<label *ngFor="let item of Data">
	   <input type="checkbox" [value]="item.value" (change)="onChange($event)"/>
		{{item.name}}
	</label>
<button>提交</button>
</form>

import {Component} from "@angular/core"
import {FormArray,FormBuilder,FormGroup} from "@angular/forms"
interface Data{
	name:string
	value:string
}
@Component({
selector :"app-checkbox",
templateUrl:"./checkbox.component.html",
styles:[]
})
export class CheckboxComponent{
  Data : Array<Data>=[
     {name:"Pear",value:"pear"},
     {name:"Plum",value:"plum"},
     {name:"Kiwi",value:"kiwi"},
     {name:"Apple",value:"apple"},
     {name:"Lime",value:"lime"}
]
form:FormGroup
constructor(private fb: FormBuilder){
this.form = this.fb.group({
checkArray : this.fb.array([])
})
} 
}

//name存储的是给用户看的数据 
//value存储的是在提交表单时 需要获取到的数据

  1. 提交表单时 获取用户选择的性别
    在组件类中
export class RadioComponent implements OnInit{
  form:FormGroup = new FormGroup({
	gender :  new FormControl()
})

onSubmit(){
   console.log(this.form,value)
}
}

在组件模板中

<form [formGroup]="form" (submit)="onSubmit()">
<input type="radio" value="male" formControlName="gender">male
<input type="radio" value="female" formControlName="gender">female
<button>提交</button>
</form>

模型驱动表单 常用方法:

《1》patchValue:设置表单控件的值(可以设置全部,也可以设置其中某一个 其他不受影响)
《2》setValue:设置表单控件的值(设置全部 不能排除任何一个)
《3》valueChanges:当表单控件的值发生变化时被触发的事件
《4》reset 表单内容置空


二十六、路由的基本使用

在angular中 路由是以模块为单位的 每个模块都可以有自己的路由

快速上手:

  1. 创建页面组件 Layout组件以及Navigation组件 供路由使用

《1》创建首页页面组件 ng g c pages/home
《2》创建关于我们页面组件 ng g c pages/about
《3》创建布局组件 ng g c pages/layout
《4》创建导航组件 nng g c pages/navigation

  1. 创建路由规则
app.module.ts
import {Routes} from "@angular/routers"

const routes:Routes = [
{
path:"home",
component:HomeComponent
},
{
path:"about",
component:AboutComponent
}
]

  1. 引入路由模块并启动
//app.module.ts
import {RouterModule,Routes} from "@angular/router"

//用来启动路由
@NgModule({
imports: [RouterModule.forRoot(routes,{useHash:true})],
})
export class AppModule{}

  1. 添加路由插座
//路由插座即为占位组件 匹配到的路由组件将会显示在这个地方
<router-outlet></router-outlet>
  1. 在导航组件中定义链接
<a routerLink="/home">首页</a>
<a routerLink="/about">关于我们</a>

匹配规则:
{
path:“”,
//重定向
redirectTo:“home”,
//完全匹配斜杠时 才重定向
pathMatch:“full”
}

路由默认是从上到下进行匹配的 什么都没有匹配上
404页面:
{
path:“**”,
component:NotFoundComponent
}


二十七、路由传递参数的两种形式

《1》查询参数

<a routerLink="/about" [queryParams]="{name:'Kitty'}">关于我们</a>

import {ActivateRoute} from "@angular/router"

export class AboutComponent implements OnInit{
  constructor(private route:ActivateRoute){}
   
ngOnInit(): void{
this.route.queryParaMap.subscribe(query=>{
query.get("name")
})
}
}

《2》动态参数

const routes:Routes = [
{
path:"home",
component:HomeComponent
},
{
path:"about/:name",
component:AboutComponent
}
]

<a [routerLink]="['/about','zhangsan']">关于我们</a>

方法变成:

this.route.paramMap.subscribe(query=>{
query.get("name")
})

二十八、路由嵌套(定义子孙级路由)

指的是如何定义子级路由

const routes:Routes = [
{
 path:"about",
component:AboutComponent,
children:[
{
path:"introduce",
component:IntroduceComponent
},
{
path:"history",
component:HistoryComponent
}
]
}
]

//about.component.html
<app-layout>
<p>about works!</p>
<a routerLinik="/about/introduce">公司简介</a>
<a routerLink="/about/history">发展历史</a>
<div>
	<router-outlet></router-outlet>
</div>
<app-layout>


二十九、路由命名插座

//新需求 在进入news这个一级路由时 同时显示两个二级的内容 并且
将子级路由组件显示到不同的路由插座中
{
 path:"about",
component:AboutComponent,
children:[
{
path:"introduce",
component:IntroduceComponent,
outlet:"left"
},
{
path:"history",
component:HistoryComponent,
outlet:"right"
}
]
}
//about.component.html
<app-layout>
<p>about works!</p>
<div>
<router-outlet name="left"></router-outlet>
<router-outlet name="right"></router-outlet>
</div>
<app-layout>

<a
[routerLink]="[
'about',{
outlets:{
 left :['introduce'],
right:['history']
}
}
]"
>
关于我们
</a>


三十、导航路由(如何实现javascript的方法去实现页面之间的跳转)

//app.component.html
<button (click)="jump">跳转到发展历史</button>

//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-module配合使用

ng g m appRouting --flat=true

三十二、实现路由模块懒加载(按需加载)

用户在首次使用应用时 只是去请求根模块,其他的模块等用户访问的时候再去加载
路由懒加载是以模块为单位的
《1》创建用户模块 ng g m user --routing=true ——并创建该模块的路由模块
《2》创建登录页面组件 ng g c user/pages/login
《3》创建注册页面组件 ng g c user/pages/register
《4》配置用户模块的路由规则

import {NgModule} from "@angular/core"
import {Routes,RouterModule} from "@angular/router"
import {LoginComponent} from "./pages/login/login.component"
import {RegisterComponent} from "./pages/register/register.component"

const  routes Routes = [
{
path : "login",
component:LoginComponent
},
{
path:"register",
component:RegisterComponent
}
]

@NgModule({
imports : [RouterModule.forChild(routes)],
exports : [RouterModule]
})

export class UserRoutingModule{}

//根模块启动路由 RouterModule.forRoot
//其他模块启用路由 RouterModule.forChild

《5》将用户路由模块关联到主路由模块

//app-routing.module.ts
const routes : Routes = [
{
path : "user",
loadChildren:() =>import("./user/user.module").then(m=>m.UserModule)
}
]
//loadChildren进行懒加载

《6》在导航组件中添加访问来链接

<a routerLink="/user/login">登录</a>
<a routerLink="/user/register">注册</a>

三十三、路由守卫 CanActivate(用来保护路由组件)

路由守卫会告诉路由是否允许导航到请求的路由

路由守卫方法可以返回boolean或Observable<boolean>或Promise<boolean>它们在将来的某个时间点解析为布尔值

CanActivate
检查用户是否可以访问某一个路由
CanActivate为接口 路由守卫类要实现该接口 该接口规定类中需要有canActivate方法 方法决定是否允许访问目标路由

路由可以应用多个守卫 所有守卫方法都允许 路由才被允许访问 有一个守卫方法不允许 则路由不允许被访问

创建路由守卫 ng g guard guards/auth
//跳转
return this.router.createUrlTree(["/home"])
//允许
return true
//不允许
return false
//保存 需要跳转到的目标信息
route : ActivatedRouteSnapshot 
//
state : RouterStateSnapshot

三十四、路由守卫CanActiveChild

检查用户是否可以访问某个子路由
创建路由守卫 ng g guard guards/admin 选择CanActiveChild
需要将箭头移动到这个选项并且敲击空格确认选择

在父级身上进行绑定
path:"about",
component: AboutComponent
canActiveChild: [AdminGuard]


三十五、CanDeactivate

检查用户是否可以退出路由 比如用户在表单中输入的内容没有保存 用户又要离开路由 此时可以调用该守卫提示用户


三十六、路由守卫 Resolve

允许在进入路由之前先获取数据,待数据获取完成之后再进入路由

ng g resolver <name>

最直接的感受就是 当网络比较拥挤时,先获取内容在进入 防止进去出现空白界面 用户看到的是空白 认为是出bug;


三十七、RXJS

是一个基于异步编程的JavaScript库 目标是使编写异步和基于回调的代码更容易

Angular深度集成了TypeScript 和RxJs
服务 表单 事件 全局状态管理 异步请求 ...

import {Observable} from "rxjs"

const observable = new Observable(function (observer){
	setTimeout(function(){
		observer.next({
	name:"张三"
})
},2000)
})

const observer = {
	next : function (value){
	console.log(value)
}
}

observable.subscribe(observer)

三十八、可观察对象

《1》在Observable对象内部可以多次调用next方法向外发送数据

import { Observable } from 'rxjs';

const observable = new Observable(function (observer) {
  let index = 0;
  setInterval(function () {
    observer.next(index++);
  }, 1000);
});

const observer = {
  next: function (value) {
    console.log(value);
  },
};

observable.subscribe(observer);

《2》当所有数据发送完成之后,可以调用complete方法终止数据发送

import { Observable } from 'rxjs';

const observable = new Observable(function (observer) {
  let index = 0;
  let timer = setInterval(function () {
    observer.next(index++);
    if (index == 3) {
      observer.complete();
      clearInterval(timer);
    }
  }, 1000);
});

const observer = {
  next: function (value) {
    console.log(value);
  },
  complete: function () {
    console.log('终止了');
  },
};

observable.subscribe(observer);

《3》当Observable内部逻辑发生错误时 可以调用error方法将失败信息发送给订阅者 Observable终止

import { Observable } from 'rxjs';

const observable = new Observable(function (observer) {
  let index = 0;
  let timer = setInterval(function () {
    observer.next(index++);
    if (index == 3) {
      // observer.complete();
      console.log('错误信息');
      clearInterval(timer);
    }
  }, 1000);
});

const observer = {
  next: function (value) {
    console.log(value);
  },
  complete: function () {
    console.log('终止了');
  },
  error: function (error) {
    console.log(error);
  },
};

observable.subscribe(observer);

说明 当调用complete或者error方法后是不能够再调用next方法

《4》可观察对象是惰性的 只有被订阅后才会执行

//observable.subscribe(observer);

《5》可观察对象可以有n多个订阅者observer 每次被订阅时都会得到执行


三十九、使用Subject构造函数创建可观察对象(在订阅后不会立即执行广播)

用于创建空的可观察对象 在订阅后不会立即执行 next方法可以在可观察对象外部调用

import { Observable, 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 demoSubject = new BehaviorSubject('默认值');
demoSubject.subscribe({
  next: function (value) {
    console.log(value);
  },
});
demoSubject.next('显示');


四十一、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);
    },
  });
});

hello 1
hello 2
hello 1
hello 2


四十二、数据流 操作符

《1》数据流:从可观察对象内部输出的数据就是数据流 可观察对象内部可以向外部源源不断的输出数据
《2》操作符:用于操作数据流 可以对数据流进行转换 过滤等操作

辅助方法

//调用某些方法之后 也会返回一些可观察者对象
range(start,length) 调用方法后返回observable对象 被订阅后会发出指定范围的数值

import { range } from 'rxjs';

range(1, 10).subscribe((n) => console.log(n));
方法内部并不是一次发出length个数值 而是发送了length次 每次发送一个数值 就是说内部调用了length次
pipe里面放置操作符
操作符:
map:对数据流进行转换 基于原有值进行转换
import { map, range } from 'rxjs';

range(1, 10)
  .pipe(map((n) => n * 10))
  .subscribe((n) => console.log(n));


四十三、辅助方法

《1》from:返回的是可观察对象

import { from } from 'rxjs';

// from(['a', 'b', 'c']).subscribe(console.log);

function p() {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve({ a: 1 });
    }, 2000);
  });
}

from(p()).subscribe(console.log);

《2》forkJoin:forkJoin是Rx版本的Promise.all() 即表示等到所有的Observable都完成之后 才一次性返回值

需求 :发出两个请求 当两个请求都发送并且拿到结果之后 再一次性地把所有的数据都拿到
同时发送 同时拿到结果

《3》辅助方法fromEvent和操作符pluck 辅助方法返回是一个Observable对象

将事件转换为可观察对象 Observable

pluck 获取对象流对象中的属性值

《4》辅助方法interval和操作符switchMap

interval:每隔一段时间发出一个数值 数值递增
import { interval } from 'rxjs';

interval(1000).subscribe((n) => console.log(n));

switchMap
切换可观察对象
 <button id="btn">点击</button>

import { fromEvent, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const button = document.getElementById('btn');
fromEvent(button, 'click')
  .pipe(switchMap((event) => interval(1000)))
  .subscribe(console.log);

再次点击当前的button时 会重新发出数据流


四十四、操作符

take takeWhile takeUntil
take:获取数据流中的前几个
import { range, take } from 'rxjs';

range(1, 10).pipe(take(5)).subscribe(console.log);

takeWhile: 根据条件从数据源前面开始获取

import { range } from 'rxjs';
import { takeWhile } from 'rxjs/operators';

range(1, 10)
  .pipe(takeWhile((n) => n < 6))
  .subscribe(console.log);

takeUntil:接收可观察对象 当可观察对象发出值时 终止主数据源

import { fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
const button = document.getElementById('btn');

fromEvent(document, 'mousemove')
  .pipe(takeUntil(fromEvent(button, 'click')))
  .subscribe(console.log);


四十五、操作符节流和防抖

节流 可观察对象高频率次向外部发出数据流,通过throttleTime限制在规定时间内每次只向订阅者传递一次数据流
import { fromEvent } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
const button = document.getElementById('btn');

fromEvent(document, 'click').pipe(throttleTime(4000)).subscribe(console.log);

debounceTime
防抖 触发高频事件 只响应最后一次
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
const button = document.getElementById('btn');

fromEvent(document, 'click').pipe(debounceTime(1000)).subscribe(console.log);


四十六、辅助方法——of 方法返回的是一个Observable

of  将参数列表作为数据流返回
of(
  'a',
  1,
  true,
  {
    name: 'Lisi',
    age: 34,
  },
  []
).subscribe(console.log); 

操作符——distinctUntilChanged
检测数据源当前发出的数据流是否和上次发出的相同 如相同 跳过 不相同 发出
of(1, 1, 2, 2, 3, 3, 4).pipe(distinctUntilChanged()).subscribe(console.log);


四十七、案例(元素拖拽)


四十八、案例(搜索案例)

用户在文本框输入内容回车后 发送到服务器中进行检索
<input id="search" type="text" placeholder="请输入搜索内容..."/>

const search = document.getElementById("search")

fromEvent(search,"keyup")
.pipe(
debounceTime(1000),
map(event=>event.target.value),
distinctUntilChanged(),
switchMap(keyword=>
from(
 axios.get(`https://jsonplaceholder.typicode.com/posts?q=${keyword}`)
)
)
).subscribe(console.log)


四十九、串联请求

先获取token 再根据token获取用户信息
<button id="btn">获取用户信息</button>
点击按钮后 获取用户信息
会执行两个操作 先获取token 其次根据token获取用户信息

concatMap 合并可观察对象
import axios from "axios"
import {from , fromEvent} from "rxjs"
import {pluck,concatMap} from "rxjs/operators"

const button = document.getElemnetById("btn")

fromEvent(button,"click")
.pipe(
concatMap(event=>from(axios.get("http://localhost:3005/token")).pipe(pluck("data","token"))),
concatMap(token=>from(axios.get("http://localhost:3005/userInfo")).pipe(pluck("data")))
).subscribe(console.log)


五十、HttpClientModule

该模块用于发送Http请求 用于发送请求的方法都返回Observable对象

《1》引入HttpClientModule模块

//app.module.ts
import {httpClientModule} from "@angular/common/http"
imports:[
httpClientModule
]

《2》注入HttpClient服务实例对象,用于发送请求

//app.component.ts
import {HttpClient} from "@angular/common/http"

export class AppComponent {
 constructor(private http:HttpClient){}
}

《3》发送请求

important {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")
}
}

《4》请求方法

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"
}
)


五十二、在发送数据的时候 在请求头中添加字段

请求头字段的创建需要使用HttpHeaders类 在类实例对象下面有各种操作请求头的方法

let headers = new HttpHeaders ({test : "Hello"})	

五十三、设置响应内容

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>

《1》请求拦截:

@Injectable
export class AuthInterceptor implements HttpInterceptor{
   constructor(){}
	//拦截方法
    intercept(
//unknown 指定请求体(body)的类型
request:HttpRequest<unknown>
next: HttpHandler
//unknown 指定请求体(body)的类型
): Observable<HttpEvent<unknown>>{
  setHeaders:{
	Authorization:"Bearer xxxxxxx"
}
})
//通过回调函数将修改后的请求头回传给应用
return next.handle(req)
}
}

《2》响应拦截

@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)))
)
}
}

《3》拦截器注入

import {AuthInterceptor} from "./auth.interceptor"
import {HTTP_INTERCEPTORS} from "@angular/common/http"

@NgModule({
providers:[
{
   provide:HTTP_INTERCEPTORS,
   useClass:AuthInterceptor,
   multi:true
}
]
})


五十五、Angular Proxy (代理)

开发环境和接口不在同一个域中
1.在项目的根目录下创建 proxy.conf.json文件并加入如下代码

{
"/api/*":{
"target":"http://localhost:3070",
"secure":false,
"changeOrigin":true
}
}

《1》/api/* 在应用中发出的以/api开头的请求走此代理
《2》target 服务器端URL
《3》secure 如果服务器端URL的协议是https 此项需要为true
《4》changeOrigin 如果服务端不是localhost 此项需要为true

2.指定proxy配置文件(方式一)

"scripts":{
"start":"ng serve --proxy-config proxy.conf.json",
}
3.指定proxy配置文件(方式二)
"serve":{
"options":{
"proxyConfig":"proxy.conf.json"
},


五十六、NgRx

NgRx是Angular应用中实现全局状态管理的Redux架构解决方案

1.@ngrx/store:全局状态管理模块
2.@ngrx/effects:处理副作用
3.@ngrx/store-devtools:浏览器调式工具,需要依赖Redux Devtools Extension
4.@ngrx/schematics:命令行工具,快速生成NgRx文件
5.@ngrx/entity:提高开发者在Reducer中操作数据的效率
6.@ngrx/router-store:将路由状态同步到全局Store


五十七、NgRx基本

1》下载NgRx
《2》配置NgRxCLI
《3》创建Store 用来存储Reducer返回的状态
//store 中存储的状态类型接口
export interface AppState{}
//状态名字和reducer的对应的关系
export const reducers: ActionReducerMap<AppState>={}4》创建Action
//store 中存储的状态类型接口
//状态名字和reducer的对应的关系5》创建Reducer
《6》创建Select——用来从Store中获取数据
ng g selector store/selectors/counter --skipTests
忽略测试文件
import {createFeatureSelector,createSelector} from "ngrx/store"
import {counterFeatureKey,State} from "../reducers/counter.reducer"
import {AppState} from ".."

export const selectCounter = createFeatureSelector<AppState,State>(counterFeatureKey)
export const selectCount = createSelector(selectCounter,state=>state.count)

五十八、Action Payload

1.在组件中使用dispatch触发Action时传递参数 参数最终会被放置在Action对象中
this.store.dispatch(increment({count : 5}))
2.在创建Action Creator函数时 获取参数并指定参数类型
import {createAction , props } from "ngrx/store"
export const increment = createAction ("increment",props<{count: number}>())

export declare function props <P extends object>():Props<p>;

3.在Reducer中通过Action对象获取参数
export const reducer = createReducer(
initialState,
on(increment,(state,action)=>({
count : state.count + action.count
}))
)

五十九、MetaReducer(中间件)

metaReducer是Action->Reducer 之间的钩子 允许开发者对Action进行预处理(在普通Reducer函数调用之前调用)

function debug(reducer:ActionReducer<any>):ActionReducer<any>{
return function (state,action){
return reducer(state,action)
}
}

export const metaReducers: MetaReducer<AppState>[] = !environment.production
? [debug]
: [ ]

六十、Effect(处理副作用)

需求:在页面中新增一个按钮 点击按钮后延迟一秒让数值增加

1.在组件模板中新增一个用于异步数值增加的按钮,在按钮点击后执行increment_async方法
<button (click)="increment_as()">async</button>
2.在组件类中新增increment_as方法 并在方法中触发执行异步操作的Action
increment_async(){
this.store.dispatch(increment_async())
}
3.在Action文件中新增执行异步操作的Action
export const increment_async = createAction("increment_async")
4.创建Effect 接收Action并执  行副作用 继续触发Action
ng g effect store/effects/counter --

六十一、Entity

翻译是实体的意思 实体就是集合中的一条数据
NgRx中提供了实体适配器对象 在实体适配器对象下面提供了各种操作集合中实体的方法 目的就是提高开发者操作实体的效率

核心:
1.EntityState:实体类型接口——为了约束我们的使用
{
ids: [1,2],
entities:{
1: {id:1,title:"Hello Angular"}
2: {id:2,title:"Hello NgRx"}
}
}
export interface State extends EntityState<Todo>{}
2. createEntityAdapter:创建实体适配器对象
3.EntityAdapter:实体适配器对象类型接口
export const adapter : EntityAdapter<Todo> = createEntityAdapter<Todo>()
//获取初始状态 可以传递对象参数 也可以不传递
// {ids: [],entities:{}}
export const initialState :State = adapter.getInitialState()

选择器:
//selectTotal 获取数据条数
//selectAll  获取所有数据  以数组形式呈现
//selectEntities 获取实体集合 以字典形式呈现
//selectIds 获取id集合 以数组形式呈现

const {selectIds,selectEntities,selectAll,selectTotal}=adapter.getSelectors()

export const selectTodo = createFeatureSelector<AppState,State>(todoFeatureKey)
export const selectTodos = createSelector(selectTodo,selectAll)


六十二、Router Store

同步路由状态
1.引入模块
import {StoreRouterConnectingModule} from "ngrx/router-store"

@NgModule({
	imports:[
	   StoreRouterConnectingModule.forRoot()
]
})
export class AppModule{}
2.将路由状态集成到Store
import * as fromRouter from "@ngrx/router-store"

export interface AppState{
   router : fromRouter.RouterReducerState
}

export const reducers: ActionReducerMap<AppState>={
	router : fromRouter.routerReducer
}
3.创建获取路由状态的Selector


六十三、动画

《1》
状态:状态表示的是要进行运动的元素在运动的不同时期所呈现的样式
《2》
状态的种类:在angular中 有三种类型的状态 分别为:void * custom
void:当元素在内存中创建好蛋尚未被添加到DOM中或将元素从DOM中删除时会发生此状态

  • : 元素被插入到DOM树之后的状态,或者是已经在DOM树中的元素的状态 也叫默认状态
    custom : 自定义状态 元素默认就在页面之中,从一个状态运动到另一个状态 比如面板的折叠和展开
    《3》进出场动画
    进场动画:是指元素被创建后以动画的形式出现在用户面前 进场动画的状态用void => * 表示 别名为:enter
    出场动画:是指元素在被删除前执行的一段告别动画 出场动画的状态用*=>void 别名为:leave

六十四、入手

1》在使用动画之前 需要引入动画模块 即BrowserAnimationModule
import {BrowserAnimationModule} from "@angular/platform-browser/animations"

@NgModule({
	imports : [BrowserAnimationModule],
})

export class AppModule
《2》默认代码解析 todo之前删除任务和添加任务
《3》创建动画
1. trigger方法用于创建动画 指定动画名称
2. transition 方法用于指定动画的状态 出场动画或者入场动画 或者自定义状态动画
3. style 方法用于设置元素在不同的状态下所对应的样式
4. animate方法用于设置运动参数 比如动画运动时间 延迟事件 运动形式


六十五、定义动画时的两个注意事项

《1》入场动画中可以不指定元素的默认状态 Angular会将void状态清空作为默认状态
《2》要设置动画的运动参数 需要将animate方法的一个参数更改为字符串类型


六十六、定义帧动画

关键帧动画使用keyframes方法定义


六十七、动画回调

angular提供了喝动画相关的两个回调函数 分别为动画开始执行时喝动画执行完成后
<li @slide (slide.start) = "start($event)" (@slide.done) = "done($event)"></li>

import {AnimationEvent} from "angular/animations"

start(event:AnimationEvent){
console.log(event)
}
done(event:AnimationEvent){
console.log(event)
}


六十八、创建可重用动画

将动画的定义放置在单独的文件中 方便多组件调用


你可能感兴趣的:(前端,angular.js,javascript,ecmascript)