Angular学习
课程链接
组件类:component.ts
组件模板:component.html
组件样式:component.scss
其它模块依赖当前的模块 前提 当前模块要导出该组件
场景:组件和组件之间可以共享的数据 angular不希望都写一遍 可以放置在服务中
服务类的实例对象 由angular帮忙创建 直接在组件类中通过construct的形参的方式
服务是单例模式
注意:将组件放在哪个模块的文件夹下 CLI会自动地识别 组件是属于哪一个module
数据绑定:
就是将组件类中的数据显示在组件模板中
即组件类和组件模板进行绑定
数据驱动DOM
组件中可以套用组件
差值表达式的使用 在差值表达式中不能够写if else的 显示字符串时 需要加上单引号
<!-- 数据展示 -->
<div>
{{ message }}
</div>
<div>
{{ getInfo() }}
</div>
<div [innerHTML]="htmlString"></div>
<div>{{ 1 === 1 ? '相等' : '不相等' }}</div>
<div>{{ 'Angular' }}</div>```
DOM对象属性绑定 HTML标记属性和DOM都有 使用DOM对象属性绑定=“”
HTML标记属性绑定 attr.绑定的是自己的标记属性 DOM中没有的 不加这个字段会报错
<!-- 属性绑定的两种形式 -->
<!-- 1 HTML标记属性绑定 -->
<div [attr.data-test]="title"></div>
绑定类名:
<!-- 为元素添加类名 不会改变原有的类 -->
<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()" />
模板变量 ?. 对于程序的一种保护 当后者不存在时 便不会去访问
@import “路径”
属性指令:修改现有元素的外观或行为 使用[]包围
结构指令:增加 或者删除 DOM节点以修改布局 使用*作为指令前缀
内置指令:
《1》*ngIf 根据条件渲染DOM节点或者移除DOM节点 值是boolean 《2》[hidden]
《3》*ngFor 遍历数据生成HTML结构
通过自定义指令来操作DOM
创建自定义指令:ng g d 指令的路径以及名字
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
使用装饰器
@Input(“name”) myName : string = “”
@Input(“age”) myAge : string = “”
<app-person></app-person>
挂载 更新 卸载
a:
constructor
angular在实例化组件类时执行 可以用来接受angular注入的服务实例对象
b:
ngOnInit
在首次接收到输入属性值后执行 在此处可以执行请求操作
//从组件外部传入到组件内部的
c:
ngAfterContentInit
当内容投影初始渲染完成后调用
d:
ngAfterViewInit
当组件视图渲染完成后调用
//代码解释
ngOnChanges
a:
当输入属性值发生变化时执行,初始设置时也会执行一次,顺序优于ngOninit
b:
不论多少输入属性同时变化,钩子函数只会执行一次,变化的值会同时存储在参数中
c:
参数类型为SimpleChanges,
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方法来获得实例对象
服务实例对象为单例模式 注入器在创建服务实例后会对其进行缓存
不同的注入器返回不同的服务实例对象
父级下面的字级注入器
服务实例的查找类似函数作用域链 当前级别可以找到就使用当前的级别 当前级别找不到去父级中查找
const injector = ReflectiveInjector.resolveAndCreate([
{ provide: MailService , useClass : MailService }
})
//provide 获取实例对象的唯一标识
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) { }
}
使用服务可以轻松实现跨模块跨组件共享数据
三种级别的注入器 放置的位置不同 作用的范围是不同的
import {Injectable} from '@angular/core';
@Injectable({
provideIn:'root'
})
export class CardListService { }
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 {}
在组件级别注册服务 该组件及其子组件使用同一个服务实例对象
import {Component} from '@angular/core';
import {CarListService} from '../car-list.service.ts';
@Component({
selector : 'app-car-list',
templateUrl:'./car-list.component.html',
providers: [CarListService]
})
两种方式:
表单的逻辑写在模板中 适合简单的表单类型
import {FormsModule} from "@angular/forms"
@NgModule({
imports : [FormsModule],
)}
export class AppModule{}
<form #f="ngForm" (submit) = "onSubmit(f)"></form>
<form #f="ngForm" (submit) = "onSubmit(f)">
<input type = "text" name="username" ngModel/>
<button>提交</button>
</form>
import {ngForm} from "@angular/forms"
export class AppComponent {
onSubmit(form: NgForm){
console.log(form.value)
}
}
<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已经支持 不需要校验
字符数目也可以动态获取
<1>表单的逻辑写在组件类中,对验证逻辑拥有更多的控制权 适合复杂的表单的类型
<2>在模型驱动表单中 表单字段需要是FormControl类的实例 实例对象可以验证表单字段中的值 值是否被修改过等等
<3>一组表单字段构成整个表单 整个表单需要是FormGroup类的实例 它可以对表单进行整体验证
//前面是概念 下面是一些用法
《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)
}
需求:在页面中默认显示一组联系方式,通过点击按钮可以添加更多联系方式组
一步步去绑定
// 经典
《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>
创建表单的快捷方式
- this.fb.control:表单项
- this.fb.group:表单组 表单至少是一个FormGroup
- 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>
//练习
<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存储的是在提交表单时 需要获取到的数据
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》创建首页页面组件 ng g c pages/home
《2》创建关于我们页面组件 ng g c pages/about
《3》创建布局组件 ng g c pages/layout
《4》创建导航组件 nng g c pages/navigation
app.module.ts
import {Routes} from "@angular/routers"
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”
}路由默认是从上到下进行匹配的 什么都没有匹配上
404页面:
{
path:“**”,
component:NotFoundComponent
}
<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")
})
}
}
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>
//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>
路由守卫会告诉路由是否允许导航到请求的路由
路由守卫方法可以返回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
检查用户是否可以访问某个子路由
创建路由守卫 ng g guard guards/admin 选择CanActiveChild
需要将箭头移动到这个选项并且敲击空格确认选择
在父级身上进行绑定
path:"about",
component: AboutComponent
canActiveChild: [AdminGuard]
检查用户是否可以退出路由 比如用户在表单中输入的内容没有保存 用户又要离开路由 此时可以调用该守卫提示用户
允许在进入路由之前先获取数据,待数据获取完成之后再进入路由
ng g resolver <name>
最直接的感受就是 当网络比较拥挤时,先获取内容在进入 防止进去出现空白界面 用户看到的是空白 认为是出bug;
是一个基于异步编程的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)
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);
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);
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方法
//observable.subscribe(observer);
用于创建空的可观察对象 在订阅后不会立即执行 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);
拥有Subject的全部功能 但是在创建Observable对象时 可以传入默认值 观察者订阅后可以直接拿到默认值
import { BehaviorSubject } from 'rxjs';
const demoSubject = new BehaviorSubject('默认值');
demoSubject.subscribe({
next: function (value) {
console.log(value);
},
});
demoSubject.next('显示');
功能类似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));
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);
需求 :发出两个请求 当两个请求都发送并且拿到结果之后 再一次性地把所有的数据都拿到
同时发送 同时拿到结果
将事件转换为可观察对象 Observable
pluck 获取对象流对象中的属性值
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 将参数列表作为数据流返回
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)
该模块用于发送Http请求 用于发送请求的方法都返回Observable对象
//app.module.ts
import {httpClientModule} from "@angular/common/http"
imports:[
httpClientModule
]
//app.component.ts
import {HttpClient} from "@angular/common/http"
export class AppComponent {
constructor(private http:HttpClient){}
}
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")
}
}
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>
@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)
}
}
@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
}
]
})
开发环境和接口不在同一个域中
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是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
《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)
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是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]
: [ ]
需求:在页面中新增一个按钮 点击按钮后延迟一秒让数值增加
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 --
翻译是实体的意思 实体就是集合中的一条数据
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)
同步路由状态
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中删除时会发生此状态
《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)
}
将动画的定义放置在单独的文件中 方便多组件调用