依賴注入:Dependency Injection 簡稱DI
注入器
提供器
providers:[{provider:ProductService,useClass:ProductService}]
providers:[ProductService]//如果provider和useClass一樣可以簡寫成這樣
providers:[{provider:ProductService,useClass:AnotherProductService}]
provider:[{provide:ProductService,useFactory:()=>{...}}]
依賴注入例子
建立一個項目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(,"iPhone7",,"最新款蘋果手機");
}
}
export class Product{
constructor(
public id:number,
public title:string,
public price:number,
public desc:string
){}
}
修改app.module.ts
修改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();
}
}
修改product1.component.html
<div>
<h1>商品詳情</h1>
<h2>名稱:{{product.title}}</h2>
<h2>價格:{{product.price}}</h2>
<h2>描述:{{product.desc}}</h2>
</div>
修改app.component.html
<div>
<div>
<h1>基本的依賴注入案例</h1>
</div>
<div>
<app-product1></app-product1>
</div>
</div>
運作程式
建立元件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(,"sumsung7",,"最新款三星手機");
}
constructor() { }
}
修改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();
}
}
修改product2.component.html
<div>
<h1>商品詳情</h1>
<h2>名稱:{{product.title}}</h2>
<h2>價格:{{product.price}}</h2>
<h2>描述:{{product.desc}}</h2>
</div>
修改app.component.html
<div>
<div>
<h1>基本的依賴注入案例</h1>
</div>
<div>
<app-product1></app-product1>
<app-product2></app-product2>
</div>
</div>
運作程式
當一個提供器聲明在子產品中時,是對所有元件可見的
當聲明在元件中時,隻對元件和其子元件可見
當子產品群組件中的提供器擁有相同的token(ProductService)時,元件中的會覆寫子產品中的
我們應該優先把提供器聲明在子產品中,隻有需要對其他元件不可見時才聲明在元件中,但這種情況是非常罕見的。
@Injectable()
export class ProductService {
@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);
}
}
修改product.service.ts
@Injectable()
export class ProductService {
constructor(private logger:LoggerService) { }
getProduct():Product{
this.logger.log("getProduct方法被調用");
return new Product(,"iPhone7",,"最新款蘋果手機");
}
}
修改app.module.ts
運作程式
使用工廠和值聲明提供器
以前用的
意思是當有元件或指令聲明自己要使用一個ProductService類型的token時,執行個體化一個ProductService對象,這裡執行個體化的意思就是new一個執行個體出來。
有時候并不是new一下就可以滿足我們的要求的。比如執行個體化對象的時候要通過構造函數傳遞參數,比如要根據實際要求執行個體化不同的對象。這時候就需要工廠提供器。
修改product2.component.ts
@Component({
selector: 'app-product2',
templateUrl: './product2.component.html',
styleUrls: ['./product2.component.css']
})
修改app.module.ts
providers: [{
provide:ProductService,
useFactory:()=>{
let logger=new LoggerService();
let dev=Math.random()>;
if(dev){
return new ProductService(logger);
}else{
return new AnotherProductService(logger);
}
}
},LoggerService],
修改product.service.ts
修改another-product.service.ts
運作程式,這時候會報一個錯誤
原因是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() > ;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}
問題:其中LoggerService的聲明和工廠方法耦合在了一起
修改factory.ts
export function Factory(logger: LoggerService) {
let dev = Math.random() > ;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}
修改app.module.ts
providers: [{
provide:ProductService,
useFactory:Factory,
deps:[LoggerService]
},LoggerService],
問題,現在我們具體聲明哪個對象是根據一個随機數來判斷的。但實際開發中肯定不是這樣的,而是根據一個變量來判斷。那變量能依賴注入嗎?是可以的
修改factory.ts
export function Factory(logger: LoggerService,isDev) {
if (isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}
修改app.module.ts
providers: [{
provide:ProductService,
useFactory:Factory,
deps:[LoggerService,"IS_DEV_ENV"]
},LoggerService,
{
provide:"IS_DEV_ENV",useValue:false
}],
這裡給傳入了一個固定的值false
也可以傳入一個對象
export function Factory(logger: LoggerService,appConfig) {
if (appConfig.isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}
修改app.module.ts
providers: [{
provide:ProductService,
useFactory:Factory,
deps:[LoggerService,"APP_CONFIG"]
},LoggerService,
{
provide:"APP_CONFIG",useValue:{isDev:false}
}],
注入器及其層級關系
提供器隻是提供執行個體化好的對象。而把執行個體化好的對象注入到元件的工作是由注入器來完成的。
在應用啟動時會建立一個應用級的注入器,會把子產品中聲明的提供器和引用子產品中聲明的提供器都注冊到這個注入器中。然後會啟動主元件AppComponent,會為這個主元件建立一個主元件注入器,并将主元件中聲明的提供器注冊到主元件注入器。
然後主元件的模闆中會引入子元件,當子元件建立時,主元件的注入器會為子元件也建立一個子元件注入器,然後将子元件的提供器注冊上去。
Angular可以通過構造函數的參數自動注入依賴,Angular隻有一個注入點就是構造函數。
現在弄一個手工注入的例子,但在實際開發中不建議這樣用。
import {Component, OnInit,Injector} 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']
})
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();
}
}
改造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(, '第一個商品', , , '這是第一個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子産品']),
new Product(, '第二個商品', , , '這是第二個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子産品', '硬體裝置']),
new Product(, '第三個商品', , , '這是第三個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子産品']),
new Product(, '第四個商品', , , '這是第四個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子産品']),
new Product(, '第五個商品', , , '這是第五個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子産品']),
new Product(, '第五個商品', , , '這是第六個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子産品'])
];
private comments:Comment[]=[
new Comment(,,"2017-09-26 22:22:22","張三",,"東西不錯"),
new Comment(,,"2017-03-26 22:22:22","李四",,"東西是不錯"),
new Comment(,,"2017-04-26 22:22:22","王五",,"東西很不錯"),
new Comment(,,"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){
}
}
修改app.module.ts
const routeConfig:Routes=[
{path:'',component:HomeComponent},
{path:'product/:productId',component:ProductDetailComponent}
]
providers: [ProductService],
修改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();
}
}
修改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>
修改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);
}
}
修改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>