Angular-多级配置搞事情啦

558 阅读5分钟
上一篇文章写到:
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中值的流动其实是从上到下单向流动的。就像一颗倒置的树,营养(数据)从根(根组件)传递到各个树枝上。

参考: