1. 程式人生 > >Angular4-線上競拍應用-依賴注入

Angular4-線上競拍應用-依賴注入

依賴注入:Dependency Injection 簡稱DI

注入器

constructor(private productServie:ProductService){...}

提供器

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(0,"iPhone7",5899,"最新款蘋果手機");
  }

}

export class
Product{
constructor( public id:number, public title:string, public price:number, public desc:string ){} }

修改app.module.ts

 providers: [ProductService],

修改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(1,"sumsung7",4899,"最新款三星手機");
  }

  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(0,"iPhone7",5899,"最新款蘋果手機");
  }

}

修改app.module.ts

  providers: [ProductService,LoggerService],

執行程式

使用工廠和值宣告提供器

以前用的

providers: [ProductService,LoggerService],

意思是當有元件或指令宣告自己要使用一個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()>0.5;
    if(dev){
      return new ProductService(logger);
    }else{
      return new AnotherProductService(logger);
    }
  }
},LoggerService],

修改product.service.ts

constructor(public logger:LoggerService) { }

修改another-product.service.ts

constructor(public logger:LoggerService) { }

執行程式,這時候會報一個錯誤

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

原因是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);
    }

}

問題:其中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);
  }

}

修改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(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, 3.5, '這是第五個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子產品']),
    new Product(6, '第五個商品', 6.99, 2.5, '這是第六個商品,是我在學習慕課網Angular入門實戰時建立的', ['電子產品'])
  ];

  private comments:Comment[]=[
    new Comment(1,1,"2017-09-26 22:22:22","張三",5,"東西不錯"),
    new Comment(2,1,"2017-03-26 22:22:22","李四",3,"東西是不錯"),
    new Comment(3,1,"2017-04-26 22:22:22","王五",4,"東西很不錯"),
    new Comment(4,2,"2017-05-26 22:22:22","趙六",2,"東西非常不錯"),
  ]
  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>