3

问题描述

初学Angular,可能对一堆注解有些懵。

我们一起通过实例来探讨Angular的依赖注入。

一路尝试

@Injectable

一个命令建的StockService,一个手动建的TestService

@Injectable({
    providedIn: 'root'
})
export class StockService {

    constructor(private testService: TestService) {
    }
}
export class TestService {

    constructor() {
    }
}

TestService未加@Injectable注解,只是一个普通的TypeScript类。

当前台组件中用到StockService时,Angular为我们构造StockService,构造函数中依赖TestService,然后Angular去构造TestService

clipboard.png

Uncaught (in promise): Error: StaticInjectorError(AppModule)[TestService]: 
StaticInjectorError(Platform: core)[TestService]: 
NullInjectorError: No provider for TestService!

然后发现控制台报错,无法提供TestService,因为它没有被Angular管理。

import {Injectable} from '@angular/core';

@Injectable()
export class TestService {

    constructor() {
    }
}

加上@Injectable,将该类交给Augular管理。

clipboard.png

无效,仍然报错。发现只交给Angular托管是不行的,我们还需要声明提供器。

提供器

需要提供器告诉Angular如何注入相关对象。

@NgModule({
    declarations: [],
    imports: [],
    providers: [TestService],
    bootstrap: [AppComponent]
})
export class AppModule {
}

clipboard.png

AppModuleproviders数组中声明TestService提供器。

按类型提供,所以上面的声明与下面的代码是等价的。

providers: [{ provide: TestService, useClass: TestService }]

标记在AppModule上,表示该TestService的注入是应用在该模块上的。在该模块中,TestService是单例的。

{ provide: TestService, useClass: TestService },表示当需要注入TestService类型的对象时,使用TestService类构造出的对象进行注入。

Angular中,为什么@Component@Pipe里能用构造函数注入@Injectable呢?答案与Spring一致,为什么@Controller@Service里能Autowired呢?思想相同。

作用域

@NgModule({
    providers: [TestService]
})
export class AppModule {
}

@Component({
    providers: [{ provide: TestService, useClass: AnotherTestService }]
})
export class StockComponent {
    constructor(private testService: TestService) {
    }
}

声明一个模块级的与一个组件级的提供器,表示在本模块或本组件中应该注入什么。

就像如上代码,声明在整个AppModule模块中,注入的TestServiceTestService类实例化的。

但是具体到组件,StockComponent,虽然声明整个模块都使用TestService,但是有时不符合需求,所以在此组件中使用AnotherTestService注入。

就像Spring中的@Primary@Qualifier一样。

值提供与工厂方法

通过useValue的值提供,可以定义一些常量,又是枚举的思想,我们使用引用,而不是用常量。

{ provide: domain, useValue: 'www.mengyunzhi.com' }

除了值提供还有工厂方法,当Angular默认的实例化对象无法满足我们的要求时,我们要写自己的工厂函数生成我们的对象实例。

但是注意:虽然该方法叫工厂方法,但是该方法只在第一次用到该对象时执行一次,以后再需要用的还是之前构造出来的对象。

虽然叫工厂方法,但是还是单例的。

{ provide: ProductService, useFactory: () => { 返回一个对象实例 } }

摇树优化

现在如果用ng生成的Angular服务,标准的写法是providedIn: 'root'

粗略学习了一下,摇树优化就是按需加载,减小我们的包体积,缩短应用的加载时间。

@Injectable({
    providedIn: 'root'
})

只要在服务本身的@Injectable()装饰器中指定,而不是在依赖该服务的NgModule或组件的元数据中指定,你就可以制作一个可摇树优化的提供商。

要想覆盖可摇树优化的提供商,请使用其它提供商来配置指定的NgModule或组件的注入器,只要使用@NgModule()@Component()装饰器中的providers: []数组就可以了。

循环依赖

通过上面的学习,我们知道了Angular中的依赖注入是通过构造函数的参数注入来实现的。

但是只要是通过构造函数实现的IOC容器就会有问题,就像Spring中一样,如果循环依赖了怎么解决呢?

clipboard.png

@Injectable({
    providedIn: 'root'
})
export class StockService {

    constructor(private testService: TestService) {
    }
}
@Injectable({
    providedIn: 'root'
})
export class TestService {

    constructor(private stockService: StockService) {
    }
}

clipboard.png

Circular dependency detected:
src/app/service/stock.service.ts -> src/app/service/test.service.ts -> src/app/service/stock.service.ts

Circular dependency detected:
src/app/service/test.service.ts -> src/app/service/stock.service.ts -> src/app/service/test.service.ts

Spring一样,如果循环了,因为是使用构造函数注入,所以一个对象都构造不出来,无法解决。开发时应避免循环依赖。

总结

万物相通。

Hello, Angular!


张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。