当前位置:   article > 正文

Angular终极教程_angular教程

angular教程

B站找到一个Angular的教程,个人感觉讲的清楚明白,分享给大家:B站链接

RxJS快速入门 快速跳转

NgRx 点我

贴一下文档吧

Angular 企业实战开发


1. 概述

Angular 是一个使用 HTML、CSS、TypeScript 构建客户端应用的框架,用来构建单页应用程序。

Angular 是一个重量级的框架,内部集成了大量开箱即用的功能模块。

Angular 为大型应用开发而设计,提供了干净且松耦合的代码组织方式,使应用程序整洁更易于维护。

Angular Angular 中文 Angular CLI

2. 架构预览

2.1 模块

Angular 应用是由一个个模块组成的,此模块指的不是ESModule,而是 NgModule 即 Angular 模块。

NgModule 是一组相关功能的集合,专注于某个应用领域,可以将组件和一组相关代码关联起来,是应用组织代码结构的一种方式。

在 Angular 应用中至少要有一个根模块,用于启动应用程序。

NgModule 可以从其它 NgModule 中导入功能,前提是目标 NgModule 导出了该功能。

NgModule 是由 NgModule 装饰器函数装饰的类。

  1. import { BrowserModule } from'@angular/platform-browser';
  2. import { NgModule } from'@angular/core';
  3. @NgModule({
  4. imports: [
  5. BrowserModule
  6. ]
  7. })
  8. export class AppModule { }
2.2 组件

组件用来描述用户界面,它由三部分组成,组件类、组件模板、组件样式,它们可以被集成在组件类文件中,也可以是三个不同的文件。

组件类用来编写和组件直接相关的界面逻辑,在组件类中要关联该组件的组件模板和组件样式。

组件模板用来编写组件的 HTML 结构,通过数据绑定标记将应用中数据和 DOM 进行关联。

组件样式用来编写组件的组件的外观,组件样式可以采用 CSS、LESS、SCSS、Stylus

在 Angular 应用中至少要有一个根组件,用于应用程序的启动。

组件类是由 Component 装饰器函数装饰的类。

  1. import { Component } from"@angular/core"
  2. @Component({
  3. selector: "app-root",
  4. templateUrl: "./app.component.html",
  5. styleUrls: ["./app.component.css"]
  6. })
  7. export class AppComponent {
  8. title="angular-test"
  9. }

NgModule 为组件提供了编译的上下文环境。

  1. import { NgModule } from'@angular/core';
  2. import { AppComponent } from'./app.component';
  3. @NgModule({
  4. declarations: [
  5. AppComponent
  6. ],
  7. bootstrap: [AppComponent]
  8. })
  9. export class AppModule { }
2.3 服务

服务用于放置和特定组件无关并希望跨组件共享的数据或逻辑。

服务出现的目的在于解耦组件类中的代码,是组件类中的代码干净整洁。

服务是由 Injectable 装饰器装饰的类。

  1. import { Injectable } from'@angular/core';
  2. @Injectable({})
  3. export class AppService { }

在使用服务时不需要在组件类中通过 new 的方式创建服务实例对象获取服务中提供的方法,以下写法错误,切记切记!!!

  1. import { AppService } from"./AppService"
  2. export class AppComponent {
  3. let appService=new AppService()
  4. }

服务的实例对象由 Angular 框架中内置的依赖注入系统创建和维护。服务是依赖需要被注入到组件中。

在组件中需要通过 constructor 构造函数的参数来获取服务的实例对象。

涉及参数就需要考虑参数的顺序问题,因为在 Angular 应用中会有很多服务,一个组件又不可能会使用到所有服务,如果组件要使用到最后一个服务实例对象,难道要将前面的所有参数都写上吗 ? 这显然不合理。

在组件中获取服务实例对象要结合 TypeScript 类型,写法如下。

  1. import { AppService } from"./AppService"
  2. export class AppComponent {
  3. constructor (
  4. private appService: AppService
  5. ) {}
  6. }

Angular 会根据你指定的服务的类型来传递你想要使用的服务实例对象,这样就解决了参数的顺序问题。

在 Angular 中服务被设计为单例模式,这也正是为什么服务可以被用来在组件之间共享数据和逻辑的原因。

<!--3. 快速开始-->

3.1 创建应用
  1. 安装 angular-cli:npm install @angular/cli -g

  1. 创建应用:ng new angular-test --minimal --inlineTemplate false

  1. --skipGit=true

  1. --minimal=true

  1. --skip-install

  1. --style=css

  1. --routing=false

  1. --inlineTemplate

  1. --inlineStyle

  1. --prefix

  1. 运行应用:ng serve

  1. --open=true 应用构建完成后在浏览器中运行

  1. --hmr=true 开启热更新

  1. hmrWarning=false 禁用热更新警告

  1. --port 更改应用运行端口

  1. 访问应用:localhost:4200

3.2 默认代码解析
3.2.1 main.ts
  1. // enableProdMode 方法调用后将会开启生产模式
  2. import { enableProdMode } from"@angular/core"
  3. // Angular 应用程序的启动在不同的平台上是不一样的
  4. // 在浏览器中启动时需要用到 platformBrowserDynamic 方法, 该方法返回平台实例对象
  5. import { platformBrowserDynamic } from"@angular/platform-browser-dynamic"
  6. // 引入根模块 用于启动应用程序
  7. import { AppModule } from"./app/app.module"
  8. // 引入环境变量对象 { production: false }
  9. import { environment } from"./environments/environment"
  10. // 如果当前为生产环境
  11. if (environment.production) {
  12. // 开启生产模式
  13. enableProdMode()
  14. }
  15. // 启动应用程序
  16. platformBrowserDynamic()
  17. .bootstrapModule(AppModule)
  18. .catch(err=>console.error(err))
3.2.2 environment.ts

// 在执行 `ng build --prod` 时, environment.prod.ts 文件会替换 environment.ts 文件

// 该项配置可以在 angular.json 文件中找到, projects -> angular-test -> architect -> configurations -> production -> fileReplacements

  1. export const environment= {
  2. production: false
  3. }
3.2.3 environment.prod.ts
  1. export const environment= {
  2. production: true
  3. }
3.2.4 app.module.ts
  1. // BrowserModule 提供了启动和运行浏览器应用所必需的服务
  2. // CommonModule 提供各种服务和指令, 例如 ngIf 和 ngFor, 与平台无关
  3. // BrowserModule 导入了 CommonModule, 又重新导出了 CommonModule, 使其所有指令都可用于导入 BrowserModule 的任何模块
  4. import { BrowserModule } from"@angular/platform-browser"
  5. // NgModule: Angular 模块装饰器
  6. import { NgModule } from"@angular/core"
  7. // 根组件
  8. import { AppComponent } from"./app.component"
  9. // 调用 NgModule 装饰器, 告诉 Angular 当前类表示的是 Angular 模块
  10. @NgModule({
  11. // 声明当前模块拥有哪些组件
  12. declarations: [AppComponent],
  13. // 声明当前模块依赖了哪些其他模块
  14. imports: [BrowserModule],
  15. // 声明服务的作用域, 数组中接收服务类, 表示该服务只能在当前模块的组件中使用
  16. providers: [],
  17. // 可引导组件, Angular 会在引导过程中把它加载到 DOM 中
  18. bootstrap: [AppComponent]
  19. })
  20. export class AppModule {}
3.2.5 app.component.ts
  1. import { Component } from"@angular/core"
  2. @Component({
  3. // 指定组件的使用方式, 当前为标记形式
  4. // app-home => <app-home></app-home>
  5. // [app-home] => <div app-home></div>
  6. // .app-home => <div class="app-home"></div>
  7. selector: "app-root",
  8. // 关联组件模板文件
  9. // templateUrl:'组件模板文件路径'
  10. // template:`组件模板字符串`
  11. templateUrl: "./app.component.html",
  12. // 关联组件样式文件
  13. // styleUrls : ['组件样式文件路径']
  14. // styles : [`组件样式`]
  15. styleUrls: ["./app.component.css"]
  16. })
  17. export class AppComponent {}
3.2.6 index.html
  1. <!doctype html>
  2. <htmllang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>AngularTest</title>
  6. <base href="/">
  7. <meta name="viewport"content="width=device-width, initial-scale=1">
  8. <link rel="icon"type="image/x-icon"href="favicon.ico">
  9. </head>
  10. <body>
  11. <app-root></app-root>
  12. </body>
  13. </html>
3.3 共享模块

共享模块当中放置的是 Angular 应用中模块级别的需要共享的组件或逻辑。

  1. 创建共享模块: ng g m shared

  1. 创建共享组件:ng g c shared/components/Layout

  1. 在共享模块中导出共享组件

  1. @NgModule({
  2. declarations: [LayoutComponent],
  3. exports: [LayoutComponent]
  4. })
  5. export class SharedModule {}
  1. 在根模块中导入共享模块

  1. @NgModule({
  2. declarations: [AppComponent],
  3. imports: [SharedModule],
  4. bootstrap: [AppComponent]
  5. })
  6. export class AppModule {}
  1. 在根组件中使用 Layout 组件

  1. @Component({
  2. selector: "app-root",
  3. template: `
  4. <div>App works</div>
  5. <app-layout></app-layout>
  6. `,
  7. styles: []
  8. })
  9. export class AppComponent { }

4. 组件模板

4.1 数据绑定

数据绑定就是将组件类中的数据显示在组件模板中,当组件类中的数据发生变化时会自动被同步到组件模板中(数据驱动 DOM )。

在 Angular 中使用差值表达式进行数据绑定,即 {{ }} 大胡子语法

  1. <h2>{{message}}</h2>
  2. <h2>{{getInfo()}}</h2>
  3. <h2>{{a == b ? '相等': '不等'}}</h2>
  4. <h2>{{'Hello Angular'}}</h2>
  5. <p[innerHTML]="htmlSnippet"></p><!-- 对数据中的代码进行转义 -->
4.2 属性绑定
4.2.1 普通属性

属性绑定分为两种情况,绑定 DOM 对象属性和绑定HTML标记属性。

  1. 使用 [属性名称] 为元素绑定 DOM 对象属性。

<img [src]="imgUrl"/>
  1. 使用 [attr.属性名称] 为元素绑定 HTML 标记属性

<td [attr.colspan]="colSpan"></td>

在大多数情况下,DOM 对象属性和 HTML 标记属性是对应的关系,所以使用第一种情况。但是某些属性只有 HTML 标记存在,DOM 对象中不存在,此时需要使用第二种情况,比如 colspan 属性,在 DOM 对象中就没有,或者自定义 HTML 属性也需要使用第二种情况。

4.2.2 class 属性
  1. <button class="btn btn-primary"[class.active]="isActive">按钮</button>
  2. <div [ngClass]="{'active': true, 'error': true}"></div>
4.2.3 style 属性
  1. <button [style.backgroundColor]="isActive ? 'blue': 'red'">按钮</button>
  2. <button [ngStyle]="{'backgroundColor': 'red'}">按钮</button>
4.3 事件绑定
  1. <button (click)="onSave($event)">按钮</button>
  2. <!-- 当按下回车键抬起的时候执行函数 -->
  3. <input type="text"(keyup.enter)="onKeyUp()"/>
  1. export class AppComponent {
  2. title="test"
  3. onSave(event: Event) {
  4. // this 指向组件类的实例对象
  5. this.title// "test"
  6. }
  7. }
4.4 获取原生 DOM 对象
4.4.1 在组件模板中获取
<input type="text"(keyup.enter)="onKeyUp(username.value)" #username/>
4.4.2 在组件类中获取

使用 ViewChild 装饰器获取一个元素

<p #paragraph>home works!</p>
  1. import { AfterViewInit, ElementRef, ViewChild } from"@angular/core"
  2. export class HomeComponent implements AfterViewInit {
  3. @ViewChild("paragraph") paragraph: ElementRef<HTMLParagraphElement>|undefined
  4. ngAfterViewInit() {
  5. console.log(this.paragraph?.nativeElement)
  6. }
  7. }

使用 ViewChildren 获取一组元素

  1. <ul>
  2. <li #items>a</li>
  3. <li #items>b</li>
  4. <li #items>c</li>
  5. </ul>
  1. import { AfterViewInit, QueryList, ViewChildren } from"@angular/core"
  2. @Component({
  3. selector: "app-home",
  4. templateUrl: "./home.component.html",
  5. styles: []
  6. })
  7. export class HomeComponent implements AfterViewInit {
  8. @ViewChildren("items") items: QueryList<HTMLLIElement>|undefined
  9. ngAfterViewInit() {
  10. console.log(this.items?.toArray())
  11. }
  12. }
4.5 双向数据绑定

数据在组件类和组件模板中双向同步。

Angular 将双向数据绑定功能放在了 @angular/forms 模块中,所以要实现双向数据绑定需要依赖该模块。

  1. import { FormsModule } from"@angular/forms"
  2. @NgModule({
  3. imports: [FormsModule],
  4. })
  5. export class AppModule {}
  6. <input type="text" [(ngModel)]="username"/>
  7. <button (click)="change()">在组件类中更改 username</button>
  8. <div>username: {{ username }}</div>
  9. export class AppComponent {
  10. username: string=""
  11. change() {
  12. this.username="hello Angular"
  13. }
  14. }
4.6 内容投影
  1. <!-- app.component.html -->
  2. <bootstrap-panel>
  3. <div class="heading">
  4. Heading
  5. </div>
  6. <div class="body">
  7. Body
  8. </div>
  9. </bootstrap-panel>
  10. <!-- panel.component.html -->
  11. <div class="panel panel-default">
  12. <div class="panel-heading">
  13. <ng-content select=".heading"></ng-content>
  14. </div>
  15. <div class="panel-body">
  16. <ng-content select=".body"></ng-content>
  17. </div>
  18. </div>

如果只有一个ng-content,不需要select属性。

ng-content在浏览器中会被 <div class="heading"></div> 替代,如果不想要这个额外的div,可以使用ng-container替代这个div。

  1. <!-- app.component.html -->
  2. <bootstrap-panel>
  3. <ng-container class="heading">
  4. Heading
  5. </ng-container>
  6. <ng-container class="body">
  7. Body
  8. </ng-container>
  9. </bootstrap-panel>
4.7 数据绑定容错处理
  1. // app.component.ts
  2. export class AppComponent {
  3. task= {
  4. person: {
  5. name: '张三'
  6. }
  7. }
  8. }
  1. <!-- 方式一 -->
  2. <span *ngIf="task.person">{{ task.person.name }}</span>
  3. <!-- 方式二 -->
  4. <span>{{ task.person?.name }}</span>
4.8 全局样式

/* 第一种方式 在 styles.css 文件中 */

  1. @import"~bootstrap/dist/css/bootstrap.css";

/* ~ 相对node_modules文件夹 */

<!-- 第二种方式 在 index.html 文件中 -->

<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"rel="stylesheet"/>

// 第三种方式 在 angular.json 文件中

  1. "styles": [
  2. "./node_modules/bootstrap/dist/css/bootstrap.min.css",
  3. "src/styles.css"
  4. ]

5. 指令 Directive

指令是 Angular 提供的操作 DOM 的途径。指令分为属性指令和结构指令。

属性指令:修改现有元素的外观或行为,使用 [] 包裹。

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

5.1 内置指令
5.1.1 *ngIf

根据条件渲染 DOM 节点或移除 DOM 节点。

  1. <div *ngIf="data.length == 0">没有更多数据</div>
  2. <div *ngIf="data.length > 0; then dataList else noData"></div>
  3. <ng-template #dataList>课程列表</ng-template>
  4. <ng-template #noData>没有更多数据</ng-template>
5.1.2 [hidden]

根据条件显示 DOM 节点或隐藏 DOM 节点 (display)。

  1. <div [hidden]="data.length == 0">课程列表</div>
  2. <div [hidden]="data.length > 0">没有更多数据</div>
5.1.3 *ngFor

遍历数据生成HTML结构

  1. interface List {
  2. id: number
  3. name: string
  4. age: number
  5. }
  6. list: List[] = [
  7. { id: 1, name: "张三", age: 20 },
  8. { id: 2, name: "李四", age: 30 }
  9. ]
  1. <li
  2. *ngFor="
  3. let item of list;
  4. let i = index;
  5. let isEven = even;
  6. let isOdd = odd;
  7. let isFirst = first;
  8. let isLast = last;
  9. "
  10. >
  11. </li>
  12. <li *ngFor="let item of list; trackBy: identify"></li>
  13. identify(index, item){
  14. returnitem.id;
  15. }
5.2 自定义指令

需求:为元素设置默认背景颜色,鼠标移入时的背景颜色以及移出时的背景颜色。

<div [appHover]="{ bgColor: 'skyblue' }">Hello Angular</div>
  1. import { AfterViewInit, Directive, ElementRef, HostListener, Input } from"@angular/core"
  2. // 接收参的数类型
  3. interface Options {
  4. bgColor?: string
  5. }
  6. @Directive({
  7. selector: "[appHover]"
  8. })
  9. export class HoverDirective implements AfterViewInit {
  10. // 接收参数
  11. @Input("appHover") appHover: Options= {}
  12. // 要操作的 DOM 节点
  13. element: HTMLElement
  14. // 获取要操作的 DOM 节点
  15. constructor(private elementRef: ElementRef) {
  16. this.element=this.elementRef.nativeElement
  17. }
  18. // 组件模板初始完成后设置元素的背景颜色
  19. ngAfterViewInit() {
  20. this.element.style.backgroundColor=this.appHover.bgColor||"skyblue"
  21. }
  22. // 为元素添加鼠标移入事件
  23. @HostListener("mouseenter") enter() {
  24. this.element.style.backgroundColor="pink"
  25. }
  26. // 为元素添加鼠标移出事件
  27. @HostListener("mouseleave") leave() {
  28. this.element.style.backgroundColor="skyblue"
  29. }
  30. }​

6. 管道 Pipe

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

6.1 内置管道
  1. date 日期格式化

  1. currency 货币格式化

  1. uppercase 转大写

  1. lowercase 转小写

  1. json 格式化json 数据

{{ date | date: "yyyy-MM-dd" }}

6.2 自定义管道

需求:指定字符串不能超过规定的长度

  1. // summary.pipe.ts
  2. import { Pipe, PipeTransform } from'@angular/core';
  3. @Pipe({
  4. name: 'summary'
  5. });
  6. export class SummaryPipe implements PipeTransform {
  7. transform (value: string, limit?: number) {
  8. if (!value) return null;
  9. let actualLimit= (limit) ?limit : 50;
  10. return value.substr(0, actualLimit) +'...';
  11. }
  12. }
  13. // app.module.ts
  14. import { SummaryPipe } from'./summary.pipe'
  15. @NgModule({
  16. declarations: [
  17. SummaryPipe
  18. ]
  19. });

7. 组件通讯

7.1 向组件内部传递数据
<app-favorite [isFavorite]="true"></app-favorite>
  1. // favorite.component.ts
  2. import { Input } from'@angular/core';
  3. export class FavoriteComponent {
  4. @Input() isFavorite: boolean=false;
  5. }

注意:在属性的外面加 [] 表示绑定动态值,在组件内接收后是布尔类型,不加 [] 表示绑定普通值,在组件内接收后是字符串类型。

  1. <app-favorite [is-Favorite]="true"></app-favorite>
  2. import { Input } from'@angular/core';
  3. export class FavoriteComponent {
  4. @Input("is-Favorite") isFavorite: boolean=false
  5. }
7.2 组件向外部传递数据

需求:在子组件中通过点击按钮将数据传递给父组件

  1. <!-- 子组件模板 -->
  2. <button (click)="onClick()">click</button>
  1. // 子组件类
  2. import { EventEmitter, Output } from"@angular/core"
  3. export class FavoriteComponent {
  4. @Output() change=new EventEmitter()
  5. onClick() {
  6. this.change.emit({ name: "张三" })
  7. }
  8. }
  1. `<!-- 父组件模板 -->
  2. <app-favorite (change)="onChange($event)"></app-favorite>
  1. // 父组件类
  2. export class AppComponent {
  3. onChange(event: { name: string }) {
  4. console.log(event)
  5. }
  6. }

8. 组件生命周期

8.1 挂载阶段

挂载阶段的生命周期函数只在挂载阶段执行一次,数据更新时不再执行。

  1. constructor

Angular 在实例化组件类时执行, 可以用来接收 Angular 注入的服务实例对象。

  1. export class ChildComponent {
  2. constructor (privatetest: TestService) {
  3. console.log(this.test) // "test"
  4. }
  5. }
  1. ngOnInit

在首次接收到输入属性值后执行,在此处可以执行请求操作。

<app-child name="张三"></app-child>
  1. export class ChildComponent implements OnInit {
  2. @Input("name") name: string=""
  3. ngOnInit() {
  4. console.log(this.name) // "张三"
  5. }
  6. }
  1. ngAfterContentInit

当内容投影初始渲染完成后调用。

  1. <app-child>
  2. <div #box>Hello Angular</div>
  3. </app-child>
  1. export class ChildComponent implements AfterContentInit {
  2. @ContentChild("box") box: ElementRef<HTMLDivElement>|undefined
  3. ngAfterContentInit() {
  4. console.log(this.box) // <div>Hello Angular</div>
  5. }
  6. }
  1. ngAfterViewInit

当组件视图渲染完成后调用。

  1. <!-- app-child 组件模板 -->
  2. <p #p>app-child works</p>
  1. export class ChildComponent implements AfterViewInit {
  2. @ViewChild("p") p: ElementRef<HTMLParagraphElement>|undefined
  3. ngAfterViewInit () {
  4. console.log(this.p) // <p>app-child works</p>
  5. }
  6. }
8.2 更新阶段
  1. ngOnChanges

  • 当输入属性值发生变化时执行,初始设置时也会执行一次,顺序优于 ngOnInit

  • 不论多少输入属性同时变化,钩子函数只会执行一次,变化的值会同时存储在参数中

  • 参数类型为 SimpleChanges,子属性类型为 SimpleChange

  • 对于基本数据类型来说, 只要值发生变化就可以被检测到

  • 对于引用数据类型来说, 可以检测从一个对象变成另一个对象, 但是检测不到同一个对象中属性值的变化,但是不影响组件模板更新数据。

基本数据类型值变化

  1. <app-child [name]="name"[age]="age"></app-child>
  2. <button (click)="change()">change</button>
  1. export class AppComponent {
  2. name: string="张三";
  3. age: number=20
  4. change() {
  5. this.name="李四"
  6. this.age=30
  7. }
  8. }
  9. export class ChildComponent implements OnChanges {
  10. @Input("name") name: string=""
  11. @Input("age") age: number=0
  12. ngOnChanges(changes: SimpleChanges) {
  13. console.log("基本数据类型值变化可以被检测到")
  14. }
  15. }

引用数据类型变化

  1. <app-child [person]="person"></app-child>
  2. <button (click)="change()">change</button>
  1. export class AppComponent {
  2. person= { name: "张三", age: 20 }
  3. change() {
  4. this.person= { name: "李四", age: 30 }
  5. }
  6. }
  7. exportclassChildComponentimplementsOnChanges {
  8. @Input("person") person= { name: "", age: 0 }
  9. ngOnChanges(changes: SimpleChanges) {
  10. console.log("对于引用数据类型, 只能检测到引用地址发生变化, 对象属性变化不能被检测到")
  11. }
  12. }
  1. ngDoCheck:主要用于调试,只要输入属性发生变化,不论是基本数据类型还是引用数据类型还是引用数据类型中的属性变化,都会执行。

  1. ngAfterContentChecked:内容投影更新完成后执行。

  1. ngAfterViewChecked:组件视图更新完成后执行。

8.3 卸载阶段
  1. ngOnDestroy

当组件被销毁之前调用, 用于清理操作。

  1. export class HomeComponent implements OnDestroy {
  2. ngOnDestroy() {
  3. console.log("组件被卸载")
  4. }
  5. }

9. 依赖注入

9.1 概述

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

  1. class MailService {
  2. constructor(APIKEY) {}
  3. }
  4. class EmailSender {
  5. mailService: MailService
  6. constructor() {
  7. this.mailService=newMailService("APIKEY1234567890")
  8. }
  9. sendMail(mail) {
  10. this.mailService.sendMail(mail)
  11. }
  12. }
  13. const emailSender=newEmailSender()
  14. emailSender.sendMail(mail)

EmailSender 类运行时要使用 MailService 类,EmailSender 类依赖 MailService 类,MailService 类是 EmailSender 类的依赖项。

以上写法的耦合度太高,代码并不健壮。如果 MailService 类改变了参数的传递方式,在 EmailSender 类中的写法也要跟着改变。

  1. class EmailSender {
  2. mailService: MailService
  3. constructor(mailService: MailService) {
  4. this.mailService=mailService;
  5. }
  6. }
  7. const mailService=new MailService("APIKEY1234567890")
  8. const emailSender=new EmailSender(mailService)

在实例化 EmailSender 类时将它的依赖项通过 constructor 构造函数参数的形式注入到类的内部,这种写法就是依赖注入。

通过依赖注入降了代码之间的耦合度,增加了代码的可维护性。MailService 类中代码的更改再也不会影响 EmailSender 类。

9.2 DI 框架

Angular 有自己的 DI 框架,它将实现依赖注入的过程隐藏了,对于开发者来说只需使用很简单的代码就可以使用复杂的依赖注入功能。

在 Angular 的 DI 框架中有四个核心概念:

  • Dependency:组件要依赖的实例对象,服务实例对象

  • Token:获取服务实例对象的标识

  • Injector:注入器,负责创建维护服务类的实例对象并向组件中注入服务实例对象。

  • Provider:配置注入器的对象,指定创建服务实例对象的服务类和获取实例对象的标识。

9.2.1 注入器 Injectors

注入器负责创建服务类实例对象,并将服务类实例对象注入到需要的组件中。

  1. 创建注入器

  1. import { ReflectiveInjector } from"@angular/core"
  2. // 服务类
  3. classMailService {}
  4. // 创建注入器并传入服务类
  5. const injector=ReflectiveInjector.resolveAndCreate([MailService])
  1. 获取注入器中的服务类实例对象

const mailService=injector.get(MailService)
  1. 服务实例对象为单例模式,注入器在创建服务实例后会对其进行缓存

  1. const mailService1=injector.get(MailService)
  2. const mailService2=injector.get(MailService)
  3. console.log(mailService1===mailService2) // true
  1. 不同的注入器返回不同的服务实例对象

  1. const injector=ReflectiveInjector.resolveAndCreate([MailService])
  2. const childInjector=injector.resolveAndCreateChild([MailService])
  3. const mailService1=injector.get(MailService)
  4. const mailService2=childInjector.get(MailService)
  5. console.log(mailService1===mailService2)
  1. 服务实例的查找类似函数作用域链,当前级别可以找到就使用当前级别,当前级别找不到去父级中查找

  1. const injector=ReflectiveInjector.resolveAndCreate([MailService])
  2. const childInjector=injector.resolveAndCreateChild([])
  3. const mailService1=injector.get(MailService)
  4. const mailService2=childInjector.get(MailService)
  5. console.log(mailService1===mailService2)
9.2.2 提供者 Provider
  1. 配置注入器的对象,指定了创建实例对象的服务类和访问服务实例对象的标识。

  1. const injector=ReflectiveInjector.resolveAndCreate([
  2. { provide: MailService, useClass: MailService }
  3. ])
  1. 访问依赖对象的标识也可以是字符串类型

  1. const injector=ReflectiveInjector.resolveAndCreate([
  2. { provide: "mail", useClass: MailService }
  3. ])
  4. const mailService=injector.get("mail")
  1. useValue

  1. const injector=ReflectiveInjector.resolveAndCreate([
  2. {
  3. provide: "Config",
  4. useValue: Object.freeze({
  5. APIKEY: "API1234567890",
  6. APISCRET: "500-400-300"
  7. })
  8. }
  9. ])
  10. const Config=injector.get("Config")

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

10. 服务 Service

10.1 创建服务
  1. import { Injectable } from'@angular/core';
  2. @Injectable({
  3. providedIn: 'root'
  4. })
  5. export class TestService { }
  6. export class AppComponent {
  7. constructor (privatetestService: TestService) {}
  8. }
10.2 服务的作用域

使用服务可以轻松实现跨模块跨组件共享数据,这取决于服务的作用域。

  1. 在根注入器中注册服务,所有模块使用同一个服务实例对象。

  1. import { Injectable } from'@angular/core';
  2. @Injectable({
  3. providedIn: 'root'
  4. })
  5. export class CarListService {
  6. }
  1. 在模块级别注册服务,该模块中的所有组件使用同一个服务实例对象。

  1. import { Injectable } from'@angular/core';
  2. import { CarModule } from'./car.module';
  3. @Injectable({
  4. providedIn: CarModule,
  5. })
  6. export class CarListService {
  7. }
  8. import { CarListService } from'./car-list.service';
  9. @NgModule({
  10. providers: [CarListService],
  11. })
  12. export class CarModule {
  13. }
  1. 在组件级别注册服务,该组件及其子组件使用同一个服务实例对象。

  1. import { Component } from'@angular/core';
  2. import { CarListService } from'../car-list.service.ts'
  3. @Component({
  4. selector: 'app-car-list',
  5. templateUrl: './car-list.component.html',
  6. providers: [ CarListService ]
  7. })

11. 表单

在 Angular 中,表单有两种类型,分别为模板驱动和模型驱动。

11.1 模板驱动
11.1.1 概述

表单的控制逻辑写在组件模板中,适合简单的表单类型。

11.1.2 快速上手
  1. 引入依赖模块 FormsModule

  1. import { FormsModule } from"@angular/forms"
  2. @NgModule({
  3. imports: [FormsModule],
  4. })
  5. export class AppModule {}
  1. 将 DOM 表单转换为 ngForm

<form #f="ngForm" (submit)="onSubmit(f)"></form>
  1. 声明表单字段为 ngModel

  1. <form #f="ngForm" (submit)="onSubmit(f)">
  2. <input type="text" name="username" ngModel/>
  3. <button>提交</button>
  4. </form>
  1. 获取表单字段值

  1. import { NgForm } from"@angular/forms"
  2. export class AppComponent {
  3. onSubmit(form: NgForm) {
  4. console.log(form.value)
  5. }
  6. }
  1. 表单分组

  1. <form #f="ngForm"(submit)="onSubmit(f)">
  2. <div ngModelGroup="user">
  3. <input type="text" name="username" ngModel/>
  4. </div>
  5. <div ngModelGroup="contact">
  6. <input type="text" name="phone" ngModel/>
  7. </div>
  8. <button>提交</button>
  9. </form>
11.1.3 表单验证
  • required 必填字段

  • minlength 字段最小长度

  • maxlength 字段最大长度

  • pattern 验证正则 例如:pattern="\d" 匹配一个数值

  1. <form #f="ngForm"(submit)="onSubmit(f)">
  2. <input type="text" name="username" ngModel required pattern="\d"/>
  3. <button>提交</button>
  4. </form>
  1. export class AppComponent {
  2. onSubmit(form: NgForm) {
  3. // 查看表单整体是否验证通过
  4. console.log(form.valid)
  5. }
  6. }
  1. <!-- 表单整体未通过验证时禁用提交表单 -->
  2. <button type="submit" [disabled]="f.invalid">提交</button>
  3. 在组件模板中显示表单项未通过时的错误信息。
  4. <form #f="ngForm" (submit)="onSubmit(f)">
  5. <input #username="ngModel"/>
  6. <div *ngIf="username.touched && !username.valid && username.errors">
  7. <div *ngIf="username.errors.required">请填写用户名</div>
  8. <div *ngIf="username.errors.pattern">不符合正则规则</div>
  9. </div>
  10. </form>

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

  1. input.ng-touched.ng-invalid {
  2. border: 2pxsolidred;
  3. }
11.2 模型驱动
11.2.1 概述

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

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

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

  1. FormControl:表单组中的一个表单项

  1. FormGroup:表单组,表单至少是一个 FormGroup

  1. FormArray:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray 中有一项没通过,整体没通过。

11.2.2 快速上手
  1. 引入 ReactiveFormsModule

  1. import { ReactiveFormsModule } from"@angular/forms"
  2. @NgModule({
  3. imports: [ReactiveFormsModule]
  4. })
  5. export class AppModule {}
  1. 在组件类中创建 FormGroup 表单控制对象

  1. import { FormControl, FormGroup } from"@angular/forms"
  2. export class AppComponent {
  3. contactForm: FormGroup=new FormGroup({
  4. name: new FormControl(),
  5. phone: new FormControl()
  6. })
  7. }
  1. 关联组件模板中的表单

  1. <form [formGroup]="contactForm" (submit)="onSubmit()">
  2. <input type="text" formControlName="name"/>
  3. <inputt ype="text" formControlName="phone"/>
  4. <button>提交</button>
  5. </form>
  1. 获取表单值

  1. export class AppComponent {
  2. onSubmit() {
  3. console.log(this.contactForm.value)
  4. }
  5. }
  1. 设置表单默认值

  1. contactForm: FormGroup=new FormGroup({
  2. name: new FormControl("默认值"),
  3. phone: new FormControl(15888888888)
  4. })
  1. 表单分组

  1. contactForm: FormGroup=new FormGroup({
  2. fullName: new FormGroup({
  3. firstName: new FormControl(),
  4. lastName: new FormControl()
  5. }),
  6. phone: newFormControl()
  7. })
  8. onSubmit() {
  9. console.log(this.contactForm.value.name.username)
  10. console.log(this.contactForm.get(["name", "username"])?.value)
  11. }
  1. <form [formGroup]="contactForm" (submit)="onSubmit()">
  2. <div formGroupName="fullName">
  3. <input type="text" formControlName="firstName"/>
  4. <input type="text" formControlName="lastName"/>
  5. </div>
  6. <input type="text" formControlName="phone"/>
  7. <button>提交</button>
  8. </form>
11.2.3 FormArray

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

  1. import { Component, OnInit } from"@angular/core"
  2. import { FormArray, FormControl, FormGroup } from"@angular/forms"
  3. @Component({
  4. selector: "app-root",
  5. templateUrl: "./app.component.html",
  6. styles: []
  7. })
  8. export class AppComponent implements OnInit {
  9. // 表单
  10. contactForm: FormGroup=new FormGroup({
  11. contacts: new FormArray([])
  12. })
  13. get contacts() {
  14. return this.contactForm.get("contacts") as FormArray
  15. }
  16. // 添加联系方式
  17. addContact() {
  18. // 联系方式
  19. const myContact: FormGroup=new FormGroup({
  20. name: new FormControl(),
  21. address: new FormControl(),
  22. phone: new FormControl()
  23. })
  24. // 向联系方式数组中添加联系方式
  25. this.contacts.push(myContact)
  26. }
  27. // 删除联系方式
  28. removeContact(i: number) {
  29. this.contacts.removeAt(i)
  30. }
  31. ngOnInit() {
  32. // 添加默认的联系方式
  33. this.addContact()
  34. }
  35. onSubmit() {
  36. console.log(this.contactForm.value)
  37. }
  38. }
  1. <form [formGroup]="contactForm"(submit)="onSubmit()">
  2. <div formArrayName="contacts">
  3. <div
  4. *ngFor="let contact of contacts.controls; let i = index"
  5. [formGroupName]="i"
  6. >
  7. <input type="text"formControlName="name"/>
  8. <input type="text"formControlName="address"/>
  9. <input type="text"formControlName="phone"/>
  10. <button (click)="removeContact(i)">删除联系方式</button>
  11. </div>
  12. </div>
  13. <button (click)="addContact()">添加联系方式</button>
  14. <button>提交</button>
  15. </form>
11.2.4 内置表单验证器
  1. 使用内置验证器提供的验证规则验证表单字段

  1. import { FormControl, FormGroup, Validators } from"@angular/forms"
  2. contactForm: FormGroup=new FormGroup({
  3. name: new FormControl("默认值", [
  4. Validators.required,
  5. Validators.minLength(2)
  6. ])
  7. })
  1. 获取整体表单是否验证通过

  1. onSubmit() {
  2. console.log(this.contactForm.valid)
  3. }
  1. <!-- 表单整体未验证通过时禁用表单按钮 -->
  2. <button [disabled]="contactForm.invalid">提交</button>
  1. 在组件模板中显示为验证通过时的错误信息

  1. getname() {
  2. return this.contactForm.get("name")!
  3. }
  1. <form [formGroup]="contactForm"(submit)="onSubmit()">
  2. <input type="text" formControlName="name"/>
  3. <div *ngIf="name.touched && name.invalid && name.errors">
  4. <div *ngIf="name.errors.required">请填写姓名</div>
  5. <div *ngIf="name.errors.maxlength">
  6. 姓名长度不能大于
  7. {{ name.errors.maxlength.requiredLength }} 实际填写长度为
  8. {{ name.errors.maxlength.actualLength }}
  9. </div>
  10. </div>
  11. </form>
11.2.5 自定义同步表单验证器
  1. 自定义验证器的类型是 TypeScript 类

  1. 类中包含具体的验证方法,验证方法必须为静态方法

  1. 验证方法有一个参数 control,类型为 AbstractControl。其实就是 FormControl 类的实例对象的类型

  1. 如果验证成功,返回 null

  1. 如果验证失败,返回对象,对象中的属性即为验证标识,值为 true,标识该项验证失败

  1. 验证方法的返回值为 ValidationErrors | null

  1. import { AbstractControl, ValidationErrors } from"@angular/forms"
  2. export class NameValidators {
  3. // 字段值中不能包含空格
  4. static cannotContainSpace(control: AbstractControl): ValidationErrors|null {
  5. // 验证未通过
  6. if (/\s/.test(control.value)) return { cannotContainSpace: true }
  7. // 验证通过
  8. returnnull
  9. }
  10. }
  11. import { NameValidators } from"./Name.validators"
  12. contactForm: FormGroup=newFormGroup({
  13. name: newFormControl("", [
  14. Validators.required,
  15. NameValidators.cannotContainSpace
  16. ])
  17. })
  1. <div *ngIf="name.touched && name.invalid && name.errors">
  2. <div *ngIf="name.errors.cannotContainSpace">姓名中不能包含空格</div>
  3. </div>
11.2.6 自定义异步表单验证器
  1. import { AbstractControl, ValidationErrors } from"@angular/forms"
  2. import { Observable } from"rxjs"
  3. export class NameValidators {
  4. static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors|null> {
  5. return new Promise(resolve=> {
  6. if (control.value=="admin") {
  7. resolve({ shouldBeUnique: true })
  8. } else {
  9. resolve(null)
  10. }
  11. })
  12. }
  13. }
  14. contactForm: FormGroup=new FormGroup({
  15. name: new FormControl(
  16. "",
  17. [
  18. Validators.required
  19. ],
  20. NameValidators.shouldBeUnique
  21. )
  22. })
  1. <div *ngIf="name.touched && name.invalid && name.errors">
  2. <div *ngIf="name.errors.shouldBeUnique">用户名重复</div>
  3. </div>
  4. <div *ngIf="name.pending">正在检测姓名是否重复</div>
11.2.7 FormBuilder

创建表单的快捷方式。

  1. this.fb.control:表单项

  1. this.fb.group:表单组,表单至少是一个 FormGroup

  1. this.fb.array:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray 中有一项没通过,整体没通过。

  1. import { FormBuilder, FormGroup, Validators } from"@angular/forms"
  2. export class AppComponent {
  3. contactForm: FormGroup
  4. constructor(privatefb: FormBuilder) {
  5. this.contactForm=this.fb.group({
  6. fullName: this.fb.group({
  7. firstName: ["
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/696410
    推荐阅读
    相关标签