也许是全网最全的 Angular 新手入门指南

news2025/1/15 6:33:55

文章目录

    • Angular概述
      • Angular程序架构
      • Angular优势
      • @angular/cli脚手架
      • 文件加载顺序
      • 项目目录结构
    • Angular模块
      • @NgModule 装饰器
      • 内置模块
      • 自定义模块
      • 模块的tips
    • Angular组件
      • @Component 元数据
      • 数据绑定
      • 脏值检测
      • 父子组件通讯
      • 投影组件
    • Angular指令
      • 内置属性型指令
      • 内置结构型指令
      • 指令事件样式绑定
    • Angular生命周期
    • Angular路由
      • 路由基本配置
      • 激活路由
      • 路由参数
      • 路由懒加载
    • Angular服务
      • @Injectable()装饰器
      • 依赖注入
    • Angular 管道
      • 内置管道
      • 自定义管道
    • Angular操作DOM
      • 原生JS操作
      • ElementRef
      • ViewChild
      • Renderer2
    • Angular网络请求
      • HttpClient
      • 拦截器
    • Angular表单
      • 模版驱动表单
      • 响应式表单
      • 表单验证
      • 动态表单
    • Angular CDK

Angular概述

Angular 是谷歌开发的一款开源的 web 前端框架,基于 TypeScript 。

和 react 与 vue 相比, Angular 更适合中大型企业级项目。

Angular程序架构

在这里插入图片描述

Angular优势

  • 可伸缩性:基于RxJS 、immutable.js和其他推送模型,能适应海量数据需求
  • 跨平台:渐进式应用(高性能、离线使用、免安装),原生(Ionic),桌面端
  • 生产率:模版(通过简单而强大的模版语法,快速创建UI视图),CLI(快速进入构建环节、添加组件和测试,然后立即部署)
  • 测试:单元测试(支持Karma、Jasmine等工具进行单元测试),端到端测试(支持Protractor等工具进行端到端测试)

@angular/cli脚手架

  1. ng new 新建项目
  • ——routing 配置路由
  • ——style=css|scss|less 配置css样式
  1. ng serve 启动项目
  • ——port 4200 端口号,默认4200
  • ——open 自动打开浏览器
  1. ng build 打包项目
  • ——aot 预编译
  • ——prod 压缩打包
  • ——base-href=/static/
  1. ng generate 创建模块/组件/服务
  • module ——routing 创建模块
  • component 创建组件
  • service / 创建服务

文件加载顺序

main.ts => app.module.ts => app.component.ts => index.html => app.component.html

项目目录结构

|-- project
	|-- .editorconfig // 用于在不同编辑器中统一代码风格
	|-- .gitignore // git中的忽略文件列表
	|-- README.md // markdown格式的说明文件
	|-- angular.json // angular的配置文件
	|-- browserslist // 用于配置浏览器兼容性的文件
	|-- karma.conf.js // 自动化测试框架Karma的配置文件
	|-- package-lock.json // 依赖包版本锁定文件
	|-- package.json // npm的包定义文件
	|-- tsconfig.app.json // 用于app项目的ts配置文件
	|-- tsconfig.json // 整个工作区的ts配置文件
	|-- tsconfig.spec.json // 用于测试的ts配置文件
	|-- tslint.json // ts的代码静态扫描配置
	|-- e2e // 自动化集成测试目录
	|-- src // 源代码目录
 
|-- src // 源代码目录
	|-- favicon.ico // 收藏图标
	|-- index.html // 单页应用到宿主HTML
	|-- main.ts // 入口 ts 文件
	|-- polyfills.ts // 用于不同浏览器的兼容脚本加载
	|-- styles.css // 整个项目的全局css
	|-- test.ts // 测试入口
	|-- app // 工程源码目录
	|-- assets // 资源目录
	|-- environments // 环境配置
		|-- environments.prod.ts // 生产环境
		|-- environments.ts // 开发环境

Angular模块

在 app.module.ts 中定义 AppModule,这个根模块会告诉 Angular 如何组装应用。

在这里插入图片描述

@NgModule 装饰器

@NgModule 接受一个元数据对象,告诉 Angular 如何编译和启动应用

设计意图

  • 静态的元数据(declarations)
  • 运行时的元数据(providers)
  • 组合与分组(imports 和 exports)

元数据

  • declarations 数组:模块拥有的组件、指令或管道,注意每个组件/指令/管道只能在一个模块中声明
  • providers 数组: 模块中需要使用的服务
  • imports 数组:导入本模块需要的依赖模块,注意是模块
  • exports 数组: 暴露给其他模块使用的组件、指令或管道等
  • bootstrap 数组:指定应用的主视图(称为根组件)通过引导根 AppModule 来启动应用,即项目刚加载时选择读哪个组件
  • entryComponents 数组:一般用于动态组件

内置模块

常用的有:核心模块、通用模块、表单模块、网络模块等

在这里插入图片描述

自定义模块

当项目比较小的时候可以不用自定义模块

但是当项目非常庞大的时候,把所有的组件都挂载到根模块里面就不太合适了

所以可以使用自定义模块来组织项目,并且通过自定义模块可以实现路由的懒加载

模块的tips

导入其他模块时,需要知道使用该模块的目的

  • 如果是组件,那么需要在每一个需要的模块中都进行导入
  • 如果是服务,那么一般来说在根模块导入一次即可

需要在每个需要的模块中进行导入的

  • CommonModule : 提供绑定、*ngIf 和 *ngFor 等基础指令,基本上每个模块都需要导入它
  • FormsModule / ReactiveFormsModule : 表单模块需要在每个需要的模块导入
  • 提供组件、指令或管道的模块

只在根模块导入一次的

  • HttpClientModule / BrowerAnimationsModule NoopAnimationsModule
  • 只提供服务的模块

Angular组件

在这里插入图片描述

  • 组件是 Angular 的核心,是 Angular 应用中最基本的 UI 构造块,控制屏幕上被称为视图的一小片区域
  • 组件必须从属于某个 NgModule 才能被其他组件或应用使用
  • 组件在 @NgModule 元数据的 declarations 字段中引用

@Component 元数据

  • selector :选择器,选择相匹配的HTML里的指令模版
  • templateUrl :将选择器中匹配的指令同级替换成值的模版
  • template :内嵌模版,直接可以在里面写HTML模版
  • styleUrls :对应模版的样式,为一个数组,可以引入多个css样式控制组件
  • encapsulation:组件样式封装策略
@Component({
  selector: 'app-xxx',
  templateUrl: 'XXX',
  styleUrls: ['XXX'],
  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签
})

数据绑定

  • 数据绑定 {{data}}

  • 属性绑定 [id]="id",其中[class.样式类名]=“判断表达式”是在应用单个class样式时的常用技巧

  • 事件绑定 (keyup)="keyUpFn($event)"

  • 样式绑定可以用 :host 这样一个伪类选择器,绑定的样式作用于组件本身

  • 双向数据绑定 [(ngModel)]

    // 注意引入:FormsModule
    import { FormsModule } from '@angular/forms';
    
    <input type="text" [(ngModel)]="inputValue"/> {{inputValue}}
    
    // 其实是一个语法糖
    [ngModel]="username" (ngModelChange)="username = $event"
    

脏值检测

脏值检测:当数据改变时更新视图(DOM)

如何进行检测:检测两个状态值(当前状态和新状态)

何时触发脏值检测:浏览器事件(clickmouseoverkeyup等)、setTimeout()setInterval()、HTTP请求

Angular 有两种变更检测策略:DefaultOnPush

可以通过在@Component元数据中设置changeDetection: ChangeDetectionStrategy.OnPush进行切换

Default

优点:每一次有异步事件发生,Angular 都会触发变更检测,从根组件开始遍历其子组件,对每一个组件都进行变更检测,对dom进行更新。

缺点:有很多组件状态没有发生变化,无需进行变更检测。如果应用程序中组件越多,性能问题会越来越明显。

OnPush

优点:组件的变更检测完全依赖于组件的输入(@Input),只要输入值不变就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升。

缺点:必须保证输入(@Input)是不可变的(可以用Immutable.js解决),每一次输入变化都必须是新的引用。

父子组件通讯

在这里插入图片描述

父组件给子组件传值 @input

父组件不仅可以给子组件传递简单的数据,还可把自己的方法以及整个父组件传给子组件。

// 父组件调用子组件的时候传入数据
<app-header [msg]="msg"></app-header>

// 子组件引入 Input 模块
import { Component, OnInit ,Input } from '@angular/core';

// 子组件中 @Input 装饰器接收父组件传过来的数据
export class HeaderComponent implements OnInit {
  @Input() msg:string
	constructor() { }
	ngOnInit() { }
}

// 子组件中使用父组件的数据
<h2>这是头部组件--{{msg}}</h2>

**子组件触发父组件的方法 @Output **

// 子组件引入 Output 和 EventEmitter
import { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';

// 子组件中实例化 EventEmitter
// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量
@Output() private outer=new EventEmitter<string>();

// 子组件通过 EventEmitter 对象 outer 实例广播数据
sendParent(){
  this.outer.emit('msg from child')
}

// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer
<app-header (outer)="runParent($event)"></app-header>

// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据
// 接收子组件传递过来的数据
  runParent(msg:string){
   alert(msg);
}

父组件通过 ViewChild 主动调用子组件DOM和方法

// 给子组件定义一个名称
<app-footer #footerChild></app-footer>

// 引入 ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';

// ViewChild 和子组件关联起来
@ViewChild('footerChild') footer;

// 调用子组件
run(){
   this.footer.footerRun();
}

投影组件

在这里插入图片描述

由于组件过度嵌套会导致数据冗余和事件传递,因此引入投影组件的概念

投影组件 ng-content 作为一个容器组件使用

主要用于组件动态内容的渲染,而这些内容没有复杂的业务逻辑,也不需要重用,只是一小部分 HTML 片段

使用 ng-content 指令将父组件模板中的任意片段投影到它的子组件上

组件里面的 ng-content 部分可以被组件外部包裹的元素替代

// 表现形式: <ng-content select="样式类/HTML标签/指令"></ng-content>

<ng-content select="[appGridItem]"></ng-content>

select 表明包含 appGridItem 的指令的元素才能投影穿透过来


Angular指令

在这里插入图片描述

指令可以理解为没有模版的组件,它需要一个宿主元素(Host)

推荐使用方括号 [] 指定 Selector,使它变成一个属性

@Directive({
selector: '[appGridItem]'
})

内置属性型指令

NgClass

ngClass 是自由度和拓展性最强的样式绑定方式

<div [ngClass]="{'red': true, 'blue': false}">
  这是一个 div
</div>

NgStyle

ngStyle由于是嵌入式样式,因此可能会覆盖掉其他样式,需谨慎

<div [ngStyle]="{'background-color':'green'}">你好 ngStyle</div>

NgModel

// 注意引入:FormsModule
import { FormsModule } from '@angular/forms';

<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}

内置结构型指令

ngIf

ngIf 根据表达式是否成立,决定是否展示 DOM 标签

<p *ngIf="list.length > 3">这是 ngIF 判断是否显示</p>

ngIf else

<div *ngIf="show else ElseContent">这是 ngIF 内容</div>
<ng-template #ElseContent>
  <h2>这是 else 内容</h2>
</ng-template>

// 结构性指令都依赖于 ng-template,*ngIf 实际上就是 ng-template 指令的 [ngIf] 属性。

ngFor

<ul>
  <li *ngFor="let item of list;let i = index;">
     {{item}} --{{i}}
  </li>
</ul>

ngSwitch

<ul [ngSwitch]="score">
   <li *ngSwitchCase="1">已支付</li>
   <li *ngSwitchCase="2">已确认</li>
   <li *ngSwitchCase="3">已发货</li>
   <li *ngSwitchDefault>已失效</li>
</ul>

指令事件样式绑定

@HostBinding 绑定宿主的属性或者样式

@HostBinding('style.display') display = "grid";

// 用样式绑定代替rd2的 this.setStyle('display','grid');

@HostListener 绑定宿主的事件

@HostListener('click',['$event.target'])

// 第一个参数是事件名,第二个是事件携带参数

Angular生命周期

生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法

当 Angular 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子

  • constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化

  • ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges

  • ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建

  • ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后

    • ngAfterContentInit :内容投影ng-content完成时调用,只在第一次 ngDoCheck 之后调用

    • ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)

    • ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次

    • ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)

  • ngOnDestroy 当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏


Angular路由

路由(导航)本质上是切换视图的一种机制,路由的导航URL并不真实存在

Angular 的路由借鉴了浏览器URL变化导致页面切换的机制

Angular 是单页程序,路由显示的路径不过是一种保存路由状态的机制,这个路径在 web 服务器上不存在

路由基本配置

/**
 * 在功能模块中定义子路由后,只要导入该模块,等同于在根路由中直接定义
 * 也就是说在 AppModule 中导入 HomeModule 的时候,
 * 由于 HomeModule 中导入了 HomeRouting Module
 * 在 HomeRoutingModule 中定义的路由会合并到根路由表
 * 相当于直接在根模块中定义下面的数组。
 * const routes = [{
 *   path: 'home',
 *   component: HomeContainerComponent
 * }]
 */

const routes: Routes = [
  {path: 'home', component: HomeComponent},
  {path: 'news', component: NewsComponent},
  {path: 'newscontent/:id', component: NewscontentComponent},  // 配置动态路由
  {
    path: '',
    redirectTo: '/home',  // 重定向
    pathMatch: 'full'
	},
  //匹配不到路由的时候加载的组件 或者跳转的路由
  {
     path: '**', /*任意的路由*/
     // component:HomeComponent
     redirectTo:'home'
  }
]

@NgModule({
  /**
   * 根路由使用 `RouterModule.forRoot(routes)` 形式。
   * 而功能模块中的路由模块使用 `outerModule.forChild(routes)` 形式。
   * 启用路由的 debug 跟踪模式,需要在根模块中设置 `enableTracing: true`
   */
  imports: [RouterModule.forRoot(routes, { enableTracing: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

激活路由

找到 app.component.html 根组件模板,配置 router-outlet

通过模版属性访问路由,即路由链接 routerLink

<h1>
  <a [routerLink]="['/home']">首页</a>
  <a [routerLink]="['/home',tab.link]">首页</a><!-- 路径参数 -->
  <a [routerLink]="['/home',tab.link,{name:'val1'}]">首页</a> <!-- 路径对象参数 -->
  <a [routerLink]="['/home']" [queryParams]="{name:'val1'}">首页</a> <!-- 查询参数 -->
</h1>
<router-outlet></router-outlet>  <!-- 路由插座,占位标签 -->
<!--
  路由显示的内容是插入到 router-outlet 的同级的下方节点
  而不是在 router-outlet 中包含
-->
<!--
  当事件处理或者达到某个条件时,可以使用手动跳转
	this.router.navigate(['home']); 
	this.router.navigate(['home',tab.link]); 
	this.router.navigate(['home',tab.link,{name:'val1'}]); 
	this.router.navigate(['home'],{queryParams:{name:'val1'}}); 
-->

控制路由激活状态的样式 routerLinkActive

<h1>
    <a routerLink="/home" routerLinkActive="active">首页</a>
    <a routerLink="/news" routerLinkActive="active">新闻</a>
</h1>

<h1>
   <a [routerLink]="[ '/home' ]" routerLinkActive="active">首页</a>
   <a [routerLink]="[ '/news' ]" routerLinkActive="active">新闻</a>
</h1>

.active{
   color:red;
}

路由参数

路径参数读取

this.route.paramsMap.subscribe(params => {...})

查询参数读取

this.route.queryParamsMap.subscribe(params => {...})

路由传递一个参数及其接收方法:

传递参数:path:’info/:id’

接收参数:

constructor(private routerInfo: ActivatedRoute){}
ngOnInit(){
	this.routerInfo.snapshot.params['id']
}

路由传递多个参数及其接收方法:

传递:[queryParams]=‘{id:1,name:‘crm’}’

接收参数:

constructor(private routerInfo: ActivatedRoute){}
ngOnInit(){
	this.routerInfo.snapshot.params['id']
	this.routerInfo.snapshot.params['name']
}

路由懒加载

懒加载子模块,子模块需要配置路由设置启动子模块 loadChildren

const routes: Routes = [
    {path:'user',loadChildren:'./module/user/user.module#UserModule' },
    {path:'product',loadChildren:'./module/product/product.module#ProductModule'},
    {path:'article',loadChildren:'./module/article/article.module#ArticleModule'},
    {path:'**',redirectTo:'user'}
];

// 上面好像会报错 Error find module 
// 配置懒加载
const routes: Routes = [
    {path:'user',loadChildren:()=>import('./module/user/user.module').then(mod=>mod.UserModule)},
    {path:'article',loadChildren:()=>import('./module/article/article.module').then(mod=>mod.ArticleModule)},
    {path:'product',loadChildren:()=>import('./module/product/product.module').then(mod=>mod.ProductModule)},
    {path:'**',redirectTo:'user'}
];

Angular服务

组件不应该直接获取或保存数据,应该聚焦于展示数据,而把数据访问的职责委托给某个服务

获取数据和视图展示应该相分离,获取数据的方法应该放在服务中

类似 VueX,全局的共享数据(通用数据)及非父子组件传值、共享数据放在服务中

组件之间相互调用各组件里定义的方法

多个组件都用的方法(例如数据缓存的方法)放在服务(service)里

import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root',
})
export class HeroService {
  aa = 'abc';
  constructor(){ }
  ngOnInit(){ }
}

import { HeroService } from '../../../services/hero/hero.service';
export class AComponent implements OnInit{
  constructor(private heroService : HeroService) {} //实例化
  ngOnInit(){
    console.log(this.heroService.aa)
  }
}

@Injectable()装饰器

在 Angular 中,要把一个类定义为服务,就要用 @Injectable() 装饰器来提供元数据,以便让 Angular 把它作为依赖注入到组件中。

同样,也要使用 @Injectable () 装饰器来表明一个组件或其它类(比如另一个服务、管道或 NgModule)拥有一个依赖。

@Injectable () 装饰器把这个服务类标记为依赖注入系统的参与者之一,它是每个 Angular 服务定义中的基本要素。

在未配置好 Angular 的依赖注入器时,Angular 实际上无法将它注入到任何位置。

@Injectable () 装饰器具有一个名叫 providedIn 的元数据选项,providedIn 设置为 'root',即根组件中,那么该服务就可以在整个应用程序中使用了。

providedIn 提供这些值:‘root''platform''any'null

对于要用到的任何服务,必须至少注册一个提供者。

服务可以在自己的元数据中把自己注册为提供者,可以让自己随处可用,也可以为特定的模块或组件注册提供者。

要注册提供者,就要在服务的 @Injectable () 装饰器中提供它的元数据,或者在 @NgModule ()@Component () 的元数据中。

在组件中提供服务时,还可以使用 viewProdiversviewProviders 对子组件树不可见

可以使用不同层级的提供者来配置注入器,也表示该服务的作用范围

  • Angular 创建服务默认采用的方式:在服务本身的 @Injectable () 装饰器中

  • 该服务只在某服务中使用:在 NgModule 的 @NgModule () 装饰器中

  • 该服务在某组件中使用:在组件的 @Component () 装饰器中

依赖注入

在项目中,有人提供服务,有人消耗服务,而依赖注入的机制提供了中间的接口,并替消费者创建并初始化处理

消费者只需要知道拿到的是完整可用的服务就好,至于这个服务内部的实现,甚至是它又依赖了怎样的其他服务,都不需要关注。

Angular 通过 service共享状态,而这些管理状态和数据的服务便是通过依赖注入的方式进行处理的

Angular 的 service 的本质就是依赖注入,将service作为一个Injector注入到component

归根到底,很多时候我们创建服务,是为了维护公用的状态和数据,通过依赖注入的方式来规定哪些组件可共享

在这里插入图片描述

正是因为 Angular 提供的这种依赖注入机制,才能在构造函数中直接声明实例化

  constructor(private heroService : HeroService) {} // 依赖注入

在这里插入图片描述

先看一下 Angular 中 TS 单文件的注入

// 首先写 @injectable 我们需要注入的东西,比如说 product
@Injectable()
class Product {
  constructor(
    private name: string,
    private color: string,
    private price: number,
  ) { }
}

class PurchaseOrder {
  constructor(private product: Product){ }
}
 
export class HomeGrandComponent implements OnInit {
  constructor() { }
  ngOnInit() {
    // 构造一个 injector 用 create 方法 里面 providers 数组中写我们需要构造的东西
    const injector = Injector.create({
      providers: [
        {
          provide: Product,
          // 构造 Product 在 useFactory 中就会把上面定义的 product 注入到这里
          useFactory: () => {
            return new Product('大米手机', '黑色', 2999);
          },
          deps: []
        },
        {
          provide: PurchaseOrder,
          deps: [Product]
        },
        {
          provide: token,
          useValue: { baseUrl: 'http://local.dev' }
        }
      ]
    }); 
    console.log('injector获取product', injector.get(PurchaseOrder).getProduct);
    console.log(injector.get(token));
  }

再看一下Angular 中 module 模块的注入

// .service.ts 中 @Injectable () 依赖注入
@Injectable()
export class HomeService {
  imageSliders: ImageSlider[] = [
    {
      imgUrl:'',
      link: '',
      caption: ''
    }
  ]
  getBanners() {
    return this.imageSliders;
  }
}

// 使用模块对应的.module.ts 中
@NgModule({
  declarations: [
    HomeDetailComponent,
  ],
  providers:[HomeService], // 在 providers 直接写对应服务,直接将服务注入模块
  imports: [SharedModule, HomeRoutingModule]
})

不管是在组件内还是在模块内,我们使用 providers 的时候,就是进行了一次依赖注入的注册和初始化

其实模块类(NgModule)也和组件一样,在依赖注入中是一个注入器,作为容器提供依赖注入的接口

NgModule 使我们不需要在一个组件中注入另一个组件,通过模块类(NgModule)可以进行获取和共享


Angular 管道

Angular 管道是编写可以在 HTML 组件中声明的显示值转换的方法

管道将数据作为输入并将其转换为所需的输出

管道其实就是过滤器,用来转换数据然后显示给用户

管道将整数、字符串、数组和日期作为输入,用 | 分隔,然后根据需要转换格式,并在浏览器中显示出来

在插值表达式中,可以定义管道并根据情况使用

Angular 应用程序中可以使用许多类型的管道

内置管道

  • String -> String
    • UpperCasePipe 转换成大写字符
    • LowerCasePipe 转换成小写字符
    • TitleCasePipe 转换成标题形式,第一个字母大写,其余小写
  • Number -> String
    • DecimalPipe 根据数字选项和区域设置规则格式化值
    • PercentPipe 将数字转换为百分比字符串
    • CurrencyPipe 改变人名币格式
  • Object -> String
    • JsonPipe 对象序列化
    • DatePipe 日期格式转换
  • Tools
    • SlicePipe 字符串截取
    • AsyncPipe 从异步回执中解出一个值
    • I18nPluralPipe 复数化
    • I18nSelectPipe 显示与当前值匹配的字符串

使用方法

<div>{{ 'Angular' | uppercase }}</div>  <!-- Output: ANGULAR -->

<div>{{ data | date:'yyyy-MM-dd' }}</div>  <!-- Output: 2022-05-17 -->

<div>{{ { name: 'ccc' } | json }}</div>  <!-- Output: { "name": "ccc" } -->

<!-- 
	管道可以接收任意数量的参数,使用方式是在管道名称后面添加: 和参数值
	若需要传递多个参数则参数之间用冒号隔开 
-->

<!-- 可以将多个管道连接在一起,组成管道链对数据进行处理 -->
<div>{{ 'ccc' | slice:0:1 | uppercase }}</div>

自定义管道

管道本质上就是个类,在这个类里面去实现 PipeTransfrom 接口的 transform 这个方法

  • 使用 @Pipe 装饰器定义 Pipemetadata 信息,如 Pipe 的名称 - 即 name 属性
  • 实现 PipeTransform 接口中定义的 transform 方法
// 引入PipeTransform是为了继承transform方法
import { Pipe, PipeTransform } form '@angular/core'; 
// name属性值惯用小驼峰写法, name的值为html中 | 后面的名称
@Pipe({ name: 'sexReform' }) 
export class SexReformPipe implements PipeTransform {
    transform(value: string, args?: any): string {
    // value的值为html中 | 前面传入的值, args为名称后传入的参数
        switch(value){
            case 'male': return '男';
            case 'female': return '女';
            default: return '雌雄同体';
        } 
    }
}

// demo.component.ts
export Class DemoComponent {
    sexValue = 'female';
}

// demo.component.html
<span>{{ sexValue | sexReform }}</span>

// 浏览器输出// 管道可以链式使用,还可以传参
<span> {{date | date: 'fullDate' | uppercase}} </span>
// 每一个自定义管道都需要实现 PipeTransform 接口,这个接口非常简单,只需要实现 transform 方法即可。
// transform()方法参数格式 - transform(value: string, args1: any, args2?: any): 
// value为传入的值(即为需要用此管道处理的值, | 前面的值); 
// args 为传入的参数(?:代表可选);
// html 中使用管道格式 - {{ 数据 | 管道名 : 参数1 : 参数2 }}
// 与 component 一样,pipe 需要先在 declarations 数组中声明后使用

Angular操作DOM

原生JS操作

ngAfterViewInit(){
   var boxDom:any=document.getElementById('box');
   boxDom.style.color='red';
}

ElementRef

ElementRef 是对视图中某个原生元素的包装类

因为 DOM 元素不是 Angular 中的类,所以需要一个包装类以便在 Angular 中使用和标识其类型

ElementRef 的背后是一个可渲染的具体元素。在浏览器中,它通常是一个 DOM 元素

class ElementRef<T> {
  constructor(nativeElement: T)
  nativeElement: T  //背后的原生元素,如果不支持直接访问原生元素,则为 null(比如:在 Web Worker 环境下运行此应用的时候)。
}

当需要直接访问 DOM 时,请把本 API 作为最后选择 。优先使用 Angular 提供的模板和数据绑定机制

如果依赖直接访问 DOM 的方式,就可能在应用和渲染层之间产生紧耦合。这将导致无法分开两者,也就无法将应用发布到 Web Worker 中

ViewChild

使用模板和数据绑定机制,使用 @viewChild

// 模版中给 DOM 起一个引用名字,以便可以在组件类或模版中进行引用 <div #myattr></div>

// 引入 ViewChild
import { ViewChild,ElementRef } from '@angular/core';

// 用 ViewChild 绑定 DOM	
@ViewChild('myattr') myattr: ElementRef;

// 在 ngAfterViewInit 生命周期函数里可以很安全的获取 ViewChild 引用的 DOM
ngAfterViewInit(){
   let attrEl = this.myattr.nativeElement;
}

父组件中可以通过 ViewChild 调用子组件的方法

// 给子组件定义一个名称
<app-footer #footerChild></app-footer>

// 引入 ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';

// ViewChild 和子组件关联起来 
// 如果想引用模版中的 Angular 组件,ViewChild 中可以使用引用名,也可以使用组件类型
@ViewChild('footerChild') footer;

// @ViewChild('imageSlider', { static: true }) // static指定是动态还是静态,在*ngFor或者*ngIf中是动态,否则即为静态,动态为 true

// 调用子组件
run(){
   this.footer.footerRun();
}

引用多个模版元素,可以用@ViewChildren,在ViewChildren中可以使用引用名

或者使用 Angular 组件/指令的类型,声明类型为 QueryList<?>

<img
  #img
  *ngFor="let slider of sliders"
  [src]="slider.imgUrl"
  [alt]="slider.capiton"
>

// 使用 ViewChildren 引用获取
@ViewChildren(’img‘);

// 使用类型引用获取
imgs: QueryList<ElementRef>;

Renderer2

Renderer2 是 Angular 提供的操作 element 的抽象类,使用该类提供的方法,能够实现在不直接接触 DOM 的情况下操作页面上的元素。

Renderer2 的常用方法:

  • addClass /removeClassdirective 的宿主元素添加或删除 class
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[testRenderer2]'
})

export class TestRenderer2Directive implements OnInit {
    constructor(private renderer: Renderer2, private el: ElementRef) {} // 实例化

    ngOnInit() {
    this.renderer.addClass(this.el.nativeElement, 'test-renderer2');
    // this.renderer.removeClass(this.el.nativeElement, 'old-class');
    }
}
  • createElement /appendChild/createText 创建 DIV 元素,插入文本内容,并将其挂载到宿主元素上
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('Hello world!');
    
    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.el.nativeElement, div);
}
  • setAttribute /removeAttribute 在宿主元素上添加或删除 attribute
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setAttribute(this.el.nativeElement, 'aria-hidden', 'true');
}
  • setStyle /removeStyle 在宿主元素上添加 inline-style
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setStyle(
        this.el.nativeElement,
        'border-left',
        '2px dashed olive'
    );
}

移除 inline-style :

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.removeStyle(this.el.nativeElement, 'border-left');
}
  • setProperty 设置宿主元素的 property 的值
constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setProperty(this.el.nativeElement, 'alt', 'Cute alligator');
}

直接操作DOM,Angular不推荐。尽量采用 @viewChildrenderer2 组合,Angular推荐使用 constructor(private rd2: Renderer2) {} 依赖注入,

import {
  Component,
  OnInit,
  Renderer2,
  ViewChild,
} from '@angular/core';
import { AboxItemComponent } from './abox-item/abox-item.component';
 
@Component({
  selector: 'app-abox',
  templateUrl: './abox.component.html',
  styleUrls: ['./abox.component.less'],
})
export class AboxComponent implements OnInit {
  private container;
  activeIndex: number;
  @ViewChild('containers') containers: any;
  constructor(private rd2: Renderer2) {}
 
  ngOnInit(): void {}
 
  ngAfterViewInit(): void {
    this.container = this.containers.nativeElement;
    this.initCarouselWidth();
  }
    
  initCarouselWidth() {
    this.rd2.setStyle(this.container, 'width', '100px');
  }
}

Angular网络请求

HttpClient

需导入 HttpClientModule ,只在根模块中导入,并且整个应用只需导入一次,不用在其他模块导入

在构造函数中注入HttpClientget/post方法对应HTTP方法,这些方法是泛型的,可以直接把返回的JSON转换成对应类型。若是不规范的请求,使用request方法

返回的值是 Observable,必须订阅才会发送请求,否则不会发送

get 请求数据

// 在 app.module.ts 中引入 HttpClientModule 并注入
import {HttpClientModule} from '@angular/common/http';
imports: [
  BrowserModule,
  HttpClientModule
]

// 在用到的地方引入 HttpClient 并在构造函数声明
import {HttpClient} from "@angular/common/http";
constructor(private http: HttpClient,private cd: ChangeDetectorRef) { } // 依赖注入

// get 请求数据
var api = "http://baidu.com/api/productlist";
this.http.get(api).subscribe(response => {
  console.log(response);
  this.cd.markForCheck()// 如果改变了脏值检测的变更原则 changeDetection: ChangeDetectionStrategy.OnPush
  // 则需要使用 this.cd.markForCheck() 手动提醒 Angular 这里需要进行脏值检测
});

post 提交数据

// 在 app.module.ts 中引入 HttpClientModule 并注入
import {HttpClientModule} from '@angular/common/http';
imports: [
   BrowserModule,
   HttpClientModule
]

// 在用到的地方引入 HttpClient 、HttpHeaders 并在构造函数声明 HttpClient
import {HttpClient,HttpHeaders} from "@angular/common/http";
constructor(private http:HttpClient) { } // 实例化

// post 提交数据
const httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
var api = "http://127.0.0.1:4200/doLogin";
this.http.post(api,{username:'瑞萌萌',age:'22'},httpOptions).subscribe(response => {
		console.log(response);
});

Jsonp请求数据

// 在 app.module.ts 中引入 HttpClientModule、HttpClientJsonpModule 并注入
import {HttpClientModule,HttpClientJsonpModule} from'@angular/common/http';
imports: [
   BrowserModule,
   HttpClientModule,
   HttpClientJsonpModule
]

// 在用到的地方引入 HttpClient 并在构造函数声明
import {HttpClient} from "@angular/common/http";
constructor(private http:HttpClient) { } // 实例化

// jsonp 请求数据
var api = "http://baidu.com/api/productlist";
this.http.jsonp(api,'callback').subscribe(response => {
   console.log(response);
});

拦截器

Angular 拦截器是 Angular 应用中全局捕获和修改 HTTP 请求和响应的方式,例如携带 Token 和捕获 Error

前提是只能拦截使用 HttpClientModule 发出的请求,如果使用 axios 则拦截不到

创建拦截器

// 使用命令 ng g interceptor name,在这里创建拦截器 ng g interceptor LanJieQi
// cli 生成拦截器是没有简写方式的

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  // 默认的 intercept() 方法只是单纯的将请求转发给下一个拦截器(如果有),并最终返回 HTTP 响应体的 Observable
  // request: HttpRequest<unknown> 表示请求对象,包含了请求相关的所有信息,unknown指定请求体body的类型
  // next: HttpHandler 请求对象修改完成,将修改后的请求对象通过next中的handle方法传回真正发送请求的方法中
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // next 对象表示拦截器链表中的下一个拦截器(在应用中可以设置多个拦截器)
    return next.handle(request);
  }
}

注入拦截器

// 在 @NgModule 模块中注入拦截器
// 拦截器也是一个由 Angular 依赖注入 (DI) 系统管理的服务,也必须先提供这个拦截器类,才能使用它
// 由于拦截器是 HttpClient 服务的依赖,所以必须在提供 HttpClient 的同一个(或其各级父注入器)注入器中提供这些拦截器
@NgModule({
  imports: [
    HttpClientModule
    // others...
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LanJieQiInterceptor,
      // multi: true 表明 HTTP_INTERCEPTORS 是一个多重提供者的令牌,表示这个令牌可以注入多个拦截器
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

请求头拦截

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // 为了统一设置请求头,需要修改请求
    // 但 HttpRequest 和 HttpResponse 实例的属性却是只读(readonly)的
    // 所以修改前需要先 clone 一份,修改这个克隆体后再把它传给 next.handle()
    let req = request.clone({
    	setHeaders:{
      	token:"123456" // 在请求头中增加 token:123456
    	}
			// setHeaders 和 headers: request.headers.set('token', '123456') 一致
  	})
  	return next.handle(req)// 将修改后的请求返回给应用
  }
}

响应捕获

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    let req = request.clone({
      setHeaders:{
        token:"123456" // 在请求头中增加 token:123456
      }
    })
    return next.handle(req).pipe(
      retry(2), // RxJS的重试操作符 retry 发现异常后自动再请求2次
      catchError((error:HttpErrorResponse) => throwError(error))
    )
  }
}

如果有多个拦截器,请求顺序是按照配置顺序执行,响应拦截则是相反的顺序

如果提供拦截器的顺序是先 A再 B再 C,那么请求阶段的执行顺序就是 A->B->C,而响应阶段的执行顺序则是 C->B->A


Angular表单

模版驱动表单

模板驱动表单在往应用中添加简单的表单时非常有用,但是不像响应式表单那么容易扩展

如果有非常基本的表单需求和简单到能用模板管理的逻辑,就使用模板驱动表单

响应式表单和模板驱动表单共享了一些底层构造块:

FormControl 实例用于追踪单个表单控件的值和验证状态

FormGroup 用于追踪一个表单控件组的值和状态

FormArray 用于追踪表单控件数组的值和状态,有长度属性,通常用来代表一个可以增长的字段集合

ControlValueAccessor 用于在 Angular 的 FormControl 实例和原生 DOM 元素之间创建一个桥梁

FormControlFormGroup 是 angular 中两个最基本的表单对象

FormControl 代表单一的输入字段,它是 Angular 表单中最小单员,它封装了这些字段的值和状态,比如是否有效、是否脏(被修改过)或是否有错误等

FormGroup 可以为一组 FormControl 提供总包接口(wrapper interface),来管理多个 FormControl

当我们试图从 FormGroup 中获取 value 时,会收到一个 “键值对” 结构的对象

它能让我们从表单中一次性获取全部的值而无需逐一遍历 FormControl,使用起来相当顺手

FormGroupFormControl 都继承自同一个祖先 AbstractControltractControl(这是 FormControlFormGroupFormArray 的基类)

首先加载 FormsModule

// 先在 NgModule 中导入了 FormsModule 表单库
// FormsModule 为我们提供了一些模板驱动的指令,例如:ngModel、NgForm
import { 
  FormsModule
} from '@angular/forms'; 

@NgModule({ 
  declarations: [ 
    FormsDemoApp, 
    DemoFormSku, 
    // ... our declarations here 
  ], 
  imports: [ 
    BrowserModule, 
    FormsModule,
  ], 
  bootstrap: [ FormsDemoApp ] 
}) 
class FormsDemoAppModule {}

接下来创建一个模版表单

 <div>
      <h2>基础表单:商品名称</h2>
      <form #f="ngForm" (ngSubmit)="onSubmit(f.value)">
        <div class="sku">
          <label for="skuInput">商品名称:</label>
          <input
            type="text"
            id="skuInput"
            placeholder="商品名称"
            name="sku" //使用form时必须定义,可以理解为当前控件的名字
            ngModel
          />
        </div>
        <button>提交</button>
      </form>
    </div>

我们导入了 FormsModule,因此可以在视图中使用 NgForm

当这些指令在视图中可用时,它就会被附加到任何能匹配其 selector 的节点上

NgForm 做了一件便利但隐晦的工作:它的选择器包含 form 标签(而不用显式添加 ngForm 属性)

这意味着当导入 FormsModule 时候,NgForm 就会被自动附加到视图中所有的标签上

NgForm 提供了两个重要的功能:

  • 一个 ngFormFormGroup 对象
  • 一个输出事件 (ngSubmit)
 <form #f="ngForm" (ngSubmit)="onSubmit(f.value)" >
 <!-- 
	这里使用了 #f=“ngForm”,#v=thing 的意思是我们希望在当前视图中创建一个局部变量
	这里为视图中的 ngForm 创建了一个别名,并绑定到变量 #f
	这个 ngForm 是由 NgForm 指令导出的
	ngForm 的类型的对象是 FormGroup 类型的
	这意味着可以在视图中把变量 f 当作 FormGroup 使用,而这也正是我们在输出事件 (ngSubmit) 中的使用方法
	在表单中绑定 ngSubmit 事件 (ngSubmit)=“onSubmit (f.value)“
	(ngSubmit) 来自 NgForm 指令
	onSubmit() 将会在组件类中进行定义
	f 就是 FormGroup ,而 .value 会以键值对的形式返回 FormGroup 中所有控件的值
	
	总结:当提交表单时,将会以该表单的值作为参数,调用组件实例上的 `onSubmit` 方法
-->

NgModel 会创建一个新的 FormControl 对象,把它自动添加到父 FormGroup 上(这里也就是 form 表单对象)

并把这个 FormControl 对象绑定到一个 DOM 上

也就是说,它会在视图中的 input 标签和 FormControl 对象之间建立关联

这种关联是通过 name 属性建立的,在本例中是 "name"

响应式表单

使用 ngForm 构建 FormControlFormGroup 很方便,但是无法提供定制化选项,因此引入响应式表单

响应式表单提供了一种模型驱动的方式来处理表单输入,其中的值会随时间而变化

使用响应式表单时,通过编写 TypeScript 代码而不是 HTML 代码来创建一个底层的数据模型

在这个模型定义好以后,使用一些特定的指令将模板上的 HTML 元素与底层的数据模型连接在一起

FormBuilder 是一个名副其实的表单构建助手(可以把他看作一个 “工厂” 对象)

在先前的例子中添加一个 FormBuilder,然后在组件定义类中使用 FormGroup

// 先在 NgModule 中导入了 ReactiveFormsModule 表单库
import { 
  ReactiveFormsModule 
} from '@angular/forms'; 
@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
}) 

// 使用 formGroup 和 formControl 指令来构建这个组件,需要导入相应的类
import { 
  FormBuilder, 
  FormGroup,
  ReactiveFormsModule
} from '@angular/forms'; 

// 在组件类上注入一个从 FormBuilder 类创建的对象实例,并把它赋值给 fb 变量(来自构造函数)
export class DemoFormSkuBuilder { 
  myForm: FormGroup;  // myForm 是 FormGroup 类型
  constructor(fb: FormBuilder) { 
    // FormBuilder 中的 group 方法用于创建一个新的 FormGroup
    // group 方法的参数是代表组内各个 FormControl 的键值对
    this.myForm = fb.group({  // 调用 fb.group () 来创建 FormGroup
      // 设置一个名为 sku 的控件,控件的默认值为 "123456"
      'sku': ['123456'] 
    }); 
  }
  onSubmit(value: string): void { 
    console.log('submit value:', value); 
  } 
}

在视图表单中使用自定义的 FormGroup

<h2 class="ui header">Demo Form: Sku with Builder</h2>
<!--  
	当导入 FormsModule 时,ngForm 就会自动创建它自己的 FormGroup
	但这里不希望使用外部的 FormGroup,而是使用 FormBuilder 创建这个 myForm 实例变量
	Angular提供了 formGroup 指令,能让我们使用现有的 FormGroup
	NgForm 不会应用到带 formGroup 属性的节点上
	这里我们告诉Angular,想用 myForm 作为这个表单的 FormGroup
-->
<form [formGroup]="myForm" 
  <label for="skuInput"> SKU </label> 
  <input type="text" 
     id="skuInput" 
     placeholder="SKU" 
     [formControl]="myForm.controls['sku']">
<!--  
	将 FormControl 绑定到 input 标签上 : 
	ngModel 会创建一个新的 FormControl 对象并附加到父 FormGroup 中
	但在例子中,我们已经用 FormBuilder 创建了自己的 FormControl
	要将现有的 FormControl 绑定到 input 上,可以用 formControl 指令
	将 input 标签上的 formControl 指令指向 myForm.controls 上现有的 FormControl 控件 sku  
-->

记住以下两点:

  1. 如果想隐式创建新的 FormGroup 和 FormControl,使用:ngForm、ngModel
  2. 如果要绑定一个现有的 FormGroup 和 FormControl,使用:formGroup、formControl

表单验证

用户输入的数据格式并不总是正确的,如果有人输入错误的数据格式,我们希望给他反馈并阻止他提交表单

因此,我们要用到验证器,由 validators 模块提供

Validators.required 是最简单的验证,表明指定的字段是必填项,否则就认为 FormControl 是无效的

如果 FormGroup 中有一个 FormControl 是无效的, 那整个 FormGroup 都是无效的

要为 FormControl 对象分配一个验证器 ,可以直接把它作为第二个参数传给 FormControl 的构造函数

const control = new FormControl('name', Validators.required);

// 在组件定义类中使用 FormBuilder
  constructor(fb: FormBuilder) { 
    this.myForm = fb.group({ 
      'name': ['',Validators.required] 
    }); 
    this.name = this.myForm.controls['name']; 
  } 

在视图中检查验证器的状态,并据此采取行动

template:`<div>
      <h2>商品表单:商品名称</h2>
      <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
        <div>
          <label for="nameInput">商品名称:</label>
          <input
            type="text"
            id="nameInput"
            placeholder="请输入名称"
            [formControl]="myForm.controls['name']"
          />
          <div style="color:red" *ngIf="!name.valid">
            名称无效
          </div>
          <div style="color:red" *ngIf="name.hasError('textinvalid')">
            名称不是以“123”开头
          </div>
          <div *ngIf="name.dirty">
            数据已变动
          </div>
        </div>
        <div>
          <label for="codeInput">商品料号:</label>
          <input
            type="text"
            id="codeInput"
            placeholder="请输入料号"
            [formControl]="myForm.controls['code']"
          />
          <div
            style="color:red"
            *ngIf="myForm.controls.code.hasError('required')"
          >
            该项必填
          </div>
          <div
            style="color:red"
            *ngIf="myForm.controls.code.hasError('pattern')"
          >
            只可输入数字和英文
          </div>
        </div>
        <div style="color:green" *ngIf="myForm.isvalid">
          表单无效
        </div>
        <div style="color:green" *ngIf="myForm.valid">
          表单有效
        </div>
        <button type="submit">提交</button>
      </form>
    </div>`
export class NonInWarehouseComponent implements OnInit {
  myForm: FormGroup;
  name: AbstractControl;
  constructor(fb: FormBuilder) {
    this.myForm = fb.group({
      name: ['牛奶', Validators.compose([Validators.required, textValidator])],
      code: ['', [Validators.required, Validators.pattern('^[A-Za-z0-9]*$')]],
    });
    this.name = this.myForm.controls.name;
  }
  ngOnInit() {
    const nameControl = new FormControl('nate');
    console.log('nameControl', nameControl);
  }
  onSubmit(a: any) {
    console.log('a', a);
  }
}

内置校验器

Angular 提供了几个内置校验器,下面是比较常用的校验器:

  • Validators.required - 表单控件值非空
  • Validators.email - 表单控件值的格式是 email
  • Validators.minLength() - 表单控件值的最小长度
  • Validators.maxLength() - 表单控件值的最大长度
  • Validators.pattern() - 表单控件的值需匹配 pattern 对应的模式(正则表达式)

自定义验证器

假设我们的 name 有特殊的验证需求,比如 name 必须以 123 作为开始

当输入值(控件的值 control.value)不是以 123 作为开始时,验证器会返回错误代码 invalidSku

// angular 源代码中实现 Validators.required 
export class Validators {
  // 接收一个 AbstractControl 对象作为输入
	static required(control: AbstractControl): ValidationErrors | null;
}
// 当验证器失败时,会返回一个 String Map<string,any> 对象,他的键是” 错误代码 “,它的值是 true
export declare type ValidationErrors = {
    [key: string]: any;
};

// 自定义验证器
function textValidator(
  controls: FormControl // 因为FormControl继承于 AbstractControl 所以也可以写成FormControl对象
): {
  [s: string]: boolean;
} {
  if (!controls.value.match(/^123/)) {
    return { textinvalid: true };
  }
}

FormControl 分配验证器,但是 name 已经有一个验证器了,如何在同一个字段上添加多个验证器

Validators.compose 来实现

Validators.compose 把两个验证器包装在一起,我们可以将其赋值给 FormControl

只有当两个验证器都合法时,FormControl 才是合法的

Validators.compose([Validators.required, textValidator])
// 不用compose   [Validators.required, textValidator]
// 保留 compose 是为了向以前历史版本进行兼容,不用 compose 也可实现

动态表单

要实现 Angular 动态表单,主要使用 formArray 方法,formArray 生成的实例是一个数组,在这个数组中可以动态的放入 formGroupformControl,这样便形成了动态表单。

export class ReativeFormsComponent implements OnInit {
  ngOnInit() {
    this.addContact()
  }
  //动态表单
  personMess: FormGroup = new FormGroup({
    //生成动态表单数组
    contacts: new FormArray([]) 
  })
  //获取数组对象
  get contacts(){
    return this.personMess.get('contacts') as FormArray
  }
  //增加一个表单组
  addContact(){
    let myContact = new FormGroup({
      name: new FormControl(),
      phone: new FormControl()
    })
    this.contacts.push(myContact)
  }
   //删除一个表单组
  deleteContact(i:number){
    this.contacts.removeAt(i)
  }
  //提交表单
  OnSubmit() {
    console.log(this.personMess.value)
  }
}
<form [formGroup]="personMess" (submit)="OnSubmit()">
  <div formArrayName="contacts">
    <!-- 注意:这里遍历的时contacts.controls -->
    <div *ngFor="let contact of contacts.controls;let i =index" [formGroupName]="i">
      <input type="text" formControlName="name">
      <input type="text" formControlName="phone">
      <button (click)="deleteContact(i)">删除信息</button>
    </div>
  </div>
  <button (click)="addContact()">添加信息</button><br>
  <input type="submit">
</form>

Angular CDK

CDK 是 Component Dev kit 的简称,是 Angular Material 团队在开发 Library 时发现组件有很多相似的地方,最后进行了抽取,提炼出了公共的逻辑,这部分即是 CDK

官方用了一个很形象的比喻:如果组件库是火箭飞船,那么 CDK 就是发动机零件盒
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/409757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

若依框架(前后端分离)打war包部署到linux

一、前端部署 1.找到ruoyi-ui目录。 2.安装依赖。 npm install 3.执行以下操作&#xff0c;解决 npm 下载速度慢的问题。 npm install --registryhttps://registry.npmmirror.com 4.修改vue.config.js,若后端采用的是默认8080端口&#xff0c;则不用修改&#xff0c;默认就是…

2023最新最全vscode插件精选

文章简介 本文介绍最新、最实用、最强大的 vscode 精选扩展。好用的扩展&#xff0c;犹如神兵利器&#xff0c;帮助程序员在代码的世界中&#xff0c;所向披靡&#xff0c;战无不胜&#xff01; 作者介绍 随易出品&#xff0c;必属精品&#xff0c;只写有深度&#xff0c;有质…

vue 路由钩子

路由钩子分为三种 全局钩子&#xff1a; beforeEach、 afterEach、beforeResolve单个路由里面的钩子&#xff1a; beforeEnter组件路由&#xff1a;beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave 它的三个参数&#xff1a; to: (Route路由对象) 即将要进入的目标…

【前端知识体系梳理(三)】Diff策略

​ 目录 &#x1f349;前言 &#x1f349;传统Diff算法 &#x1f349;React Diff &#x1f353;&#x1f353;&#x1f353;1、tree diff &#x1f353;&#x1f353;&#x1f353;2、component diff &#x1f353;&#x1f353;&#x1f353;3、element diff &#x1…

前端页面项目——博客系统

目录 1.实现博客列表页 1.1 实现导航栏 1.2 实现中间版心 1.3 实现个人信息 1.4 实现博客列表 2. 实现博客正文页 3. 实现博客登陆页 4. 实现博客编辑 4.1 实现编辑区 4.2 引入编辑器 展示 1&#xff09;登录页面 2&#xff09;博客列表页 3&#xff09;博客详情页 4&am…

【JavaScript】手撕前端面试题:手写Object.create | 手写Function.call | 手写Function.bind

&#x1f5a5;️ NodeJS专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ 博主的前端之路&#xff08;源创征文一等奖作品&#xff09;&#xff1a;前端之行&#xff0c;任重道远&#xff08;来自大三学长的万字自述&#xff09; &#x1f5a5;️ TypeScript知识总结&…

PyQt5之进度条:QProgressBar

PyQt5之进度条&#xff1a;QProgressBar 在软件中&#xff0c;在处理特别冗长的任务时&#xff0c;如果没有相关的进度信息&#xff0c;这个等待的过程会比较考验用户的耐心&#xff0c;根据相关理论&#xff0c;进度条可以缓解用户在等待过程中的焦虑&#xff0c;所以&#x…

前端学习笔记(14)-Vue3组件传参

1.props&#xff08;父组件传递给子组件&#xff09;1.1 实现如果你没有使用 <script setup>&#xff0c;props 必须以 props 选项的方式声明&#xff0c;props 对象会作为 setup() 函数的第一个参数被传入&#xff1a;在子组件中&#xff1a;export default {props: {ti…

微信小程序头像昵称填写能力

1、基本介绍 微信小程序获取头像昵称的能力&#xff0c;最近又进行了一次调整&#xff0c;如果没有记错这是今年第三次调整了&#xff0c;每次调整每个开发者心中我相信都跟我一样&#xff0c;万马奔腾。。。今天写个demo体验下实际效果如何。 详细信息请见小程序用户头像昵称…

微信小程序实现PDF预览功能——pdf.js(含源码解析)

文章目录前言一、pdf.js 是什么&#xff1f;二、使用步骤1.下载库文件2.使用方式微信小程序端——使用 web-view 标签H5 端——使用 iframe 标签&#xff08;使用vue框架&#xff09;3.更改源码如何隐藏顶部工具栏如何让用户强制阅读一定时间如何获取pdf总页数如何获取pdf当前页…

【折腾电脑】Edge浏览器看B站视频卡顿最全解决办法合集

开头碎碎念&#xff1a;更新频率明显和疫情呈正相关&#xff0c;祝大家健健康康吃好喝好&#xff01; 使用Microsoft Edge浏览器观看B站视频&#xff0c;卡得无法忍受。 在网络上搜索相关问题&#xff0c;最早的一条是2016/04/17微软问题反馈的记录。任何原因的卡顿都是正常的&…

Vue样式穿透

Vue样式穿透 vue文件的style标签的scoped属性作用&#xff1a;PostCSS在元素标签上添加特殊属性值&#xff0c;在样式的选择器后面添加属性选择器&#xff0c;实现了组件样式的私有化&#xff0c;防止组件之间的样式污染&#xff08;比如相同类名的元素&#xff09;。 但在使…

【CSS】盒子模型内边距 ② ( 内边距复合写法 | 代码示例 )

文章目录一、内边距复合写法1、语法2、代码示例 - 设置 1 个值3、代码示例 - 设置 2 个值4、代码示例 - 设置 3 个值5、代码示例 - 设置 4 个值一、内边距复合写法 1、语法 盒子模型内边距 可以通过 padding-left 左内边距padding-right 右内边距padding-top 上内边距padding-…

前端开发服务器中的 Proxy 代理跨域实现原理解读

各位朋友你们好&#xff0c;我是桃小瑞&#xff0c;微信公众 桃小瑞。在这给大家拜个晚年&#xff0c;祝各位朋友新年快乐。 前言 在前端的开发过程中&#xff0c;尤其是在浏览器环境下&#xff0c;跨域是个绕不开的话题&#xff0c;相信每个前端都会涉及到这个问题&#xf…

“write javaBean error, fastjson version 1.2.83, class org.apache.shiro.web.servlet.ShiroHttpServletR

1. 相关技术 springboot 2.6.3mybatis-spring-boot-starter 2.2.2mybatis 3.5.10fastjson 1.2.83hutool-all 5.7.22shiro-spring 1.8.0 2. 报错信息 "write javaBean error, fastjson version 1.2.83, class org.apache.shiro.web.servlet.ShiroHttpServletRequest, meth…

<router-view> can no longer be used directly inside <transition> or <keep-alive>.

百度翻译&#xff1a; &#xff1c;router view&#xff1e;不能直接在&#xff1c;transition&#xff1e;或&#xff1c;keep alive&#xff1e;中使用。 改用插槽道具&#xff1a; 运行环境&#xff1a; "vue": "^3.2.8", "vue-router": &quo…

idea的vue文件中使用ElementUi组件

作为计算机专业的学生&#xff0c;在做实训项目时很惆怅前端页面的搭建&#xff0c;这个时候就突出到了组件的好处&#xff1b; 这篇就是给大家展示使用ElementUi组件&#xff01;&#xff01;&#xff01; 内容上分为vue3和之前的版本&#xff0c;自行选择&#xff01;&#x…

33.JavaScript映射与集合(Map、Set)数据类型基础知识介绍与使用

文章目录映射与集合&#xff08;Map、Set&#xff09;映射&#xff08;Map&#xff09;Map常用的方法不要使用map[key]访问属性对象作为Map的键Map的遍历与迭代默认的迭代方式forEach()从数组、对象创建Map从数组、Map创建对象集合&#xff08;Set&#xff09;集合迭代总结映射…

Vuex 之一:3种拿到 state 中数据的方式与实例剖析

Ⅰ、Vuex 简介&#xff1a; 1、Vuex 是什么&#xff1f; 答&#xff1a;Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式&#xff1b; 而所谓状态就是指&#xff1a;组件中所维护的数据); (简而言之&#xff1a;就是状态管理&#xff0c;解决复杂组件数据通信&#xff0c…

React中实现keepalive组件缓存效果

背景&#xff1a;由于react官方并没有提供缓存组件相关的api&#xff08;类似vue中的keepalive&#xff09;&#xff0c;在某些场景&#xff0c;会使得页面交互性变的很差&#xff0c;比如在有搜索条件的表格页面&#xff0c;点击某一条数据跳转到详情页面&#xff0c;再返回表…