Angular+TS学习

宗涵蓄
2023-12-01

生成新项目命令行

ng new my-app

新建文件的时候如果要放在app里面,命令行后面要加–flat=true,比如

ng g m 文件名 --flat=true

进入项目文件,运行项目

ng serve --open

创建服务命令行

ng generate service 服务名

共享模块

创建模块

ng generate model 模块名称

创建模块内组件,运行如下命令来生成一个新组件:

ng generate component 组件名称

exports中导出需要使用的共享模块:

@NgModule({
  declarations: [
    LayoutComponent
  ],
  imports: [
    CommonModule
  ],
  exports:[LayoutComponent],
})

在终端中通过运行以下命令生成一个新服务:

ng generate service 服务名称

组件生命周期顺序

主要分为挂载阶段、更新阶段、卸载阶段
constructor,来初始化类。Angular中的组件就是基于class类实现的,在Angular中,constructor用于注入依赖。组件的构造函数会在所有的生命周期钩子之前被调用,它主要用于依赖注入或执行简单的数据初始化操作。
public: 公共的,被public修饰的类、方法、属性、可以跨类和跨包访问。

import { Component, ElementRef } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <p>Hello {{name}}</p>
  `,
})
export class AppComponent {
  name: string = '';

  constructor(public elementRef: ElementRef) {//使用构造注入的方式注入依赖对象
    // 执行初始化操作
    this.name = 'Semlinker'; 
  }
}

ngOnChanges():
当输入属性值发生变化时执行,初始设置时也会执行一次;
不论多少输入属性同时变化,钩子函数只会执行一次,,变化的值会同时存储在参数中,参数类型为SimpleChanges ,子属性类型为SimpleChange
对于基本数据类型来说,只要值发生变化就可以被检测到
钩子函数主要用于父子组件传值中。父组件给子组件传值的时候以及父组件改变传值数据的时候,会触发该钩子函数。首次调用一 定会发生在 ngOnInit()之前。

ngOnInit():

  • 在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,触发的生命周期函数。
  • 作用:在构造函数外部执行复杂的初始化。组件的构造应该既简洁安全。比如,你不应该在组件构造函数中获取数据。所以在ngOnInit阶段获取初始数据
    输入属性:比如@Input,从组件外部传递到组件内部的属性叫做输入属性,当输入属性接收完成之后会调用ngOnInit生命周期函数

ngDoCheck():
主要用于调试,不推荐使用变化频率太高。只要属性发生变化,不论是基本数据类型还是引用数据类型还是引用数据类型中的属性变化,都会执行

ngAfterContentInit():
组件渲染完成后触发的生命周期函数。内容投影初始渲染完成后调用

ngAfterContentChecked():
内容投影更新完成后执行。
在组件初始化完成后可做一些自定义操作。

ngAfterViewInit():
视图加载完成后调用,非常重要,一般用来进行dom操作。

ngAfterViewChecked():
组件视图更新完成后执行。
视图初始化完成后对视图自定义操作

ngOnDestroy():
在组件销毁的时候触发的,例如当销毁时将用户信息保存起来或做清理操作。

视图封装

@Component({
  selector: 'app-shitufengzhuang',
  template:`<h2>None</h2>`,
  styles:['h2{color:red}'],
  encapsulation: ViewEncapsulation.None,
  encapsulation: ViewEncapsulation.Emulated
}) 

Emulated 只应用于组件的视图,即使该组件中包含子组件,对子组件也没效果;None 样式全局应用

渲染器(Renderer)

使直接DOM访问安全,并且它是一个独立的平台。它修改DOM元素而不直接接触DOM。呈现程序是由一些方法组成的服务。它有助于操作DOM。

如何正确操作dom

angular2采用aot静态编译模式,这种形式下需要我们的模板类型必须是稳定和安全的,直接使用javascript和jquery语言是不稳定,因为他们的编译不会提前发现错误,所以angular2才会选择javascript的超集typescript语言(这种语言编译期间就能发现错误)。

所以要用到Renderer2和ElementRef

ElementRef: ElementRef是一个类,它包含所有本地的DOM元素

1.使用Render2

Render2 是angular中用于操作dom的,Angular做了封装,屏蔽底层差异,通用性更强。

Render2之指令用法
setStyle、removeStyle

  # 定义指令
   import { Directive, ElementRef, OnInit, Renderer2, HostListener } from '@angular/core';
   @Directive({ 
       selector: '[animate]' 
   })
   export class Animate  {
   注入
   constructor(private renderer: Renderer2, private el: ElementRef) {}
       
   @HostListener('click') 
   performTask() {
       let randomColor = "#"+((1<<24)*Math.random()|0).toString(16);
       使用
       this.renderer.setStyle(this.el.nativeElement, 'color', randomColor);
       this.renderer.setStyle(this.el.nativeElement, 'background-color', 'black');   
       this.renderer.removeStyle(this.el.nativeElement, 'color','red');   
   }

   }

2.使用ViewChild获取dom节点

利用ViewChild不仅可以获取dom节点,还能获取子组件里get方法获取子组件里的数据

//html
<div #box3>this is box3</div>
//ts
export class DomComponent implements ngAfterViewInit{
  //获取dom节点
  @ViewChild('box3') box3:any;
  constructor() { }
  ngAfterViewInit():void{
    console.log(this.box3.nativeElement.innerText);
  }
}

双向数据绑定

实现数据的双向绑定需要依赖该模块-FormsModule ,通过ngModel实现数据双向绑定

import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    FormsModule
  ]
})
export class AppModule {}
<input type="text" [(ngModel)]="usname">
<button (click)="setData()">该数据</button>
<button (click)="getData()">获取数据</button>
<div>{{usname}}</div>
 usname:string =''
 setData(){
    this.usname = '改名字'
  }
  getData(){
    console.log(this.usname)
  }

数据绑定的容错处理

当我们绑定的数据属性层级比较深,并且对象中的属性是可选的,就需要做容错处理。如果不做处理,当属性不存在时继续访问这个属性当中的属性angular就会报错
Interface typescript 中,接口就是为类型命名,为代码或第三方代码定义契约。
自己的理解就是定一个的数据格式可以使用接口,以此来约定类型
在typescript 中,为安全操作符

interface Task{
  person?:{
    name:string
  }
};
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  task:Task={
    person:{
      name:"aha"
    }
  }
}
<!-- 容错处理两种方式 -->
<div *ngIf="task.person">{{task.person.name}}</div>
<div>{{task.person?.name}}</div>

全局样式引入三种方式

//1.在style.css中
@import "/node_modules/bootstrap/dist/css/bootstrap.css";
//2.在index.html中
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
//3.在angular.json中(改动需重新启动项目,不会立即生效)
"styles": [
             "src/styles.css",
             "./node_modules/bootstrap/dist/css/bootstrap.css"
          ],

指令Directive

指令是angular提供操作dom的途径。分为属性指令和结构指令。
属性指令:用来修改现有元素的外观和行为,使用[]包裹。(比如修改元素高度宽度背景色等,不涉及增加、删除DOM元素)
结构指令:增加、删除DOM节点以修改布局,使用 * 作为指令前缀

常用内置指令

*ngIf<div *ngIf="data.length==0">没有更多数据了</div>

//大于0 dataList,小于0 noData
<div *ngIf="data.length>0;then dataList else noData"></div>
<ng-template #dataList>课程列表</ng-template>
<ng-template #noData>没有更多数据了</ng-template>

[hidden]:根据条件显示或隐藏dom节点

<div [hidden]="data.length>0"></div>

*ngFor: 遍历数据生成HTML结构

//i 索引、isEven是否是偶数行(布尔类型)、isOdd是否是奇数行
//isFirst是否是第一行、isLast是否是最后一行,trackBy:相当于vue里的key
<li *ngFor="let item of list;
  let i = index;
  let isEven =even; 
  let isOdd = odd;
  let isFirst = first;
  let isLast = last;
  trackBy:identfy;
">
</li>
list:List[]=[
    {id:'1',names:'san',age:"20"},
    {id:'2',names:'san',age:"20"},
    {id:'3',names:'san',age:"20"},
  ]
  //identfy有两个参数index、item
  identfy(index:number,item:List){
    console.log(index)
    console.log(item)
    return item.id
  }

自定义指令

通过ng generate directive 文件夹名/文件名创建自定义指令
private: 私有的,被private修饰的类、方法、属性、只能被本类的对象所访问。
@HostListener 绑定事件; @Input 定义模块输入,是用来让父级组件向子组件传递内容。

//appHover指令绑定动态元素要加中括号,不需要不加
<div [appHover]="{bgColor:''}">haolou</div>
import { AfterViewInit, Directive ,ElementRef, Input,HostListener} from '@angular/core';
//interface 接口定义Options:接口名字
interface Options{
  bgColor?:string
}

@Directive({
  selector: '[appHover]'//中括号代表要通过属性的方法去使用
})
//AfterViewInit视图加载完成后,implements接口实现
export class HoverDirective implements AfterViewInit{
//@Input 定义模块输入,是用来让父级组件向子组件传递内容
  @Input('appHover') appHover:Options={}
  element:HTMLElement
  constructor(private elementRef:ElementRef) {//依赖注入
    this.element =this.elementRef.nativeElement
   }
   //页面初始化操作dom要在ngAfterViewInit	
   ngAfterViewInit(): void {
    this.element.style.backgroundColor=this.appHover.bgColor||'green'
   }
   //@HostListener绑定事件;
   @HostListener('mouseenter') enter(){//HostListener装饰器后面要指定一个事件处理函数,名字自定义这里叫enter
    this.element.style.backgroundColor ='pink '
   }
   @HostListener('mouseleave') leave(){
    this.element.style.backgroundColor ='green '
   }
}

管道pipe

用来处理组件模板中数据格式
内置常用管道

  • date日期格式化
  • date = new Date()
  • <div>{{date|date:"yyyy-MM-dd"}}</div>
  • currency 货币格式化
  • money = 3423423400
  • {{money|currency:"¥"}}
  • uppercase 转大写
  • title="tets"
  • {{title|uppercase}}
  • lowercase 转小写
  • json 格式化json数据
  • test={ person:{ name:"san", age:"2" } }
  • <pre>{{test|json}}</pre>

自定义管道

生成自定义管道命令行

ng generate pipe 文件夹名/文件名 
parapg="哦吼吼吼吼吼吼dasdbmnasbdnmasbdnmb"
<div>{{parapg |summary:5}}</div>
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'summary'
})
export class SummaryPipe implements PipeTransform {
//transform第一个参数是我们要处理的数据,第二个参数及以都是要往管道传递的参数
  transform(value: string, limit?: number): string {
    return value.substring(0,limit||10)+"...";
  }
}

组件通讯

向内部组件传递数据
//创建组件命令
ng generate component 文件夹名/文件名

//引用组件,name,age给组件内部传参
<app-com-child name="sna" [age]="20"></app-com-child>
//组件内部接受外部传递的数据
 export class ComChildComponent {
  //@Input()用来接收外部传递给内部数据的
  @Input() name:string=''; //接收的属性名字与变量名字一样可这样简写
  //接收'myAge'属性赋值给age变量
  @Input('myAge') age:number=0;//接收的属性名字与变量名称不一致不可简写
}
//组件模板
    <p>{{name}}</p> 
    <p>{{age}}</p> 
组件向外部传递数据
//创建组件命令
ng generate component 文件夹名/文件名

父组件模板页调用子组件
//监听sendData事件,事件被触发的时候调用父组件的事件,通过事件对象$event获取子组件数据
<app-com-child (sendData)="getData2($event)"></app-com-child>
//父组件事件
getData2(event:string){
    alert(event)
}

子组件模板页
<div class="child-color">
    <h1>子组件</h1>
    <button (click)="onCclick()">子组件数据传递给父组件</button>
</div>
//子组件事件
export class ComChildComponent {
//调用组件的时候进行监听,sendData属于子组件内部定义的属性,通过Output装饰器才可以从外面拿到
 @Output() sendData = new EventEmitter()
 
onCclick(){
//子组件点击触发自定义事件sendData 
     this.sendData.emit('子组件传参')//通过emit触发这个自定义事件
  }
}

依赖注入

依赖注入(Dependency injection)简称DI,是面向对象编程中的一种设计原则,用来减少代码之间的耦合度。
例如:

//错误示范
class  emaialSender {
    mailService:MailService
    constructor() {
      this.mailService = new MailService('APIKEY123123123')
    }
    sendMail(mail){
      this.mailService.sendMail(mail)
    }
    const emaialSender = new EmaialSender()
    emaialSender.sendMail(mail)
  }
//正确示范
 class  emaialSender {
    mailService:MailService
    constructor(mailService:MailService) {
      this.mailService = mailService
    }
    const mailService = new MailService('APIKEY123123123')
    const emaialSender = new MailService(mailService)
  }

Injector的创建和使用

DI框架,有四个核心概念:
1.Dependency:组件要依赖的实例对象,服务实例对象
2.Token:获取服务实例对象的标识
3.Injector:注入器,负责创建维护服务类的实例对象并向组件中注入服务实例对象
4.Provider:配置注入器的对象,指定创建服务实例对象的服务类和获取实例对象的标识。
根据上面提到的 const mailService = new MailService('APIKEY123123123')如果MailService的内部类发生了更改,这里也要跟着更改,如何做到内部无论怎么更改也不影响外部?要通过DI框架去达到这个状态,在DI框架中类的实例化不需要我们自己去做,DI框架会去做。

import { NgModule ,Injector} from '@angular/core';//引入Injector

class MailService{}
//创建注入器并传入服务类
const injector =  Injector.create({providers: [{provide: MailService, deps: []}]});
//通过get方法获取实例标识(类的名字)
const mailService = injector.get(MailService);
console.error(mailService)

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

const mailService1 = injector.get(MailService);
const mailService2 = injector.get(MailService);
console.error(mailService1 === mailService2)//true

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

//注入器有父级有子级
const injector =  ReflectiveInjector.resolveAndCreate([MailService])
const childInjector =  ReflectiveInjector.resolveAndCreateChild([MailService])
const mailService1 = injector.get(MailService);
const mailService2 = childInjector.get(MailService);
console.error(mailService1 === mailService2)//false  

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

const injector =  ReflectiveInjector.resolveAndCreate([MailService])
const childInjector =  ReflectiveInjector.resolveAndCreateChild([])
const mailService1 = injector.get(MailService);
const mailService2 = childInjector.get(MailService);
console.error(mailService1 === mailService2)//true

Provider的使用

Provider是注入器的配置对象,指定了创建实例对象的服务类和访问服务实例对象的标识
根据上方const injector = ReflectiveInjector.resolveAndCreate([MailService])resolveAndCreate里只传递了一个类,是简写,完整写法如下:

const injector =  ReflectiveInjector.resolveAndCreate([
//useClass属性指定的是使用哪一个类去创建实例对象,provide属性指定的是要通过什么样的标识去获取这个实例对象
	{ provide:MailService,useClass:MailService }
])

访问依赖对象的标识也可以是字符串类型

const injector =  ReflectiveInjector.resolveAndCreate([
	{ provide:"mail",useClass:MailService }
])
const mailService = injector.get("mail");

将实例对象和外部的引用建立了松耦合关系,外部通过标识获取实例对象,是要标识保持不变,内部代码怎么变都不会影响到外部

const injector =  ReflectiveInjector.resolveAndCreate([
	{ 
		//useValue存储一个值进来,如果值是一个配置对象的话,当别人过去到这个对象的时候,
		//我们不想让别人在外部去更改这个对象,所以通过Object.freeze去冻结这个对象,只能获取不能修改
		provide:"config",useValue:Object.freeze({
			apikey:"APIKEY123123123",
			apiScrte:"200-300-100"
		})
	}
])
const config = injector.get("config");

路由

创建路由规则

import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
  {
    path: 'home',
    component: HomeComponent,
  },
  //redirectTo重定向,pathMatch完全匹配
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  //如果任何一个规则都没匹配到path: '**'代表任何请求地址
  { path: '**', redirectTo: '/home', pathMatch: 'full' },
];

@NgModule({
  //forRoot用来启动路由
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})

路由传参

1.查询传参

//传参
<a routerLink="/home" [queryParams]="{name:'33'}">首页</a>
//接参
export class AboutComponent implements OnInit {
  constructor(private router: ActivateRouter) {}
  ngOnInit(): void {
  	this.route.queryParamMap.subscribe(res =>{
		res.get('name')
	})
  }
}

2.动态参数
参数在地址栏显示

const routes: Routes = [
  {
    path: 'home',
    component: HomeComponent,
  },
  {
    path: 'about/:name',
    component: AboutComponent,
  },
];
//传参
<a [routerLink]="'/about','33'">首页</a>
//接参
export class AboutComponent implements OnInit {
  constructor(private router: ActivateRouter) {}
  ngOnInit(): void {
  	this.route.queryParamMap.subscribe(res =>{
		res.get('name')
	})
  }
}

路由嵌套

路由嵌套是指如何定义子级路由

const routes: Routes = [
  {
    path: 'about',
    component: AboutComponent,
    chilldren:[
    	{
			path: 'h1',
			component: h1Component,
		},
		{
			path: 'h2',
			component: h2Component,
		},
    ]
  },
];
//html
<a routerLink="/about/h1">h1</a>
<a routerLink="/about/h2">h2</a>
<div>
	<router-outlet></router-outlet>
</div>

命名插座

将子级路由组件显示到不同的路由插座中

const routes: Routes = [
  {
    path: 'about',
    component: AboutComponent,
    chilldren:[
    	{
			path: 'h1',
			component: h1Component,
			outlet:'left'
		},
		{
			path: 'h2',
			component: h2Component,
			 :'right'
		},
    ]
  },
];
//about.html
<div>
	<router-outlet name="left"></router-outlet>
	<router-outlet name="right"></router-outlet>
</div>
//导航页
<a [routerLink]="'/about',{outlets:{left:['h1'],right:['h2']}}">导航页</a>

导航路由

//html
<button (click)="jump()">跳转</button>
//ts
import { Routes } from '@angular/router';
const routes: Routes = [
  {
    path: 'about/:name',
    component: AboutComponent,
  },
];
export class AboutComponent {
  constructor(private router: Router ) {}
  jump() {
    //navigate用来实现跳转,第一个参数是要跳转的地址,第二个是一个配置对象,queryParams查询参数
  	this.route.navigate(["/about","hihi"],{
		queryParams:{ 
			name:"hi"
		}
	})
  }
}

路由懒加载

命令行创建模块时--routing=true 会自行创建对应的路由模块。急上手,挺简单的掠过了,自行百度吧

路由守卫

1.CanActivate:检查用户是否可以访问这个路由,CanActivate规定类中需要有canActivate方法,方法会返回布尔值,true为允许访问,false不允许访问
创建路由守卫命令行ng g guards 文件夹/文件名

//返回跳转地址需构造函数注入路由
constructor(private router: Router) {}
canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
    return this.router.createUrlTree(["/home"])//用于实现跳转
  }

2.CanActivateChild: 检查用户是否可以访问某个子路由
执行命令行ng g guards 文件夹/文件名选择CanActivateChild创建

const routes: Routes = [
  {
    path: 'about',
    component: AboutComponent,
    canActivateChild:[AdminGuard]
    chilldren:[
    	{
			path: 'h1',
			component: h1Component,
		},
    ]
  },
];
//guard.ts
  constructor(private router: Router) {}
  canActivateChild(
	  route: ActivatedRouteSnapshot,
	  state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    	return true;
  }

HttpClientModule

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

//app.module
import { HttpClientModule } from '@angular/common/http';
imports: [ HttpClientModule]
//app.component
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.http.get('请求地址')返回结果为可观察对象,通过subscribe方法打印到控制台中
		this.http.get('请求地址').subscribe(console.log)
	}
}

请求参数

HttpParams类

向服务器发送请求的时候携带参数

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):Httparams;//追加请求参数
	set(param:string,value:string):HttpParams;//设置请求参数
	delete(param:string,value?:string):HttpParams;//删除请求参数
	toString():string;
}

HttpParamsOptions

declare interface HttpParamsOptions{
	fromString?:string;
	fromObject?:{
		[param:string]:string | ReadonlyArray<string>;
	};
	encoder?:HttpParameterCodec;
}

使用示例

import { HttpParams} from '@angular/common/http';
//通过new实例化类
let params = new HttpParams({fromObject:{name:"33",age:"20"}})
params = params.append('sex','male')
let params = new HttpParams({fromString:'name=33&age =20'})

请求头

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

export declare class HttpHeaders{
	constructor(headers?:string | { [name:string]:string | string[];
	});
	has(param:string):boolean;//判断里面是否有某一个参数
	get(param:string):string:null;//获取参数
	getAll(param:string):string[] | null;//获取全部参数
	keys():string[];
	append(param:string,value:string):Httparams;//追加请求参数
	set(param:string,value:string):HttpParams;//设置请求参数
	delete(param:string,value?:string):HttpParams;//删除请求参数
}
let headers = new HttpHeaders({ test:"hello"})

响应内容

body是默认值
declare type HttpObserve = 'body' | 'response';
//response 读取完整响应体
//body 读取服务器端返回的数据
// 注意:每个进度事件都会触发变更检测,所以只有当需要在UI上报告进度时,你才应该开启它们
this.http.get(
"http://localhost:3005/use",
{ observe:"body" }
).subscribe(console.log)

变更检测机制

引用博主文章:点这

表单

补充引用博主文章点这去看
表单有两种类型,分别为模板驱动和模型驱动
模板驱动:表单的控制逻辑写在组件模板,适合简单表单类型
模型驱动:表单的控制逻辑全部写在组件类中

模板驱动

 引入import { FormsModule} from '@angular/forms';
 @NgModule({
  imports: [FormsModule],
})
//#f模板变量,名字自定义,ngForm给模板变量赋值
<form #f="ngForm" (submit)="onsubmit(f)">
	<input tpype="text" name="usename" ngModel>//ngModel声明表单字段为ngModel
</form>

表单分组

<form #f="ngForm" (submit)="onsubmit(f)">
	<div ngModelGroup="user">//ngModelGroup表示分组
		<input tpype="text" name="usename" ngModel>
	</div>
</form>

表单验证

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

表单整体未通过校验

//invalid
<button type="submit" [disabled]="f.invalid">提交</button>

在组件模板中显示表单项未通过的时的错误信息

<form #f="ngForm" (submit)="onsubmit(f)">
	<input #usename="ngModel">
	<div *ngIf="usename.touched&&!usename.valid">//valid不为真
		//errors验证规则都通过了值是NAN,不通过是对象
		<div *ngIf="usename.errors.required">请填写正确用户名</div>
		<div *ngIf="usename.errors.pattern">不符合规则</div>
	</div>
</form>

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

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

自定义表单控件

forwardRef:允许引用尚未定义的引用
NG_VALUE_ACCESSOR:用于为表单控件提供 ControlValueAccessor
ControlValueAccessor:定义一个接口,该接口充当 Angular 表单 API 和 DOM 中的原生元素之间的桥梁。
NG_VALIDATORS:可将控件注册成为可让表单访问到其验证状态的控件。
InjectionToken:每当你要注入的类型无法确定(没有运行时表示形式)时,比如在注入接口、可调用类型、数组或参数化类型时,都应使用 InjectionToken。
writeValue(obj: any): void 数据由模型更新到视图(model->view)时,方法被调用
registerOnChange(fn: any): void //数据由视图更新到模型(view->model)时,方法被调用

添加访问

@Component({
  selector: 'app-radio-item-group',
  templateUrl: './radio-item-group.component.html',
  styleUrls: ['./radio-item-group.component.less'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR, //访问器
      useExisting: forwardRef(() => RadioItemGroupComponent),
      multi: true,
    },
  ],
})
export class RadioItemGroupComponent implements OnInit {
  @Input() checkList = [];
  constructor() {}
  ngOnInit(): void {
    console.log(this.checkList);
  }
  控件view发生变化,把变化emit给表单
  registerOnChange(fn: any): void {
    this.propagateChange = fn;
  }
  什么状态算touched,告诉表单
  registerOnTouched(fn: any): void {}

  setDisabledState?(isDisabled: boolean): void {}
 提供值的写入方法
  writeValue(obj: any): void {
    console.log('写入值了',obj);
  }
   当表单控件值改变时,函数 fn 会被调用
   这也是我们把变化 emit 回表单的机制
  private propagateChange = (_: any) => {};

  selecte(i) {
    if (i == 0) {
      this.checkList[0].selected = true;
      this.checkList[1].selected = false;
    } else {
      this.checkList[0].selected = false;
      this.checkList[1].selected = true;
    }
    console.log(this.checkList);
    this.propagateChange(this.checkList);
  }
}

发布订阅

我在A模块里面,需要点击展示一个整个页面的蒙层,但是A模块在B模块有底部导航栏的模块里面,在A模块里面加蒙层,没有办法全部覆盖屏幕,所以就用到了发布订阅

原本错误方式是,我在A模块里写了蒙层,但遮盖不全;
所以通过发布订阅改为如下:

创建Service文件(放哪里都可以,如果多处用到,可以放到公共文件里)

import { Observable, Subject } from 'rxjs';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ModalboxService {
  constructor() { }

  private subject = new Subject<any>();

  send(message: any) {
    this.subject.next(message);
  }

  get(): Observable<any> {
    return this.subject.asObservable();
  }
}

A模块注入Service,发布数据

export class HomeComponent{
  constructor(private ModalboxService:ModalboxService) {}
  show(){
    this.ModalboxService.send(true);
  }
}

在B导航栏模块里加蒙层,注入服务,订阅服务

export class TabBarComponent implements OnInit {
  constructor(private sve:ModalboxService) {}

  ngOnInit(): void {
    this.sve.get().subscribe((result) => {
      console.log(result);
      this.ifshow = result;
    });
  }
}

路由守卫

路由守卫方法可以返回boolean 或 Observable 或 Promise,
1.CanActivate
检查用户是否可以访问某一个路由
路由可以应用多个守卫,所有守卫方法都允许,路由才允许被访问
创建路由守卫:ng g guard 文件名
选择你需要的路由方法

ng g guard guars/test-guard
? Which type of guard would you like to create? (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
>(*) CanActivate
 ( ) CanActivateChild
 ( ) CanDeactivate
 ( ) CanLoad
 ( ) CanMatch
@Injectable({
  providedIn: 'root'
})
export class TestGuardGuard implements CanActivate {
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
   		UrlTree 用于实现页面跳转
   		return this.router.createUrlTree(["/user/login"])
  }
  
}

2.CanActivateChild
检查用户是否可以访问某个子路由,CanActivateChild会在任何子路由被激活之前运行。

@Injectable({
  providedIn: 'root'
})
export class TestGuardGuard implements CanActivateChild{
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
   		UrlTree 用于实现页面跳转
   		return this.router.createUrlTree(["/user/login"])
  }
  
}
{
    path:'home',
    component:DemoComponent,
    canActivateChild:[TestGuardGuard],
    children:[
      {
        path:'',
        component:ComChildComponent
      }
    ]
  }

3.CanDeactivate
检查用户是否可以退出路由。

//guard.ts文件
//由于路由守卫可以同时应用在多个路由当中,每个路由组件都提供这个方法,所以路由守卫提供的方法名字应该是一致的,如果不一致,就需要定义一个接口去约束这个方法的名字,以及返回值的类型

//定义接口
export interface canLeave{
	canLeave:()=>boolean
}
@Injectable({
  providedIn: 'root'
})
export class CandeactGuard implements CanDeactivate<canLeave> {
  canDeactivate(
    component: canLeave,//component应用了当前路由那个组件的实例
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
			 if(component.canLeave()){
			 	return true
			 }else{
			 	return flase 
			 }
  }
}
//home.ts文件
把canLeave接口引入过来
export class HomeComponent implements canLeave{
  canLeave() {
  //如果满足条件可以离开,不满足为false
    return true
  }
}

4.Resolve
允许在进入路由之前先获取数据,数据获取完成之后在进入路由
ng g resolver 文件名

{
    path:'home',
    component:DemoComponent,
   	resolve:{
   		user:ResolvGuardResolver 
   	}
  }
type returnType =  Promise<{ name:string }>

@Injectable({
  providedIn: 'root'
})
export class ResolvGuardResolver implements Resolve<returnType> {
  resolve():returnType {
    return new Promise((resolve)=>{
      setTimeout(()=>{
        resolve({name:'222'})
      },2000)
    })
  }
}
export class HomeComponent implements OnInit {
  constructor(private router:ActivatedRoute){}
  ngOnInit(): void {
    console.log(this.rputer.snapshot.data.name);
  }
}

RxJS

1.什么是RxJS?
RxJS是一个用于处理异步编程的Javascript库,目标是使编写异步和基于回调的代码更容易。

2.快速入门
1.可观察对象(Observable):类比Promise对象,内部可以用于执行异步代码,通过调用内部提供的方法将异步代码执行的结果传递到可观察的对象外部
2.观察者(Observer):类比then方法中的回调函数,用于接收可观察对象中传递出来的数据
3.订阅(subscribe):类比then方法,通过订阅将可观察对象和观察者连接起来,当可观察对象发出数据时,订阅者可以接收到数据

import {Observable} from "rxjs";
const obserevable = new Observablle((observer)=>{
	setTimeoout(()=>{
	//通过next方法把数据传递出去
		observer.next({
			name:'张三'
		})
	},2000)
})
const observer = {
	//接收传递出来的数据
	next:funciton(value){
		console.log(value)
	}
}
//通过subscribe,observer订阅observable对象
observable.subscribe(observer);

可观察对象特性

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

const obserevable = new Observablle((observer)=>{
	let index = 0;
	setInterval(()=>{
		observer.next(index++;)
	},1000)
})
const observer = {
	//接收传递出来的数据
	next:funciton(value){
		console.log(value)
	}
}
//通过subscribe,observer订阅observable对象
observable.subscribe(observer);

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

const obserevable = new Observablle((observer)=>{
	let index = 0;
	let time =  setInterval(()=>{
		observer.next(index++;)
		if(index===3){
			通过complete终止
			observer.complete();
			clearInterval(time);
		}
	},1000)
})
也可以通过observer 里的
const observer = {
	//接收传递出来的数据
	next:funciton(value){
		console.log(value)
	}
	complete:function(){
		console.log('中止')
	}
}

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

import {Observable} from "rxjs";
const obserevable = new Observablle((observer)=>{
	let index = 0;
	let time =  setInterval(()=>{
		observer.next(index++;)
		if(index===3){
			通过complete终止
			observer.error('出错了');
			clearInterval(time);
		}
	},1000)
})
const observer = {
	//接收传递出来的数据
	next:funciton(value){
		console.log(value)
	},
	error:funciton(error){
		console.log(error)
	},
}
//通过subscribe,observer订阅observable对象
observable.subscribe(observer);

可观察对象默认不执行,只有被订阅后才会执行

const obserevable = new Observablle(()=>{
	console.log('RxJs')
})
//订阅才执行
observable.subscribe();

可观察对象可以有很多订阅者,每次被订阅都会被执行

Subject

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

import {Subject} from "rxjs";
const demoSubject= new Subject();
//观察者订阅可观察对象
demoSubject.subscribe({next:(value)=>{console.log(value)}});
demoSubject.subscribe({next:(value)=>{console.log(value)}}); 

setTimeoout(()=>{
		demoSubject.next('哈哈')
	},3000)

BehaviorSubject

拥有Subject全部功能,但是在创建Observable对象时可以传入默认值,观察者订阅后可以直接拿到默认值

import {BehaviorSubject} from "rxjs";

const demoBehavior= new BehaviorSubject('默认值');

demoBehavior.subscribe({next:(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('hello1')
rSubject .next('hello2')
setTimeoout(()=>{
	rSubject .subscribe({next:(value)=>{console.log(value)}})
},3000)

数据流、操作符

1.数据流:从可观察对象内部输出得到数据就是数据流,可观察对象内部可以像外部源源不断的输出数据。
2.操作符:用于操作数据流,可以对象数据流进行转换、过滤等操作

range

调用方法后返回observable(可观察对象)对象,被订阅后会发出指定范围的数值

import {range} from "rxjs";

range(0,5).subscribe(n=> console.log(n))
//0
//1
//2
//3
//4

方法内部并不是一次发出length个数值,而是发送了length次,每次发送一个数值,就是说内部调用了length次next方法

map

对数据流进行转换,基于原有的值进行转换。

import {interval} from "rxjs";
import {map} from "rxjs/operators";

interval(1000)
.pipe(map(n=>n*2))
.subscribe(n=>console.log(n))

辅助方法

from

将Arry,Promise,lterator(迭代器)转换为observable对象

from(['a','b','c']).subscribe(v=>console.log(v))
//a
//b
//c
import {from} from "rxjs";
p(){
	return new Promise((resolve)=>{
		resolve([100,200])
	})
}

from(p()).subscribe(v=>console.log(v))

forkJoin

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

 import {axios} from "axios";
 import { from,forkJoin } from "rxjs";

拦截器-只返回data数据结果
axios.interceptors.response.use(response=? response.data)
 
 forkJoin({
	use:from(axios.get(" http://localhost:3005/use")),
	name:from(axios.get("http://localhost:3005/name"))
  }).subscribe(console.log)

fromEvent

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

//html
<button id="btn"></button>
//ts
 import { fromEvent } from "rxjs";
 const btn = document.getElementById("btn")
 //可以将Observer简写成一个函数,表示next
 fromEvent(btn,'click')
.pipe(获取事件对象的事件源
	map(event=>event.target)
)
.subscribe(e=>console.log(e))

pluck

获取数据流对象中的属性值

//html
<button id="btn"></button>
//ts
 import { fromEvent } from "rxjs";
 import { pluck,map} from "rxjs/operators";


 const btn = document.getElementById("btn")
 
 fromEvent(btn,'click')
.pipe(pluck('target'))
.subscribe(e=>console.log(e))

interval、timer

interval: 每隔一段时间发出一个数值,数值递增

 import { interval} from "rxjs";
 interval(1000).subscribe(n=>console.log(n))

timer: 间隔时间过去以后发出数值,行为终止,或间隔时间发出数值后,继续按第二个参数的时间间隔发出值

 import { timer} from "rxjs";
 timer(1000).subscribe(n=>console.log(n))
 timer(0,500).subscribe(n=>console.log(n))

switchMap

切换可观察对象(Observable)

//html
<button id="btn"></button>
//ts
 import { fromEvent,interval } from "rxjs";
 import { switchMap} from "rxjs/operators";
 
  const btn = document.getElementById("btn")
  
fromEvent(btn,'click')//点击事件是一个Observable,点击按钮的时候发送一个事件对象出来
 .pipe(switchMap(ev=>interval(1000)))//调用switchMap方法将Observable切换为interval,switchMap会抛弃前一个结果
 .subscribe(e=>console.log(e))//订阅这里拿到的是interval

of

将参数列表作为数据流返回

 import { of } from "rxjs";

 of('a','b',1,true,[]).subscribe(v=>console.log(v))

操作符

take

获取数据流中的前几个

 import { range} from "rxjs";
  import { take } from "rxjs/operators";
 // range(1,10)从1开始发送,发送10个数值
 range(1,10).pipe(take(5)).subscribe(console.log)

takeWhile

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

 import { range} from "rxjs";
  import { takeWhile} from "rxjs/operators";
 // range(1,10)从1开始发送,发送10个数值
 range(1,10).pipe(takeWhile(n=>n<8)).subscribe(console.log)

takeUntil

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

//html
<button id="btn"></button>
//ts
 import { fromEvent } from "rxjs";
 import { takeUntil } from "rxjs/operators";
  
 const btn = document.getElementById("btn")
  
fromEvent(doument,'mousemove')//点击事件是一个Observable,点击按钮的时候发送一个事件对象出来
 .pipe(takeUntil(fromEvent(button,"click")))//调用switchMap方法将Observable切换为interval
 .subscribe(console.log())//订阅这里拿到的是interval

distinctUntilChanged

检测数据源当前发出的数据流是否和上次发出的相同,如相同,跳过,不相同,发出

 import { of } from "rxjs";
 import { distinctUntilChanged } from "rxjs/operators";

of(1,2,3,4,4,5,4).pipe(distinctUntilChanged ()).subscribe(x=>console.log(x))

操作符节流和防抖

throttleTime(节流)

可观察对象高频次的向外部发出数据流,通过throttleTime限制在规定时间内每次只向订阅者传递一次数据流

 import { fromEvent } from "rxjs";
 import { throttleTime} from "rxjs/operators";
 
 fromEvent(doument,'click')
 .pipe(throttleTime(2000)
 .subscribe(console.log())

debounceTime(防抖)

检测数据源当前发出的数据流是否和 上次发出的相同,如相同,跳过,不相同出发

 import { fromEvent } from "rxjs";
 import { debounceTime} from "rxjs/operators";
 
 fromEvent(doument,'click')
 .pipe(debounceTime(2000)
 .subscribe(x=>console.log(x))

拦截器

拦截器是Angular应用中全局捕获和修改的Http请求和响应的方式(Token,Error)
拦截器将只拦截使用HttpClientModule模块发出的请求。
ng g interceptor 文件名

请求拦截

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

响应拦截

@Injectable()
export class AuthInjectable implements HttpInterceptor{
	 constructor() {}
	 //拦截方法
	 intercept(
		//unknown指定请求体(body)的类型
		request:HttpRequest<unknown>,//unknown泛型
		next:HttpHandler
		//unknown指定请求体(body)的类型
	 ):Observable<any>{
		return next.handle(request).pipe(
			retry(2),
			catchError((error:HttpErrorResponse)=>throwError(error))
		)
	}
}

拦截器注入

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

 @NgModule({
 	providers:[
 	
		{
			provide:HTTP_INTERCEPTORS,//provide指定谁调用,调用的人通过什么标识获取实例对象
			useClass:AuthInterceptor,
			multi:true 
		}
	]
 })

Angular Proxy(代理)

解决开发环境下,接口不在同一个域的问题

在项目根目录下创建proxy.config.json文件

{   "/api": {  //在应用中发出的以/api开头的请求走此代理
  "target": "http://192.168.224.114:5200/",//服务器端url
  "secure": false,//如果服务器端的url的协议是https,此项需要为true
  "logLevel": "debug",
  "changeOrigin": true//如果服务器端不是localhost,此项需要为true
},

如何应用:
1.在proxy配置文件里添加

"scripts":{
	"start":"ng serve --proxy-config proxy.conf.json"
}

2.在angular.json配置文件里添加

"serve":{
	"optins":{
		"proxyConfig":"proxy.conf.json"
	}
}

NgRx

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

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

快速开始

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
如果Store属于根模块就加–root,不是就去掉
module属于哪个模块
statePath 指定store存储的路径
stateInterFace 存储状态类型接口名字

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';

//store中存储的类型接口
export interface State {

}
//状态名字和reducer的额对应的关系
export const reducers: ActionReducerMap<State> = {

};


export const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];

4.创建Action
ng g action store/actions/counter --skipTests
skipTests忽略测试文件

import { createAction } from "@ngrx/store"
export const increment = createAction("increment")
export const decrement = createAction("decrement")

5.创建Reducer
ng g reducer store/reducers/counter --skipTests --reducers=../index.ts

import { createReducer, on } from "@ngrx/store"
import { decrement, increment } from "../actions/counter.actions"
export const counterFeatureKey = "counter"
export interface State {
  count: number
}
export const initialState: State = {
  count: 0
}
export const reducer = createReducer(
  initialState,
  on(increment, state => ({ count: state.count + 1 })),
  on(decrement, state => ({ count: state.count - 1 }))
)

6.创建 Selector
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)

7.组件类触发 Action、获取状态

import { select, Store } from "@ngrx/store"
import { Observable } from "rxjs"
import { AppState } from "./store"
import { decrement, increment } from "./store/actions/counter.actions"
import { selectCount } from "./store/selectors/counter.selectors"
export class AppComponent {
  count: Observable<number>
  constructor(private store: Store<AppState>) {
    this.count = this.store.pipe(select(selectCount))
  }
  increment() {
    this.store.dispatch(increment())
  }
  decrement() {
    this.store.dispatch(decrement())
  }
}

8.组件模板显示状态

<button (click)="increment()">+</button>
<span>{{ count | async }}</span>
<button (click)="decrement()">-</button>
 类似资料: