简介
Angular框架可以直接确定依赖性在应用程序中的流动方向,从而使调试工作无缝地进行。
Angular允许通过父组件的注入器提供的依赖性在其子组件中共享,方法是将它们注入到子组件的构造器中。
为了更好地理解这一点,让我们考虑一下父-子依赖注入的实际方法。
要继续学习本教程,你应该有。
- Node.js v10.x
- 对Angular的了解
- 对TypeScript的了解
开始学习
运行下面的命令来创建一个新的Angular应用程序。
ng new phone-book
复制代码
CD进入电话簿目录,在终端运行下面的命令,为我们的应用程序的正确结构创建一个模块。
ng generate module contacts
复制代码
创建app/contacts/contact.model.ts
。
//app/contacts/contact.model.ts
export interface Contact {
id: number;
name: string;
phone_no: string;
}
复制代码
上面的片段使用了TypeScript接口。它创建了一个模型来验证将从产品的服务中返回的数据。
运行下面的命令来创建一个联系服务。
ng generate service contacts/contact
复制代码
更新contact.service.ts
。
//app/contacts/contact.service.ts
import { Injectable } from '@angular/core';
import { Contact } from './contact.model';
@Injectable({
providedIn: 'root'
})
export class ContactService {
constructor() { }
getContacts(): Contact[] {
return [
{ id: 1, name: 'Peter', phone_no: '09033940948' },
{ id: 2, name: 'Sam', phone_no: '07033945948'},
{ id: 3, name: 'Bryce', phone_no: '08033740948' },
{ id: 4, name: 'Lee', phone_no: '090339409321' },
{ id: 5, name: 'Albert', phone_no: '09066894948' }
];
}
}
复制代码
providedIn
属性为该服务创建了一个提供者。在这种情况下,providedIn: 'root'
指定Angular应该在根注入器中提供服务(即让它在整个应用程序中可用)。
现在,ContactService
可以被注入到我们应用程序的任何地方。
更新contact-list.component.ts
。
//app/contacts/contact-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Contact } from '../contact.model';
import { ContactService } from '../contact.service';
@Component({
selector: 'app-contact-list',
templateUrl: './contact-list.component.html',
styleUrls: ['./contact-list.component.css'],
providers: [ContactService]
})
export class ContactListComponent implements OnInit {
contacts: Contact[];
constructor(private contactService: ContactService) { }
ngOnInit(): void {
this.contacts = this.contactService.getContacts();
}
}
复制代码
上面的片段使用组件构造函数中的new关键字实例化了ContactService
私有属性,在ngOnInit
方法中调用contactService
的getContacts
方法,并将返回值分配给contacts
属性。
更新contact-list.component.html
。
//app/contacts/contact-list.component.html
<h3>My Contact List</h3>
<ul>
<li *ngFor="let contact of contacts">
{{contact.name}}
</li>
</ul>
<app-recent-contact></app-recent-contact>
复制代码
上述代码段使用ngFor
指令来显示联系人列表,也接受RecentContact
作为其直接子组件。
更新app/contacts/recent-contact.component.ts
。
//app/contacts/recent-contact.component.ts
import { Component, OnInit } from '@angular/core';
import { ContactService } from '../contact.service';
import { Contact } from '../contact.model';
@Component({
selector: 'app-recent-contact',
templateUrl: './recent-contact.component.html',
styleUrls: ['./recent-contact.component.css']
})
export class RecentContactComponent implements OnInit {
contacts: Contact[];
constructor(private contactService: ContactService) { }
ngOnInit(): void {
this.contacts = this.contactService.getContacts();
}
}
复制代码
RecentContactComponent
,作为ContactListComponent
的直接子组件,即使没有通过@component
装饰器的providers
属性实际提供,也可以访问ContactListComponent
提供的依赖关系。
更新app/contacts/recent-contact.component.html
。
//app/contacts/recent-contact.component.html
<h3>My Recent Contacts</h3>
<ul>
<li *ngFor="let contact of contacts | slice:0:3">
{{contact.name}}
</li>
</ul>
复制代码
在这里,我们将SlicePipe
应用到ngFor
语句中,只显示最后三个联系人。
使用ng serve
命令运行应用程序,我们应该在浏览器中呈现所有联系人和最近联系人的列表。
覆盖Angular中的依赖关系
在Angular中重写依赖关系需要两个关键属性。
provide
– 指向你想覆盖的依赖关系useClass
– 指向新的依赖关系,它将覆盖providers
属性中的现有依赖关系。
它遵循一个简单的格式。
@Component({
providers: [
{ provide: Service, useClass : FakeService }
]
})
复制代码
何时覆盖依赖关系
在某些情况下,您希望覆盖您的构造的默认解析。
这些是一些典型的情况。
- 编写单元测试时覆盖一个提供者
- 在不对依赖关系进行修改的情况下为其添加一个独特的功能
后者是通过使用extend
关键字从现有依赖关系中创建一个新的依赖关系来实现的。
让我们考虑后一种情况的实际做法,创建一个名为RecentContactService
的新服务,它将扩展ContactService
,并使用slice
数组方法过滤掉数据,而不是使用模板中的管道。
更新recent-contact.service.ts
。
//app/contacts/recent-contact.service.ts
import { Injectable } from '@angular/core';
import { ContactService } from './contact.service';
import { Contact } from './contact.model';
@Injectable({
providedIn: 'root'
})
export class RecentContactService extends ContactService {
constructor() {
super();
}
getContacts(): Contact[] {
return super.getContacts().slice(Math.max(super.getContacts().length - 3, 0))
}
}
复制代码
在这里,getContacts
方法从ContactService
所产生的数组中返回最后三个项目。
现在,我们可以将这个新的服务(依赖关系)添加到providers
的属性中。RecentContactComponent
更新recent-contact.component.ts
。
//app/contact/recent-contact.component.ts
...
import { RecentContactService } from '../recent-contact.service';
@Component({
selector: 'app-recent-contact',
templateUrl: './recent-contact.component.html',
styleUrls: ['./recent-contact.component.css'],
providers: [{
provide: ContactService,
useClass: RecentContactService
}]
})
export class RecentContactComponent implements OnInit {
contacts: Contact[];
constructor(private recentContactService: RecentContactService) {}
ngOnInit(): void {
this.contacts = this.recentContactService.getContacts();
}
}
复制代码
在这里,useClass
属性允许Angular用我们的新依赖性(RecentContactService
)覆盖我们之前的依赖性(ContactService
)。
用字符串值覆盖依赖关系
在我们想要覆盖的依赖是一个字符串的情况下,useValue
语法会很方便。
比如我们的应用程序有一个DBconfig
文件。
//app/db.config.ts
export interface DBConfig {
name: string;
version: number;
}
export const databaseSettings: DBConfig = {
name: 'MongoDB',
version: 2.0
};
复制代码
为了使数据库配置能够在我们的应用程序中访问,我们需要提供一个InjectionToken
对象。
//app/db.config.ts
import { InjectionToken } from '@angular/core';
export const DB_CONFIG = new InjectionToken<DBConfig>('db.config');
...
复制代码
现在,我们的组件可以使用@Inject
装饰器访问数据库配置。
//app/app.component.ts
import { Component, Inject } from '@angular/core';
import { DB_CONFIG, databaseSettings, DBConfig } from './db.config';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [{
provide: DB_CONFIG,
useValue: databaseSettings
}]
})
export class AppComponent {
name: string;
version: number;
constructor(@Inject(DB_CONFIG) config: DBConfig) {
this.name = config.name;
this.version = config.version;
}
}
//app/app.component.html
<h4>This app uses {{name}} Database v. {{version}}</h4>
<app-contact-list></app-contact-list>
复制代码
在运行时重写依赖性
useFactory
关键字允许Angular决定在运行时向构造中注入什么依赖。
import { RecentContactService } from './recent-contact.service';
import { ContactService } from './contact.service';
export function contactFactory(isRecent: boolean) {
return () => {
if (isRecent) {
return new RecentContactService();
}
return new ContactService();
};
}
复制代码
上面的片段是一个工厂,根据布尔条件返回RecentContactService
或ContactService
。
现在,我们可以修改RecentContactComponent
‘的providers
属性,如下所示。
...
providers: [{
provide: ContactService,
useClass: contactFactory(true)
}]
...
复制代码
有了这个,我们可以尽可能多地覆盖任何依赖关系。这就更容易了,因为我们只需要把新的依赖关系添加到contactFactory
,调整条件,最后调整新的依赖关系的相应组件的useClass
属性。
在运行时覆盖依赖关系的限制
如果这些依赖中的任何一个在其构造函数中注入其他依赖与之前的实现contactFactory
,Angular将抛出一个错误。
假设RecentContactService
和ContactService
都依赖于AuthService
依赖关系,我们将不得不对contactFactory
进行如下调整。
export function contactFactory(isRecent: boolean) {
return (auth: AuthService) => {
if (isRecent) {
return new RecentContactService();
}
return new ContactService();
};
}
复制代码
RecentContactComponent
然后,我们必须将AuthService
依赖关系添加到providers
对象的deps
属性中,如下所示。
...
providers: [{
...
deps: [AuthService]
}]
...
复制代码
结论
在大规模的Angular应用程序上工作,需要很好地理解依赖注入的工作方式。在大规模的Angular应用中,了解如何以及何时覆盖依赖性是至关重要的,因为它可以帮助你避免应用中不必要的代码重复。
这篇文章的源代码可以在GitHub上找到。
The postOverriding dependencies in the Angular injector hierarchyappeared first onLogRocket Blog.