依赖注入:Dependency Injection 简称DI
注入器
constructor(private productServie:ProductService){...}@H_404_15@
提供器
providers:[{provider:ProductService,useClass:ProductService}]
providers:[ProductService]//如果provider和useClass一样可以简写成这样
providers:[{provider:ProductService,useClass:AnotherProductService}]
provider:[{provide:ProductService,useFactory:()=>{...}}]@H_404_15@
依赖注入例子
新建一个项目ng new di
新建组件ng g component product1
新建服务ng g service shared/product
修改product.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class ProductService {
constructor() { }
getProduct():Product{
return new Product(0,"iPhone7",5899,"最新款苹果手机");
}
}
export class Product{
constructor(
public id:number,public title:string,public price:number,public desc:string
){}
}@H_404_15@
修改app.module.ts
providers: [ProductService],@H_404_15@
修改product1.component.ts
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();
}
}@H_404_15@
修改product1.component.html
<div>
<h1>商品详情</h1>
<h2>名称:{{product.title}}</h2>
<h2>价格:{{product.price}}</h2>
<h2>描述:{{product.desc}}</h2>
</div>@H_404_15@
修改app.component.html
<div>
<div>
<h1>基本的依赖注入案例</h1>
</div>
<div>
<app-product1></app-product1>
</div>
</div>@H_404_15@
运行程序
新建组件ng g component product2
新建服务ng g service shared/anotherProduct
修改another-product.service.ts
import { Injectable } from '@angular/core';
import {Product,ProductService} from "./product.service";
@Injectable()
export class AnotherProductService implements ProductService{
getProduct(): Product {
return new Product(1,"sumsung7",4899,"最新款三星手机");
}
constructor() { }
}@H_404_15@
修改product2.component.ts
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();
}
}@H_404_15@
修改product2.component.html
<div>
<h1>商品详情</h1>
<h2>名称:{{product.title}}</h2>
<h2>价格:{{product.price}}</h2>
<h2>描述:{{product.desc}}</h2>
</div>@H_404_15@
修改app.component.html
<div>
<div>
<h1>基本的依赖注入案例</h1>
</div>
<div>
<app-product1></app-product1>
<app-product2></app-product2>
</div>
</div>@H_404_15@
运行程序
当一个提供器声明在模块中时,是对所有组件可见的
当声明在组件中时,只对组件和其子组件可见
当模块和组件中的提供器拥有相同的token(ProductService)时,组件中的会覆盖模块中的
我们应该优先把提供器声明在模块中,只有需要对其他组件不可见时才声明在组件中,但这种情况是非常罕见的。
@Injectable()
export class ProductService {@H_404_15@
@Injectable()这个装饰器的意思是,其他服务也可以注入到这个服务中,建议给每个服务都添加这个装饰器。
问题:为啥我组件上没有声明这个装饰器也可以注入服务,因为@Component装饰器是@Injectable()装饰器的子类。
而这个服务是否可以注入到其他服务中,是根据他是否声明在app.module.ts中的providers属性中决定的。
新建服务ng g service shared/logger
修改logger.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
constructor() { }
log(message:string){
console.log(message);
}
}@H_404_15@
修改product.service.ts
@Injectable()
export class ProductService {
constructor(private logger:LoggerService) { }
getProduct():Product{
this.logger.log("getProduct方法被调用");
return new Product(0,"最新款苹果手机");
}
}@H_404_15@
修改app.module.ts
providers: [ProductService,LoggerService],@H_404_15@
运行程序
使用工厂和值声明提供器
以前用的
providers: [ProductService,@H_404_15@
意思是当有组件或指令声明自己要使用一个ProductService类型的token时,实例化一个ProductService对象,这里实例化的意思就是new一个实例出来。
有时候并不是new一下就可以满足我们的要求的。比如实例化对象的时候要通过构造函数传递参数,比如要根据实际要求实例化不同的对象。这时候就需要工厂提供器。
修改product2.component.ts
@Component({
selector: 'app-product2',templateUrl: './product2.component.html',styleUrls: ['./product2.component.css']
})@H_404_15@
修改app.module.ts
providers: [{
provide:ProductService,useFactory:()=>{
let logger=new LoggerService();
let dev=Math.random()>0.5;
if(dev){
return new ProductService(logger);
}else{
return new AnotherProductService(logger);
}
}
},@H_404_15@
修改product.service.ts
constructor(public logger:LoggerService) { }@H_404_15@
修改another-product.service.ts
constructor(public logger:LoggerService) { }@H_404_15@
运行程序,这时候会报一个错误
ERROR in Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 24:17 in the original .ts file),resolving symbol AppModule in F:/WebstormProjects/di/src/app/app.module@H_404_15@
原因是useFactory后边直接跟的是一个函数。错误信息中也给出了解决办法Consider replacing the function or lambda with a reference to an exported function。让把这个函数换成一个引用。
工厂方法创建的对象是一个单例对象
export function Factory(){
let logger = new LoggerService();
let dev = Math.random() > 0.5;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}@H_404_15@
问题:其中LoggerService的声明和工厂方法耦合在了一起
修改factory.ts
export function Factory(logger: LoggerService) {
let dev = Math.random() > 0.5;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}@H_404_15@
修改app.module.ts
providers: [{
provide:ProductService,useFactory:Factory,deps:[LoggerService]
},@H_404_15@
问题,现在我们具体声明哪个对象是根据一个随机数来判断的。但实际开发中肯定不是这样的,而是根据一个变量来判断。那变量能依赖注入吗?是可以的
修改factory.ts
export function Factory(logger: LoggerService,isDev) {
if (isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}@H_404_15@
修改app.module.ts
providers: [{
provide:ProductService,deps:[LoggerService,"IS_DEV_ENV"]
},LoggerService,{
provide:"IS_DEV_ENV",useValue:false
}],@H_404_15@
这里给传入了一个固定的值false
也可以传入一个对象
export function Factory(logger: LoggerService,appConfig) {
if (appConfig.isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}@H_404_15@
修改app.module.ts
providers: [{
provide:ProductService,"APP_CONFIG"]
},{
provide:"APP_CONFIG",useValue:{isDev:false}
}],@H_404_15@
注入器及其层级关系
提供器只是提供实例化好的对象。而把实例化好的对象注入到组件的工作是由注入器来完成的。
在应用启动时会创建一个应用级的注入器,会把模块中声明的提供器和引用模块中声明的提供器都注册到这个注入器中。然后会启动主组件AppComponent,会为这个主组件创建一个主组件注入器,并将主组件中声明的提供器注册到主组件注入器。
然后主组件的模板中会引入子组件,当子组件创建时,主组件的注入器会为子组件也创建一个子组件注入器,然后将子组件的提供器注册上去。
Angular可以通过构造函数的参数自动注入依赖,Angular只有一个注入点就是构造函数。
现在弄一个手工注入的例子,但在实际开发中不建议这样用。
import {Component,OnInit,Injector} from '@angular/core';
import {Product,styleUrls: ['./product2.component.css']
})
export class Product2Component implements OnInit {
product: Product;
// constructor(private productService: ProductService) {
// }
private productService:ProductService;
constructor(private injector: Injector) {
this.productService=injector.get(ProductService);
}
ngOnInit() {
this.product = this.productService.getProduct();
}
}@H_404_15@
改造Auction
- 编写ProductService.包含3个方法:getProducts(),getProduct(id),以及getCommentsForProduct(id)
- 修改路由配置。在从商品列表进入商品详情时不再传递商品名称,改为传递商品ID
- 注入ProductService并使用其服务。
新建服务ng g service shared/product
修改product.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class ProductService {
private products:Product[]= [
new Product(1,'第一个商品',1.99,3.5,'这是第一个商品,是我在学习慕课网Angular入门实战时创建的',['电子产品']),new Product(2,'第二个商品',2.99,2.5,'这是第二个商品,是我在学习慕课网Angular入门实战时创建的',['电子产品','硬件设备']),new Product(3,'第三个商品',3.99,4.5,'这是第三个商品,是我在学习慕课网Angular入门实战时创建的',new Product(4,'第四个商品',4.99,1.5,'这是第四个商品,是我在学习慕课网Angular入门实战时创建的',new Product(5,'第五个商品',5.99,'这是第五个商品,是我在学习慕课网Angular入门实战时创建的',new Product(6,6.99,'这是第六个商品,是我在学习慕课网Angular入门实战时创建的',['电子产品'])
];
private comments:Comment[]=[
new Comment(1,1,"2017-09-26 22:22:22","张三",5,"东西不错"),new Comment(2,"2017-03-26 22:22:22","李四",3,"东西是不错"),new Comment(3,"2017-04-26 22:22:22","王五",4,"东西很不错"),new Comment(4,2,"2017-05-26 22:22:22","赵六","东西非常不错"),]
constructor() { }
getProducts(){
return this.products;
}
getProduct(id:number):Product{
return this.products.find((product)=>product.id==id);
}
getCommentsForProductId(id:number):Comment[]{
return this.comments.filter((comment:Comment)=>comment.productId==id);
}
}
export class Product {
constructor(public id: number,public title: string,public price: number,public rating: number,public desc: string,public categories: Array<string>) {
}
}
export class Comment{
constructor(public id:number,public productId:number,public timestamp:string,public user:string,public rating:number,public content:string){
}
}@H_404_15@
修改app.module.ts
const routeConfig:Routes=[
{path:'',component:HomeComponent},{path:'product/:productId',component:ProductDetailComponent}
]
providers: [ProductService],@H_404_15@
修改product.component.ts
export class ProductComponent implements OnInit {
private products:Product[];
private imageUrl= 'http://placehold.it/320x150';
constructor(private productService:ProductService) {}
ngOnInit() {
this.products=this.productService.getProducts();
}
}@H_404_15@
修改product.component.html
<div *ngFor="let product of products" class="col-md-4 col-sm-4 col-lg-4">
<div class="thumbnail">
<img [src]="imageUrl">
<div class="caption">
<h4 class="pull-right">{{product.price}}元</h4>
<h4><a [routerLink]="['/product',product.id]">{{product.title}}</a></h4>
<p>{{product.desc}}</p>
</div>
<div>
<app-stars [rating]="product.rating"></app-stars>
</div>
</div>
</div>@H_404_15@
修改product-detail.component.ts
import {Component,OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Product,ProductService,Comment} from "../shared/product.service";
@Component({
selector: 'app-product-detail',templateUrl: './product-detail.component.html',styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
product: Product;
comments: Comment[];
constructor(private routeInfo: ActivatedRoute,private productService: ProductService) {
}
ngOnInit() {
let productId: number = this.routeInfo.snapshot.params["productId"];
this.product = this.productService.getProduct(productId);
this.comments = this.productService.getCommentsForProductId(productId);
}
}@H_404_15@
修改product-detail.component.html
<div class="thumbnail">
<img src="http://placehold.it/820x230">
<h4 class="pull-right">{{product.price}}元</h4>
<h4>{{product.title}}</h4>
<p>{{product.desc}}</p>
<div>
<p class="pull-right">{{comments.length}}</p>
<p>
<app-stars [rating]="product.rating"></app-stars>
</p>
</div>
</div>
<div class="well">
<div class="row" *ngFor="let comment of comments">
<hr>
<div class="col-md-12">
<app-stars [rating]="comment.rating"></app-stars>
<span>{{comment.user}}</span>
<span class="pull-right">{{comment.timestamp}}</span>
<p></p>
<p>{{comment.content}}</p>
</div>
</div>
</div>@H_404_15@