上一篇文章写到:
input组件的方式,可以扩展为依靠服务在业务模块中进行配置,以达到每个模块所用的同一个公共组件拥有不同的交互。
但经过具体实践发现,在惰性加载模块进行配置是可行的;在急性加载模块配置是会出事的!
结合官网的这篇文档可以一窥究竟:Angular - 多级依赖注入器,可以从“元素注入器”读起。 那其实有一句挺有意思的:
在不同层次上重新配置一个或多个提供商的能力,开启了一些既有趣又有用的可能性。
关键
那是时候展现真正的技术了。完整代码
预备知识
服务提供商
做个比喻:打电话可以视为电信提供的一个服务,那么电信就可以视为打电话这个服务的提供商。假如就电信这么一个公司,没有它就没法使用打电话的服务。那么需要使用服务就需要服务提供商。
再经过层层“代理”,模块可以提供服务,组件也可以提供服务。它们在各自的范围内,提供服务。
服务的分类
以下示例均采用可以摇树优化的方式提供服务;组件提供的除外,因为一般上,组件会需要使用服务的方法或数据,若采用此方式,则会造成循环依赖。
建议:将服务放到最顶层的业务模块或组件中提供,在其子组件中,通过注入器冒泡的特性去获取该服务的实例或方法或值。
1.单例服务
在 Angular 中有两种方式来生成单例服务:声明该服务应该在应用的根上提供。把该服务包含在 AppModule 或某个只会被 AppModule 导入的模块中。
// 前者
@Injectable({
providedIn: 'root'
})
// 抽离core模块是目前看到的官网最佳实践
@NgModule({
imports: [
CommonModule
],
declarations: [],
providers: [ShareModuleConfigService],
})
export class CoreModule { }
@NgModule({
imports: [
CoreModule,
// ....
],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: []
})
export class AppModule { }
2.模块级服务:
@Injectable({
providedIn: EagerlyLoadedModule
})
3.组件级服务
@Component({
selector: 'app-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.css'],
providers: [IndexService]
})
样例
文件夹结构。子模块默认页IndexComponent
1.准备工作
(1)根组件中拥有helloComponent
(2)各子模块的Index.component.html内也有helloComponent
(3)share模块中的helloComponent将使用ShareModuleConfigService内的值进行显示
export class HelloComponent implements OnInit {
@Input() txt: string;
constructor(
// 可选的服务
@Optional() private config: ShareModuleConfigService ) {}
ngOnInit() {
this.txt = this.config ? this.config.helloCompTxt : null;
}
}
(4)我们还有一个由coreModule提供的单例服务ShareModuleConfigService(将share模块可配置的集中于此,供各模块灵活配置),修改此配置来达到自定义share模块的某些展示或交互。
@Injectable()
export class ShareModuleConfigService {
helloCompTxt: string;
data = [1];
constructor() {
console.log('ShareModuleConfigService constructor');
}
}
(5)均通过useValue的方式简单的配置一下:
providers: [
{
provide: ShareModuleConfigService,
useValue: {
helloCompTxt: '惰性模块搞一下hello组件',
data: [4]
}
}
]
(6)我们将在页面上看到在不同层级下的helloComponent的展示效果。
2.惰性加载模块内配置
首先将样例代码中AppModule中imports中注释掉EagerlyLoadedModule,将会看到这样的运行效果。
根组件展示了ShareModuleConfigService的初始值
然后,点击”去懒加载模块(惰性加载模块)“,将会看到:
经过惰性模块修改后,初始值被改变了,实现了模块级配置
再来修改下lazy-loaded中的Index.component.ts,我已写好,只要把注释去掉即可。那么将会看到这样的效果。
组件的配置,”覆盖“掉了模块内的配置
还有一点值得注意:根组件所使用的值未变化,可以理解为惰性模块将单例服务又实例化了一次。将两者完全隔离开了。由此可以达到各个惰性模块内拥有自己的配置,以将share模块内的组件、指令、管道实现不同的交互或功能。
3.急性模块内修改配置
一如之前的流程,但一开始将会看到不一样结果。去掉AppModule中之前的注释。将急性模块加载到应用里来。
根组件的值直接被修改了
然后,将AppModule中的将CoreModule放在最后
根组件的值并未发生改变
是不是很神奇?其实官方已经给出解释,让我们来看一看。
根AppModule导入了CoreModule,并把它的providers添加到了AppModule的服务提供商中。 特别是,Angular 会在@NgModule.providers前面添加这些导入的服务提供商。 这种顺序保证了AppModule中的服务提供商总是会优先于那些从其它模块中导入的服务提供商
由此,若是想保证单例服务的唯一性,就必须将CoreModule置于所有其余模块的后面,以免被急性模块干扰。
子组件的配置就不再提了,跟惰性模块是一样的。说了这么多,本文提到的点还是蛮多的。现在来总结一下:
1.惰性模块若需配置share模块:利用惰性模块新建实例的性质,在模块内直接配置。
2.急性模块若需配置share模块,则不能在模块内进行配置,要么会被单例服务覆盖,要么会覆盖单例服务。所以可以将配置提到该模块的根组件内以达到此效果。
3.由急性模块中的组件修改单例服务的值,根模块中的值其实并未变化的现象。可以看出,在Angular中值的流动其实是从上到下单向流动的。就像一颗倒置的树,营养(数据)从根(根组件)传递到各个树枝上。
参考: