Angular基础保姆级教程 - 1

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 装饰器函数装饰的类。

javascript 复制代码
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

@NgModule({
  imports: [
    BrowserModule
  ]
})
export class AppModule { }
2.2 组件

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

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

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

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

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

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

javascript 复制代码
import { Component } from "@angular/core"

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  title = "angular-test"
}

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

javascript 复制代码
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
2.3 服务

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

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

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

javascript 复制代码
import { Injectable } from '@angular/core';

@Injectable({})
export class AppService { }

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

javascript 复制代码
import { AppService } from "./AppService"

export class AppComponent {
  let appService = new AppService()
}

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

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

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

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

javascript 复制代码
import { AppService } from "./AppService"

export class AppComponent {
  constructor (
  	private appService: AppService
  ) {}
}

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

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

3. 快速开始

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

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

    1. --skipGit=true
    2. --minimal=true
    3. --skip-install
    4. --style=css
    5. --routing=false
    6. --inlineTemplate
    7. --inlineStyle
    8. --prefix
  1. 运行应用:ng serve

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

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

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

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

  6. 访问应用:localhost:4200

3.2 默认代码解析
3.2.1 main.ts
javascript 复制代码
// enableProdMode 方法调用后将会开启生产模式
import { enableProdMode } from "@angular/core"
// Angular 应用程序的启动在不同的平台上是不一样的
// 在浏览器中启动时需要用到 platformBrowserDynamic 方法, 该方法返回平台实例对象
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"
// 引入根模块 用于启动应用程序
import { AppModule } from "./app/app.module"
// 引入环境变量对象 { production: false }
import { environment } from "./environments/environment"

// 如果当前为生产环境
if (environment.production) {
  // 开启生产模式
  enableProdMode()
}
// 启动应用程序
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err))
3.2.2 environment.ts
javascript 复制代码
// 在执行 `ng build --prod` 时, environment.prod.ts 文件会替换 environment.ts 文件
// 该项配置可以在 angular.json 文件中找到, projects -> angular-test -> architect -> configurations -> production -> fileReplacements

export const environment = {
  production: false
}

#####3.2.3 environment.prod.ts

javascript 复制代码
export const environment = {
  production: true
}
3.2.4 app.module.ts
javascript 复制代码
// BrowserModule 提供了启动和运行浏览器应用所必需的服务
// CommonModule 提供各种服务和指令, 例如 ngIf 和 ngFor, 与平台无关
// BrowserModule 导入了 CommonModule, 又重新导出了 CommonModule, 使其所有指令都可用于导入 BrowserModule 的任何模块 
import { BrowserModule } from "@angular/platform-browser"
// NgModule: Angular 模块装饰器
import { NgModule } from "@angular/core"
// 根组件
import { AppComponent } from "./app.component"
// 调用 NgModule 装饰器, 告诉 Angular 当前类表示的是 Angular 模块
@NgModule({
  // 声明当前模块拥有哪些组件
  declarations: [AppComponent],
  // 声明当前模块依赖了哪些其他模块
  imports: [BrowserModule],
  // 声明服务的作用域, 数组中接收服务类, 表示该服务只能在当前模块的组件中使用
  providers: [],
  // 可引导组件, Angular 会在引导过程中把它加载到 DOM 中
  bootstrap: [AppComponent]
})
export class AppModule {}
3.2.5 app.component.ts
javascript 复制代码
import { Component } from "@angular/core"

@Component({
  // 指定组件的使用方式, 当前为标记形式
  // app-home   =>  <app-home></app-home>
	// [app-home] =>  <div app-home></div>
  // .app-home  =>  <div class="app-home"></div>
  selector: "app-root",
  // 关联组件模板文件
  // templateUrl:'组件模板文件路径'
	// template:`组件模板字符串`
  templateUrl: "./app.component.html",
  // 关联组件样式文件
  // styleUrls : ['组件样式文件路径']
	// styles : [`组件样式`]
  styleUrls: ["./app.component.css"]
})
export class AppComponent {}
3.2.6 index.html
html 复制代码
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AngularTest</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>
3.3 共享模块

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

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

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

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

    javascript 复制代码
    @NgModule({
      declarations: [LayoutComponent],
      exports: [LayoutComponent]
    })
    export class SharedModule {}
  4. 在根模块中导入共享模块

    javascript 复制代码
    @NgModule({
      declarations: [AppComponent],
      imports: [SharedModule],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
  5. 在根组件中使用 Layout 组件

    javascript 复制代码
    @Component({
      selector: "app-root",
      template: `
        <div>App works</div>
        <app-layout></app-layout>
      `,
      styles: []
    })
    export class AppComponent { }

4. 组件模板

4.1 数据绑定

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

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

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

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

  1. 使用 属性名称 为元素绑定 DOM 对象属性。
html 复制代码
<img [src]="imgUrl"/>
  1. 使用 attr.属性名称 为元素绑定 HTML 标记属性
html 复制代码
<td [attr.colspan]="colSpan"></td> 

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

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

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

html 复制代码
<p #paragraph>home works!</p>
javascript 复制代码
import { AfterViewInit, ElementRef, ViewChild } from "@angular/core"

export class HomeComponent implements AfterViewInit {
  @ViewChild("paragraph") paragraph: ElementRef<HTMLParagraphElement> | undefined
  ngAfterViewInit() {
    console.log(this.paragraph?.nativeElement)
  }
}

使用 ViewChildren 获取一组元素

html 复制代码
<ul>
  <li #items>a</li>
  <li #items>b</li>
  <li #items>c</li>
</ul>
javascript 复制代码
import { AfterViewInit, QueryList, ViewChildren } from "@angular/core"

@Component({
  selector: "app-home",
  templateUrl: "./home.component.html",
  styles: []
})
export class HomeComponent implements AfterViewInit {
  @ViewChildren("items") items: QueryList<HTMLLIElement> | undefined
  ngAfterViewInit() {
    console.log(this.items?.toArray())
  }
}
4.5 双向数据绑定

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

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

javascript 复制代码
import { FormsModule } from "@angular/forms"

@NgModule({
  imports: [FormsModule],
})
export class AppModule {}
html 复制代码
<input type="text" [(ngModel)]="username" />
<button (click)="change()">在组件类中更改 username</button>
<div>username: {{ username }}</div>
javascript 复制代码
export class AppComponent {
  username: string = ""
  change() {
    this.username = "hello Angular"
  }
}
4.6 内容投影
html 复制代码
<!-- app.component.html -->
<bootstrap-panel>
	<div class="heading">
        Heading
  </div>
  <div class="body">
        Body
  </div>
</bootstrap-panel>
html 复制代码
<!-- panel.component.html -->
<div class="panel panel-default">
  <div class="panel-heading">
    <ng-content select=".heading"></ng-content>
  </div>
  <div class="panel-body">
    <ng-content select=".body"></ng-content>
  </div>
</div>

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

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

html 复制代码
<!-- app.component.html -->
<bootstrap-panel>
	<ng-container class="heading">
        Heading
    </ng-container>
    <ng-container class="body">
        Body
    </ng-container>
</bootstrap-panel>
4.7 数据绑定容错处理
javascript 复制代码
// app.component.ts
export class AppComponent {
    task = {
        person: {
            name: '张三'
        }
    }
}
html 复制代码
<!-- 方式一 -->
<span *ngIf="task.person">{{ task.person.name }}</span>
<!-- 方式二 -->
<span>{{ task.person?.name }}</span>
4.8 全局样式
css 复制代码
/* 第一种方式 在 styles.css 文件中 */
@import "~bootstrap/dist/css/bootstrap.css";
/* ~ 相对node_modules文件夹 */
html 复制代码
<!-- 第二种方式 在 index.html 文件中  -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet" />
javascript 复制代码
// 第三种方式 在 angular.json 文件中
"styles": [
  "./node_modules/bootstrap/dist/css/bootstrap.min.css",
  "src/styles.css"
]

5. 指令 Directive

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

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

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

5.1 内置指令
5.1.1 *ngIf

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

html 复制代码
<div *ngIf="data.length == 0">没有更多数据</div>
html 复制代码
<div *ngIf="data.length > 0; then dataList else noData"></div>
<ng-template #dataList>课程列表</ng-template>
<ng-template #noData>没有更多数据</ng-template>
5.1.2 hidden

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

html 复制代码
<div [hidden]="data.length == 0">课程列表</div>
<div [hidden]="data.length > 0">没有更多数据</div>
5.1.3 *ngFor

遍历数据生成HTML结构

javascript 复制代码
interface List {
  id: number
  name: string
  age: number
}

list: List[] = [
  { id: 1, name: "张三", age: 20 },
  { id: 2, name: "李四", age: 30 }
]
html 复制代码
<li
    *ngFor="
      let item of list;
      let i = index;
      let isEven = even;
      let isOdd = odd;
      let isFirst = first;
      let isLast = last;
    "
  >
  </li>
html 复制代码
<li *ngFor="let item of list; trackBy: identify"></li>
javascript 复制代码
identify(index, item){
  return item.id; 
}
5.2 自定义指令

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

html 复制代码
<div [appHover]="{ bgColor: 'skyblue' }">Hello Angular</div>
javascript 复制代码
import { AfterViewInit, Directive, ElementRef, HostListener, Input } from "@angular/core"

// 接收参的数类型
interface Options {
  bgColor?: string
}

@Directive({
  selector: "[appHover]"
})
export class HoverDirective implements AfterViewInit {
  // 接收参数
  @Input("appHover") appHover: Options = {}
  // 要操作的 DOM 节点
  element: HTMLElement
	// 获取要操作的 DOM 节点
  constructor(private elementRef: ElementRef) {
    this.element = this.elementRef.nativeElement
  }
	// 组件模板初始完成后设置元素的背景颜色
  ngAfterViewInit() {
    this.element.style.backgroundColor = this.appHover.bgColor || "skyblue"
  }
	// 为元素添加鼠标移入事件
  @HostListener("mouseenter") enter() {
    this.element.style.backgroundColor = "pink"
  }
	// 为元素添加鼠标移出事件
  @HostListener("mouseleave") leave() {
    this.element.style.backgroundColor = "skyblue"
  }
}

6. 管道 Pipe

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

6.1 内置管道
  1. date 日期格式化
  2. currency 货币格式化
  3. uppercase 转大写
  4. lowercase 转小写
  5. json 格式化json 数据
html 复制代码
{{ date | date: "yyyy-MM-dd" }}
6.2 自定义管道

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

javascript 复制代码
// summary.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
   name: 'summary' 
});
export class SummaryPipe implements PipeTransform {
    transform (value: string, limit?: number) {
        if (!value) return null;
        let actualLimit = (limit) ? limit : 50;
        return value.substr(0, actualLimit) + '...';
    }
}
typescript 复制代码
// app.module.ts
import { SummaryPipe } from './summary.pipe'
@NgModule({
    declarations: [
      SummaryPipe
    ] 
});

7. 组件通讯

7.1 向组件内部传递数据
html 复制代码
<app-favorite [isFavorite]="true"></app-favorite>
javascript 复制代码
// favorite.component.ts
import { Input } from '@angular/core';
export class FavoriteComponent {
    @Input() isFavorite: boolean = false;
}

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

html 复制代码
<app-favorite [is-Favorite]="true"></app-favorite>
javascript 复制代码
import { Input } from '@angular/core';

export class FavoriteComponent {
  @Input("is-Favorite") isFavorite: boolean = false
}
7.2 组件向外部传递数据

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

html 复制代码
<!-- 子组件模板 -->
<button (click)="onClick()">click</button>
javascript 复制代码
// 子组件类
import { EventEmitter, Output } from "@angular/core"

export class FavoriteComponent {
  @Output() change = new EventEmitter()
  onClick() {
    this.change.emit({ name: "张三" })
  }
}
html 复制代码
<!-- 父组件模板 -->
<app-favorite (change)="onChange($event)"></app-favorite>
javascript 复制代码
// 父组件类
export class AppComponent {
  onChange(event: { name: string }) {
    console.log(event)
  }
}

8. 组件生命周期

  1. constructor

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

    javascript 复制代码
    export class ChildComponent {
      constructor (private test: TestService) {
        console.log(this.test) // "test"
      }
    }
  2. ngOnInit

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

    html 复制代码
    <app-child name="张三"></app-child>
    javascript 复制代码
    export class ChildComponent implements OnInit {
      @Input("name") name: string = ""
      ngOnInit() {
        console.log(this.name) // "张三"
      }
    }
  3. ngAfterContentInit

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

    html 复制代码
    <app-child>
    	<div #box>Hello Angular</div>
    </app-child>
    javascript 复制代码
    export class ChildComponent implements AfterContentInit {
      @ContentChild("box") box: ElementRef<HTMLDivElement> | undefined
    
      ngAfterContentInit() {
        console.log(this.box) // <div>Hello Angular</div>
      }
    }
  4. ngAfterViewInit

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

    html 复制代码
    <!-- app-child 组件模板 -->
    <p #p>app-child works</p>
    javascript 复制代码
    export class ChildComponent implements AfterViewInit {
      @ViewChild("p") p: ElementRef<HTMLParagraphElement> | undefined
      ngAfterViewInit () {
        console.log(this.p) // <p>app-child works</p>
      }
    }
8.2 更新阶段
  1. ngOnChanges

    1. 当输入属性值发生变化时执行,初始设置时也会执行一次,顺序优于 ngOnInit
    2. 不论多少输入属性同时变化,钩子函数只会执行一次,变化的值会同时存储在参数中
    3. 参数类型为 SimpleChanges,子属性类型为 SimpleChange
    4. 对于基本数据类型来说, 只要值发生变化就可以被检测到
    5. 对于引用数据类型来说, 可以检测从一个对象变成另一个对象, 但是检测不到同一个对象中属性值的变化,但是不影响组件模板更新数据。

    基本数据类型值变化

    html 复制代码
    <app-child [name]="name" [age]="age"></app-child>
    <button (click)="change()">change</button>
    javascript 复制代码
    export class AppComponent {
      name: string = "张三";
    	age: number = 20
      change() {
        this.name = "李四"
        this.age = 30
      }
    }
    javascript 复制代码
    export class ChildComponent implements OnChanges {
      @Input("name") name: string = ""
    	@Input("age") age: number = 0
    
      ngOnChanges(changes: SimpleChanges) {
        console.log("基本数据类型值变化可以被检测到")
      }
    }

    引用数据类型变化

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

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

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

8.3 卸载阶段
  1. ngOnDestroy

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

    javascript 复制代码
    export class HomeComponent implements OnDestroy {
      ngOnDestroy() {
        console.log("组件被卸载")
      }
    }

9. 依赖注入

9.1 概述

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

javascript 复制代码
class MailService {
  constructor(APIKEY) {}
}

class EmailSender {
  mailService: MailService
  constructor() {
    this.mailService = new MailService("APIKEY1234567890")
  }

  sendMail(mail) {
    this.mailService.sendMail(mail)
  }
}

const emailSender = new EmailSender()
emailSender.sendMail(mail)

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

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

javascript 复制代码
class EmailSender {
  mailService: MailService
  constructor(mailService: MailService) {
    this.mailService = mailService;
  }
}
const mailService = new MailService("APIKEY1234567890")
const emailSender = new EmailSender(mailService)

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

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

9.2 DI 框架

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

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

  1. Dependency:组件要依赖的实例对象,服务实例对象
  2. Token:获取服务实例对象的标识
  3. Injector:注入器,负责创建维护服务类的实例对象并向组件中注入服务实例对象。
  4. Provider:配置注入器的对象,指定创建服务实例对象的服务类和获取实例对象的标识。
9.2.1 注入器 Injectors

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

  1. 创建注入器

    javascript 复制代码
    import { ReflectiveInjector } from "@angular/core"
    // 服务类
    class MailService {}
    // 创建注入器并传入服务类
    const injector = ReflectiveInjector.resolveAndCreate([MailService])
  2. 获取注入器中的服务类实例对象

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

    javascript 复制代码
    const mailService1 = injector.get(MailService)
    const mailService2 = injector.get(MailService)
    
    console.log(mailService1 === mailService2) // true
  4. 不同的注入器返回不同的服务实例对象

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

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

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

    javascript 复制代码
    const injector = ReflectiveInjector.resolveAndCreate([
      { provide: "mail", useClass: MailService }
    ])
    const mailService = injector.get("mail")
  3. useValue

    javascript 复制代码
    const injector = ReflectiveInjector.resolveAndCreate([
      {
        provide: "Config",
        useValue: Object.freeze({
          APIKEY: "API1234567890",
          APISCRET: "500-400-300"
        })
      }
    ])
    const Config = injector.get("Config")

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

10. 服务 Service

10.1 创建服务
javascript 复制代码
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TestService { }
javascript 复制代码
export class AppComponent {
 	constructor (private testService: TestService) {}
}
10.2 服务的作用域

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

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

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

    javascript 复制代码
    import { Injectable } from '@angular/core';
    import { CarModule } from './car.module';
    
    @Injectable({
      providedIn: CarModule,
    })
    
    export class CarListService {
    }
    javascript 复制代码
    import { CarListService } from './car-list.service';
    
    @NgModule({
      providers: [CarListService],
    })
    export class CarModule {
    }
  3. 在组件级别注册服务,该组件及其子组件使用同一个服务实例对象。

    javascript 复制代码
    import { Component } from '@angular/core';
    import { CarListService } from '../car-list.service.ts'
    
    @Component({
      selector:    'app-car-list',
      templateUrl: './car-list.component.html',
      providers:  [ CarListService ]
    })
相关推荐
计算机魔术师17 分钟前
管谷歌的不是Pichai一个人,是一整层AI智能体
前端
米米商聊21 分钟前
图标能点,按钮就可用了吗?键盘、名称与收藏状态
前端·javascript
Younglina1 小时前
三个晚上,我和 WorkBuddy 做了个生图网站
前端·后端·ai编程
xy34531 小时前
Axure 9.0 内联框架加载方式
前端·ui·html·axure·原型·产品设计
技术小甜甜2 小时前
【Cursor】Skill 被标黄色警告,怎么改成合格的 SKILL.md
开发语言·javascript·ai编程·cursor·1024程序员节·ai agent·skills
陈随易2 小时前
副业搞钱点子之收入查看器
前端·后端
Profile排查笔记2 小时前
浏览器指纹逆向怎么分析:从网络请求追踪到 JavaScript 采集链
前端·人工智能·后端·自动化
hudie7653 小时前
LikeShop 二开最常改的 5 个文件(附改前改后对比)
前端·uni-app·php·个人开发
qetfw3 小时前
Bash 函数与流程控制:read、if、for、case 验证
linux·前端·javascript·bash
鬼手点金4 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节