一.依赖注入内容
- 什么是依赖注入
- 依赖注入:Dependency Injection简称DI(IOC实现控制反转的手段)
- 控制反转:Inversion of Control简称IOC(将依赖的控制权从代码的内部转移到代码的外部)
- 实现了控制反转的框架叫做:IOC容器
二.依赖注入的好处
-
好处一:松耦合和可重用
- 实例:
//providers中定义所有要注入的内容,当模块中的组件构造函数使用时,angular采用依赖注入的方式将对应的内容注入进去,举例如下 @NgModule({ prividers:[ProductService]//表示providers:[{provide:ProductService, useClass:ProductService}]当需要注入ProductService时,注入的是ProductService的实例 }) export class AppModule {} //使用依赖注入的方式获得productService实例 @Component({ ...省略组件配置 }) export class ProductComponent{ product:Product; constructor(productService:ProductService){ this.product = productService.getProduct(); } }
-
好处二:提高了组件的可测试性
//如我们需要完成loginComponent组件,此组件依赖服务去判断是否登录成功 //此时我们可以自行编码MockLoginService逻辑进行硬编码返回值内容 //当其他人将真实的RealLoginService开发完成后,我们只需将provider修改即可
三.提供器入门
1.注入器
-
每一个组件都有一个注入器实例负责注入组件需要的对象,注入器是angular提供的服务类,一般情况下不需要直接调用注入器的方法,注入器会自动根据组件的构造函数将组件所需的对象注入进组件
-
实例
constructor(private productService: ProductService){...}
- 在构造函数上声明productService属性,类型是ProductService
- Angular的注入器在看到这样的构造函数声明时,就会在整个angular应用中去寻找一个productService实例,如果找到实例,就会将实例注入到productService对象中,我们直接使用即可
2.提供器
-
为了让angular知道如何产生对应的实例,就需要指定提供器,即providers
-
实例
providers:[ProductService]
- 使用providers属性来声明所有的provider
- 上述代码是如下代码的简写
providers:[{provide:ProductService,useClass:ProductService}]
,provide声明的是token的内容 - 如声明为
provider:[{provide:ProductService,useClass:AnotherProductService}]
,则当我们在函数中声明需要一个ProductService的token时,new出来的是AnotherProductService【通过token确定需要的provider,通过useClass确定要实例化的类】- 另一声明方法:
providers:[{provide:ProductService,useFactory:()={...}}]
,在useFactory中需要写一些实例代码完成一些实例化的工作
- 另一声明方法:
3.实例学习
-
创建应用
ng new ditest
-
添加组件
ng g component product1
,添加服务(在多个组件中共享)ng g service shared/product
生成到shared文件夹下的product中 -
修改服务类shared/product
-
添加Product类
export class Product { constructor( public int: number, public title: string, public price: number, public desc: string ) { } }
-
定义获取Product的方法getProduct()
getProduct(): Product { return new Product(0, 'iphone7', 6999, '最新苹果手机'); }
-
-
在
app.module.ts
中利用提供器添加模块的声明:providers:[ProductService]
-
修改组件的控制器
-
定义product属性用来接收service返回的product
-
定义构造函数,使用依赖注入的方式注入ProductService
-
在初始化方法中调用service对象的getProduct方法
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 { public product: Product; constructor(private productService: ProductService) { } ngOnInit() { this.product = this.productService.getProduct(); } }
-
在product模板中显示商品信息
<div> <h1>商品信息</h1> <p>商品题目:{{product.title}}}</p> <p>商品价格:{{product.price}}}</p> <p>商品描述:{{product.desc}}}</p> </div>
-
在app模板中显示product组件
<div> <div> <h1>基本依赖注入案例</h1> <app-product1></app-product1> </div> </div>
-
4.提供器声明在组件中
-
上述实例将提供器声明在模块中,以下将阐述如何将提供器声明在组件中
-
声明第二个组件
ng g component product2
,声明第二个服务ng g service shared/anotherProduct
-
编写anotherProduct服务内容
- 实现ProductService,重写getProduct()方法,返回另一个product实例
import { Injectable } from '@angular/core'; import {Product, ProductService} from './product.service'; @Injectable({ providedIn: 'root' }) export class AnotherProductService implements ProductService { getProduct(): Product { return new Product(1, 'iphone8', 7999, '最新款iphone手机'); } constructor() { } }
-
编写product2组件内容
-
控制器代码与product1相同
-
在组件级别(@Component)定义providers提供器,使用与模块中构造器注入的相同的token即ProductService,但是使用AnotherProductService类
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 { public product: Product; constructor(private productService: ProductService) { } ngOnInit() { this.product = this.productService.getProduct(); } }
-
模板代码与product1相同
<div> <h1>商品信息</h1> <p>商品题目:{{product.title}}</p> <p>商品价格:{{product.price}}</p> <p>商品描述:{{product.desc}}</p> </div>
-
-
在app模板中添加product2组件内容
<div> <div> <h1>基本依赖注入案例</h1> <app-product1></app-product1> <app-product2></app-product2> </div> </div>
5.总结提供器作用域规则
- 当一个提供器声明在模块中时,对所有组件可见,所有组件都可以注入
- 当一个提供器声明在组件中时,只对组件及其子组件可见,其他组件不可以注入
- 当声明在模块中的提供器与声明在组件中的提供器有相同的token时,声明在组件中的提供器会覆盖声明在模块中的提供器
- 一般情况下,我们应优先将提供器声明在模块中,只有在服务必须在其他组件不可见时才将其声明在组件中
- 其他内容
- 观察声明的ProductService类上有@Injectable()装饰器,装饰器的意思表示ProductService也可以通过构造函数注入其他服务(表示其他服务可以注入到这个服务中,不是这个服务可以注入到其他服务中,这服务能不能注入其他地方是由其是否在providers中声明来决定的)【只有声明了@Injectable()装饰器才可以注入其他服务,@Component()是@Injectable()装饰器的子类】
6.演示服务间互相注入
-
生成新的服务
ng g service shared/logger
-
给LoggerService服务添加log方法,打印参数message
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class LoggerService { constructor() { } log(message: string) { console.log(message); } }
-
在模块中添加loggerService的提供器
providers: [ProductService, LoggerService]
-
在ProductService构造函数中注入LoggerService
-
在ProductService的getProduct()方法中刚调用logger的log方法
import { Injectable } from '@angular/core'; import {Product, ProductService} from './product.service'; import {LoggerService} from './logger.service'; @Injectable({ providedIn: 'root' }) export class AnotherProductService implements ProductService { getProduct(): Product { this.loggerService.log('注入了loggerService'); return new Product(1, 'iphone8', 7999, '最新款iphone手机'); } constructor(private loggerService: LoggerService) { } }
四.使用工厂和值对象定义提供器
-
某些时候我们的服务对象可能不是简单new一下就可以满足要求的,比如有可能我们需要根据某些条件,决定具体实例化哪些对象,也可能实例化某些对象时需要传递一些参数,此时需要用到工厂提供器。
-
工厂提供器实例演示(通过随机函数决定实例化哪个服务)
-
在product2组件中去掉内部的提供器声明,此时product1和product2是使用同一个服务获取商品信息(在模块中的服务声明)
-
修改模块中提供器声明
providers: [ { provide: ProductService, useFactory: () => { const logger: LoggerService = new LoggerService(); const dev: Boolean = Math.random() < 0.5; if (dev) { return new ProductService(logger); } else { return new AnotherProductService(logger); } } } , LoggerService]
-
修改ProdectService和AnotherProductService构造函数
constructor(public logger: LoggerService){}
-
此时刷新界面时,根据随机数的不同会导致使用不同的服务。但此时LoggerService是自己new出来的,耦合度高。
-
在提供器中注入其他提供器,修改代码(deps数组用来声明工厂方法依赖的参数)
providers: [ { provide: ProductService, useFactory: (logger) => { const dev: Boolean = Math.random() < 0.5; if (dev) { return new ProductService(logger); } else { return new AnotherProductService(logger); } }, deps: [LoggerService] } , LoggerService]
- 此时angular会得到LoggerService的实例并注入到ProductService的工厂方法参数中
-
使用变量进行依赖注入,定义字符串类型的提供器,并使用useValue属性设定对应的值
providers: [ { provide: ProductService, useFactory: (logger, isDev) => { if (isDev) { return new ProductService(logger); } else { return new AnotherProductService(logger); } }, deps: [LoggerService, 'IS_DEV_ENV'] } , LoggerService, { provide: 'IS_DEV_ENV', useValue: true }]
-
使用对象进行依赖注入,定义字符串类型的提供器,并使用useValue属性设定对应的对象值
providers: [ { provide: ProductService, useFactory: (logger, isDev) => { if (isDev.dev) { return new ProductService(logger); } else { return new AnotherProductService(logger); } }, deps: [LoggerService, 'IS_DEV_ENV'] } , LoggerService, { provide: 'IS_DEV_ENV', useValue: {dev: false} }]
-
五.注入器及层级关系
-
注入器作用:将实例化好的对象注入到组件中
-
应用启动时,angular会创建应用级注入器,将应用中的提供器注册到此注入器中,被注册的提供器包括应用中声明的提供器和所有被引用的模块中声明的提供器。之后,angular会创建启动模块的主组件[AppComponent],同时应用级的注入器会为主组件创建主组件注入器,并将组件中声明的提供器注册到这个组件级的注入器上。当子组件创建时,父组件的注入器会为子组件也创建一个注入器,并将组件中声明的提供器注册到这个子组件的注入器上。
-
一般情况下不需要编码调用注入器的方法,angular可以通过构造函数参数自动注入所需依赖。且angular只有一个注入点在构造函数参数。
-
将angular本身的注入器Inject注入到组件的构造函数中,组件向注入器请求ProductService服务[实际使用中,需要避免使用此写法,常在框架中使用通用方式获取服务采用此写法]
private productService: ProductService; constructor(private injector: Injector){ this.productService = injector.get(ProductService); }
六.改造Auction
-
进入商品详情页时显示商品相关信息,及商品评论信息
-
重构Auction
- 编写ProductService,包含三个方法:
getProducts()
、getProduct(id)
以及getCommentsForProduct(id)
- 修改路由配置,在从商品列表进入商品详情时不再传递商品名称,给为传递商品Id
- 注入ProductService并修改其服务
- 编写ProductService,包含三个方法:
-
实例代码
-
通过
ng g service shared/product
创建product.service.tsimport { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ProductService { constructor() { } private products: Product[] = [ new Product (1, '第一个商品', 1.99, 3.5, '这是第一个商品描述', ['电子产品', '硬件产品']), new Product (2, '第二个商品', 2.99, 2.5, '这是第二个商品描述', ['电子产品']), new Product (3, '第三个商品', 3.99, 0.5, '这是第三个商品描述', ['电子产品', '硬件产品']), new Product (4, '第四个商品', 4.99, 1.5, '这是第四个商品描述', ['硬件产品']), new Product (5, '第五个商品', 5.99, 2.5, '这是第五个商品描述', ['电子产品', '硬件产品']), new Product (6, '第六个商品', 6.99, 4.5, '这是第六个商品描述', ['电子产品']), new Product (7, '第七个商品', 7.99, 3.5, '这是第七个商品描述', ['电子产品', '硬件产品']), new Product (8, '第八个商品', 8.99, 1.5, '这是第八个商品描述', ['硬件产品']), new Product (9, '第九个商品', 9.99, 2.5, '这是第九个商品描述', ['图书产品']) ]; private comments: Comment[] = [ new Comment(1, 1, '2018-10-06 22:22:22', '张三1', 3, '东西不错1'), new Comment(2, 1, '2018-10-06 22:22:23', '张三2', 3.5, '东西不错2'), new Comment(3, 1, '2018-10-06 22:22:24', '张三3', 4, '东西不错3'), new Comment(4, 2, '2018-10-06 22:22:25', '张三4', 5, '东西不错4') ] getProducts() { return this.products; } getProduct(id: number): Product { return this.products.find( (product) => product.id == id ); // 获得一个对象使用数组的find方法,find中匿名方法参数为每一个数组中的对象 } getCommentsForProductId (id: number): Comment[] { return this.comments.filter( (comment) => comment.productId == id); // 获得一个集合使用数组的filter方法,filter中匿名方法参数为每一个数组中的对象 } } 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,添加提供器和修改路由传递id属性
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { NavbarComponent } from './navbar/navbar.component'; import { FooterComponent } from './footer/footer.component'; import { SearchComponent } from './search/search.component'; import { ProductComponent } from './product/product.component'; import { StarsComponent } from './stars/stars.component'; import { CarouselComponent } from './carousel/carousel.component'; import { ProductDetailComponent } from './product-detail/product-detail.component'; import { HomeComponent } from './home/home.component'; import { RouterModule, Routes} from '@angular/router'; import {ProductService} from './shared/product.service'; const routeConfig: Routes = [ {path: '', component: HomeComponent}, {path: 'product/:productId', component: ProductDetailComponent} ] @NgModule({ declarations: [ AppComponent, NavbarComponent, FooterComponent, SearchComponent, ProductComponent, StarsComponent, CarouselComponent, ProductDetailComponent, HomeComponent ], imports: [ BrowserModule, RouterModule.forRoot(routeConfig) ], providers: [ProductService], bootstrap: [AppComponent] }) export class AppModule { }
-
修改
product.component.ts
中product获取方式,及html中传递的是id内容//ts import { Component, OnInit } from '@angular/core'; import {Product, ProductService} from '../shared/product.service'; @Component({ selector: 'app-product', templateUrl: './product.component.html', styleUrls: ['./product.component.css'] }) export class ProductComponent implements OnInit { public products: Product[]; public imgUrl = 'https://www.baidu.com/img/bd_logo1.png?where=super'; constructor(public productService: ProductService) { } // 组件被实例化的时候,此方法被调用一次,用来初始化组件中的数据 ngOnInit() { this.products = this.productService.getProducts(); } } //html <!-- Angular指令ngFor含义(在页面上循环创建html):循环products属性,每次循环的元素放在product变量中 --> <div *ngFor="let product of products" class="col-md-4 col-sm-4 col-lg-4" style="padding:20px;float: left;"> <div class="img-thumbnail"> <!-- 利用[]进行属性绑定,[]对标签属性括上,并在值中给出对应控制器的变量,则可以将变量值绑定给标签属性 --> <img [src]="imgUrl" style="width:100%"> <div class="figure-caption"> <h6 class="float-right">{{product.price}}元</h6> <h6><a [routerLink]="['/product',product.id]">{{product.title}}</a></h6> <p>{{product.desc}}</p> </div> <div> <!-- 表示star组件中的rating属性,应该由product.rating传递进去 --> <app-stars [rating]="product.rating"></app-stars> </div> </div> </div>
-
修改
product-detail.component.ts
中数据获取方式及html页面显示内容//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 { public product: Product; public comments: Comment[]; constructor(private routerInfo: ActivatedRoute, public productService: ProductService) { } ngOnInit() { const productId: number = this.routerInfo.snapshot.params['productId']; this.product = this.productService.getProduct(productId); console.log(this.product) this.comments = this.productService.getCommentsForProductId(productId); } } //html <div> <img src="http://static.runoob.com/images/mix/img_fjords_wide.jpg" class="img-thumbnail"> <div style="height: 100px"> <h4 class="float-right">{{product.price}}元</h4> <h4>{{product.title}}</h4> <p>{{product.desc}}</p> </div> <div> <p class="float-right">评论总数:{{comments.length}}</p> <p> 综合评分:<app-stars [rating]="product.rating"></app-stars> </p> </div> <div class="rpw" *ngFor="let comment of comments"> <hr> <div class="col-md-12"> <app-stars [rating]="comment.rating"></app-stars> <span>{{comment.user}}</span> <span class="float-right">{{comment.timestamp}}</span> <p></p> <p>{{comment.content}}</p> </div> </div> </div>
-