简介
依赖注入是重要的程序设计模式。 Angular 有自己的依赖注入框架,离开了它,几乎没法构建 Angular 应用。
它使用得非常广泛,以至于几乎每个人都会把它简称为 DI。
注入器与提供器
注入器
//注入器
Constructor(private productService: ProductService ){ }
说明:
一般是在组件或者是服务中写入端代码,如果一个组件或者服务中的 constructor 注入为空。
就代表着这个组件或者服务没有注入任何的服务。
提供器
//提供器写法一
Providers:[ProductService]
//提供器写法二
Providers:[{provide: ProductService, userClass:ProductService}]
//提供器写法三
Providers:[{ provide: ProductService, userClass:AnotherProductService }]
//提供器写法四
Providers:[{ provide: ProductService , userFactory: () => { /*新建服务的方法*/ } }]
说明:
写法一:使用这种方法是默认的写法(也是最精简的写法),直接提供一个 ProuctService。
写法二:使用这种方法和默认的写法是一个意思。
provide: ProductService就是提供的是 ProductService服务。
userClass:ProductService 就是我们new 这个服务对象的时候,new的是 ProductService。
写法三:使用这种方式,就是提供器提供的是 ProductService,但是 new 服务对象的时候,
new 的是 AnotherProductService。
写法四:使用工厂模式创建提供器
寻找依赖注入的逻辑
在代码中找注入的方法:
1.在具体的组件或者服务中的 constructor 方法中找注入的服务
2.根据 1 中注入的服务找 提供器
3.跟据2 中的提供器找到对应的注入服务类
例:
注入器为 :Constructor(private productService: ProductService ){ }
就去找对应的 providers(提供器)
如果提供器为:Providers:[ProductService] 那么注入的就是 ProductService
如果提供器为:Providers:[{ provide: ProductService, userClass:AnotherProductService }]
那么注入的就是 AnotherProductService
依赖注入的层级结构
一方面,NgModule 中的提供商是被注册到根注入器。这意味着在 NgModule 中注册的提供商可以被整个应用访问。
另一方面,在应用组件中注册的提供商只在该组件及其子组件中可用。
关于 @Injectable()
@Injectable() 标识一个类可以被注入器实例化。 通常,在试图实例化没有被标识为@Injectable()的类时,注入器会报错。
官方建议:
建议:为每个服务类都添加 @INJECTABLE()
建议:为每个服务类都添加@Injectable(),包括那些没有依赖严格来说并不需要它的。因为:
面向未来: 没有必要记得在后来添加依赖的时候添加 @Injectable()。
一致性:所有的服务都遵循同样的规则,不需要考虑为什么某个地方少了一个。
"注意":
总是使用@Injectable()的形式,不能只用@Injectable。 如果忘了括号,应用就会神不知鬼不觉的报错!
最简单的例子
根模块中注入
目的:在新建的工程中将数据从Service中注入到component中,并且在界面上面展示出来
1.新建一个工程: ng new di
2.新建 product1 组件: ng g c product1
3.新建 product 服务(在shared 路径下面新建 product 服务):
ng g service shared/product 或者 ng g s shared/product
修改代码 produc.service.ts:
/*
增加 class Product, 以及返回 Product对象供外部调用的方法 getProduct()
getProduct方法需要返回一个 Product 。如果需要让外部访问到当前的 Service ,就需要加上一个注解 @Injectable()
*/
import { Injectable } from '@angular/core';
@Injectable()
export class ProductService {
constructor() { }
getProduct(): Product {
return new Product(1, "IPhone X", "最牛逼的全面屏手机", 8388);
}
}
export class Product{
constructor(
public id: number,
public name: string,
public desc: string,
public price: number
){}
}
修改 product1.component.ts
/*
在当前的 Product 类中增加 变量 product 以及 注入 ProductService,在初始化的钩子中 调用 ProductService 的 getProduct 方法,返回一个 Product
*/
import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from "../shared/product.service";
@Component({
selector: 'app-product1',
templateUrl: './product1.component.html',
styleUrls: ['./product1.component.css']
})
export class Product1Component implements OnInit {
product: Product;
constructor(private productService: ProductService) { }
ngOnInit() {
this.product = this.productService.getProduct();
}
}
修改 product1.component.html
商品编码:{{product.id}}
商品名称:{{product.name}}
商品描述:{{product.desc}}
商品价格:{{product.price}}
修改 app.conponent.html
依赖注入的例子
修改 app.modules.ts
/*添加 Product.service.ts 到 providers 中,在这个地方注入是叫做 “从根组件中注入”,然后所有的都可以访问到。*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { Product1Component } from './product1/product1.component';
import {ProductService} from "app/shared/product.service";
@NgModule({
declarations: [
AppComponent,
Product1Component
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [ProductService],
bootstrap: [AppComponent]
})
export class AppModule { }
图示:
在组件中注入服务
在具体的某个组件中去注入服务,而不是通过"根模块"去注入。
由于在具体的组件中去注入服务,这样子可以"覆盖根模块的注入",从而使自己的子模块拥有该服务,这样子就可以做服务的多级注入。
目的:我新建一个 product2 组件,然后在 product2 组件中注入 anotherProductService
1.新建一个组件 product2
ng g c product2
2.在 shared目录下新建一个service anotherProduct
ng g s shared/anotherProduct
修改 another-product.service.ts
/*
实现 ProductService 服务,共同的拥有一个返回 Product 的方法,这个地方 用不用 implements 实现 ProductService都不影响,在 component中的提供器上面 使用这个服务
*/
import { Injectable } from '@angular/core';
import {Product, ProductService} from "./product.service";
@Injectable()
export class AnotherProductService implements ProductService{
constructor() { }
getProduct(): Product {
return new Product(2, "小米 MIX2", "小米最牛逼的全屏手机,陶瓷机身", 3600);
}
}
修改product2.conponent. ts
/*
在 Component 语法糖中添加 服务的提供器 provide, 这样子做会覆盖根模块中注入的服务。并且在 Component中注入的服务就可以不用在 app.modules.ts 中的 provide中添加
*/
import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from "../shared/product.service";
import {AnotherProductService} from "../shared/another-product.service";
@Component({
selector: 'app-product2',
templateUrl: './product2.component.html',
styleUrls: ['./product2.component.css'],
providers:[{
provide: ProductService, useClass: AnotherProductService
}]
})
export class Product2Component implements OnInit {
product: Product;
constructor(private productService: ProductService) { }
ngOnInit() {
this.product = this.productService.getProduct();
}
}
修改product2.component.html
商品编码:{{product.id}}
商品名称:{{product.name}}
商品描述:{{product.desc}}
商品价格:{{product.price}}
修改 app.component.html
依赖注入的例子
图示:
给服务中注入服务
上面我们是介绍了怎么去在 组件中注入服务,其实在"服务"中可以"注入服务"
新建服务 logger.service.ts
ng g s shared/logger
修改 logger.service.ts
/*
增加一个打印日志的方法
*/
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
constructor() { }
log(msg: string){
console.log(msg);
}
}
修改app.module.ts
/*增加 loggerService的服务到提供器中*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { Product1Component } from './product1/product1.component';
import {ProductService} from "app/shared/product.service";
import { Product2Component } from './product2/product2.component';
import {LoggerService} from "./shared/logger.service";
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [ProductService, LoggerService],
bootstrap: [AppComponent]
})
export class AppModule { }
修改another-product.service.ts
/*增加 logger 服务到当前的 服务中,调用的时候就打印日志*/
import { Injectable } from '@angular/core';
import {Product, ProductService} from "./product.service";
import {LoggerService} from "./logger.service";
@Injectable()
export class AnotherProductService implements ProductService{
constructor(private logger: LoggerService) { }
getProduct(): Product {
this.logger.log("product2 getProduct() 方法被调用");
return new Product(2, "小米 MIX2", "小米最牛逼的全屏手机,陶瓷机身", 3600);
}
}
图示:
使用工厂模式注入
说明:
有的时候,我们需要动态创建某一个依赖值,因为它所需要的信息直到最后一刻才能确定。然后根据这个依赖值去创建我们所需要的类。
当遇到这样子的情况,就可以使用工厂模式。
修改 product2.component.ts
/*
目的:是为了通过工厂模式注入,而不是在 组件 Component中注入
去掉在 @component 语法糖中的提供器 providers,让product2 和 product1 共用一个提供器
*/
import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from "../shared/product.service";
@Component({
selector: 'app-product2',
templateUrl: './product2.component.html',
styleUrls: ['./product2.component.css']
})
export class Product2Component implements OnInit {
product: Product;
constructor(private productService: ProductService) { }
ngOnInit() {
this.product = this.productService.getProduct();
}
}
图示:
修改 app.module.ts
/*
修改app.module.ts 中的提供器,修改为使用工厂模式启动。
需要说明的是,因为在 AnotherProductService 中的构造器注入了 LoggerService 服务,需要在这个地方传入一个logger对象进去。
我们也可以将工厂模式的这段代码写入到单独的一个ts中。(见官方的例子)
*/
providers: [{
provide:ProductService,
useFactory: () => {
let logger = new LoggerService();
let random = Math.random();
let flag = random < 0.5;
logger.log("生成的随机数为: "+random);
if(flag){
return new ProductService();
}else{
return new AnotherProductService(logger);
}
}
}, LoggerService]
完整的 app.module.ts 的代码
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { Product1Component } from './product1/product1.component';
import {ProductService} from "app/shared/product.service";
import { Product2Component } from './product2/product2.component';
import {LoggerService} from "./shared/logger.service";
import {AnotherProductService} from "./shared/another-product.service";
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [{
provide:ProductService,
useFactory: () => {
let logger = new LoggerService();
let random = Math.random();
let flag = random < 0.5;
logger.log("生成的随机数为: "+random);
if(flag){
return new ProductService();
}else{
return new AnotherProductService(logger);
}
}
}, LoggerService],
bootstrap: [AppComponent]
})
export class AppModule { }
图示:
工厂模式下传递参数
修改 provides 的写法,增加 deps 节点,这个节点就是提供 工厂模式传递参数,并且可以把数据传入进去。
在providers 中增加 {provide: "IS_DEV_ENV", useValue: false}, 就是在 provider 中增加一个变量为 IS_DEV_ENV,值为 false。
通过 useFactory 的方法传入参数进去,就可以直接使用这个参数的值
传入值为 Boolean
providers: [{
provide:ProductService,
useFactory: (logger: LoggerService, is_dev) => {
if(is_dev){
return new ProductService();
}else{
return new AnotherProductService(logger);
}
},
deps:[LoggerService, "IS_DEV_ENV"]
}, LoggerService,
{provide:"IS_DEV_ENV", useValue :false}
]
传入值为对象
providers: [{
provide:ProductService,
useFactory: (logger: LoggerService, config) => {
if( config.isDev ){
return new ProductService();
}else{
return new AnotherProductService(logger);
}
},
deps:[LoggerService, "APP_CONFIG"]
}, LoggerService,
{
provide:"APP_CONFIG", useValue :{isDev: false}
}
]
写在最后
我是一步一步编写代码,边整理博客,按照上面的顺序,应该是可以把例子跑起来。如果需要本例子的代码,可以联系我。