美文网首页
Angular 模板解惑

Angular 模板解惑

作者: Ethan__Hunt | 来源:发表于2018-09-19 11:33 被阅读0次

    翻译来自国外大神

     上边是原文链接,英语好的可以直接进,翻译不周还望指正,共同提高.

    Angular

    Angular 中的各种模块是一个非常复杂的话题.Angular团队做了很多来写了非常长的关于NgModule的记录,可以点击这里(英文版的), 就是官网的文档 这里(中文版的),这里边的文档提供了比较详细的说明,你可以在这里找到大多数你想要的资料,但是这里边还是漏了一些,所以程序猿们会因此产生一些误解.我经常看到很多人误解了这些解释,用的建议解决方案也不对,因为他们不会到模板在底层是如何工作的.

    这篇文章提供了深入的解释,解析了一些在StackOverflow中经常看见的大家可能都有的疑惑,

    模块封装

    Angular 中模块封装的概念跟ES 中模块的概念比较像,可以声明的类型 比如组件(Component),指令(Directive) ,管道(Pipe), 这些类型可以被当前模块里边的组件来用. 举个例子:如果你想在App Module 里边的App Component ,用  A Module 里边的 a-comp 这个组件,

    code

    这里会报错的 ,

    code-Error

    这是因为在App module 里边没有声明 a-comp,如果你想用这个组件 就需要从定义这个组件的地方引进来,主要思路分两步: 第一步在A Module中导出ACompont 给别人用., 第二步App Module中引用A Module   .代码大概可以使这样.

    import AModule inAppModule

    也就是说你需要在App Module 中把A Module 引过来.这就是模块封装的概念,这样做还不够,需要在A module导出 a-comp,这样别的模块才可以使用.

    export AComponent

    改完之后 ,可能依旧报错,这时候需要把服务重启下.然后就好了.

    当然上边我们操作的是Component ,对于其他类型, Directive 和Pipe 也是同样的操作. 先导出再导入.

    需要注意一点: Angular 没有对entryComponents 中的包含的组件进行封装,如果你用动态Views 和动态组件实例化, 你可以使用在A Module 中的组件 而不需要将他们加到exports 的数组中,但是还是需要引用A Module ,相当于省去了上边的第一步 导出的过程.

     很多刚开始用Angular 的朋友可能以为对于Providers也是像上边一样根据模块进行封装的, 但是事实不是这样滴.在一个非懒加载的模块中定义的一个Provider,在整个App中都可以访问到.下一章会解释为什么是这样.


    层级模块

    关于引用模块最大的困惑是,很多人认为这样就实现了继承.比如之前的例子,可能认为在App Module 引入A Module ,所以App Module 就成了A Module 的父模块.然而事实不是这样,所有的模块在编译阶段就被合并了,所以在这里引用模块(App Module) 和被引用模块(A Module) 没有继承关系.

    跟Angular 中的Component 一样,Angular 会在根模块生成一个工厂 (Factory),根模块就是你在main.ts的 platformBrowserDynamic().bootstrapModule 指出的.

    root Module

    Angular 编译器生成的这个工厂(Factory)用 createNgModuleFactory 方法,需要传几个参数,

    1,对模块类的引用, module class reference

    2,根组件, bootstrap components

    3,带有entry Componnet的 Component Factory Resolver,component factory resolver with entry components(后来木有了)

    4,把所有模块中的Providers 合并之后的工厂,definition factory with merged module providers

    createNgModuleFactory

    我看了下参数,主要是三个 : 模块类型,跟组件,工厂的定义,

    最后两个3,4 加粗的地方,解释了为什么这里没有针对Provider 和entryComponents 的模块封装,是因为Angular 编译之后,之前是多个Module ,编译之后就成了一个Final Module. 这个Final Module 中包含 合并之后的所有Module. 在编译的时候, Angular 编译器不知道你在哪或者你想怎么来使用Provider 和动态组件,所以他不能控制Provider 和动态组件的封装,但是当解析组件的模板时,Angular知道他们是如何使用的 ,比如私有可声明组件,指令和管道.

    下面看一个Module 生成工厂的例子,假设你有A和B 两个模块,每个模块中都定义了一个Provider 和EntryComponent,

    在A Module中定义一个Provider 和entry Components

    AModule

    在B Module中定义一个Provider 和entry Components,这个跟A 比较像

    BModule    

    在App Module中引入A B Module,并且定义自己的 Provider "root"

    AppModule

    当编译器为App root 模块来生成刚才说的模块工厂的时候, 这个工厂会对所有模块中的Provider进行合并,然后生成一个factory只对最后的这个final module,也就是说这个factory 里边包含了所有的Provider ,

    你可以在Chrome 中打开F12 开发者工具中,选择Souces 标签,然后左边选择ng:// 打开AppModule里边的module.ngfactory.js.

    ngfactory.js

    你可以看见所有的Provider 和entry Component 都被合并了 ,并且传入了moduleDef 这个方法,所有不管你创建了多少个Module ,最后都是生成了一个Factory ,在这个Factory 里边包含了所有合并的Provider. 这个factory 用来创建带有自己injector的module实例. 因为我们最后得到了一个合并后的Module,Angular会创建一个根Injector来使用这些Provider.

    现在你可能疑惑 如果有两个Module 里边 有相同的provider token ,那会发生什么?

    总结就是两条规则. 第一个条 就是就近原则. 

    因为在A Module中定义过  providers:[{provide:'a',useValue:'a'}], 

    这时候如果 App Module 中也定义一个  providers: [{provide:'a', useValue:'root'}], 

    最后生成的是这样 :  也就是App 本身的a 覆盖了 A Module 中的a ,自己有了就不需要别人帮忙, 就近原则. 

    ngfactory.js

    第二条就是覆盖原则. 占山为王原则,原本有个土匪头子 ,后来又来了个土匪头子干掉了第一个, 那这个山头就是第二个土匪头子的.

    比如在Bmodule 中是这样写的 , 为了覆盖A Module 中的a provider

    providers:[{provide:'a',useValue:'b'}],

      entryComponents:[BComponent]

    在App 中引用的顺序是这样,

    @NgModule({

    imports: [AModule, BModule],

    ...

    })

    export class AppModule {} 

    也就是B Module 在Amodule 之后 , 那B 这个土匪头子就把之前的干掉 ,

    生成之后的ModuleDef 就是这样的

    你要是把顺序反过来 , A 干掉了B  就是这样.

    懒加载模块

    Angular 本身通过路由提供模块懒加载的功能,不熟悉的可以参照Angular懒加载

    Angular creates a lazy-loaded module with its own injector, a child of the root injector… So a lazy-loaded module that imports that shared module makes its own copy of the service.

    字面意思是:Angular对于懒加载的模块会给他们生成自己的Injector ,而懒加载中的这些provider 是没有被打平合并到之前factory 生成的final modules 里边. Angular 本身会给这些懒加载的模块创建一个 独立的Factory.  如果懒加载模块中的provider token (就是那个Service 名字) 跟 final modules 里边的一样, 那么Angular 会重新创建一个 provider 实例.

    换句话说就是在懒加载模块中 ,懒加载模块是被之前说的 那个 Angular 的编译器给一起打成那个final modules 一起,但是懒加载模块中的injector 用的是  从parentInjector 获得之后重新create 的.

    文中标红代码

    forRoot( ) 和 forChild() 方法

    首先说我们常用的 在RouterModule中的这两个方法:

    forRoot() : static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule>

    forChild(): static forChild(routes: Routes): ModuleWithProviders<RouterModule>

    表面看上去 这两个 区别就是在于forRoot() 方法有一个可选的参数,对其进行一些配置.两个方法都返回的是ModuleWithProviders,

    forRoot是用在根模块加载路由配置,而forChild是用在子模块加载路由配置.那么除了这些还有什么区别?

    官方解释是这样: 因为forRoot() creates a module that contains all the directives, the given routes, and the router service itself.

    forChild() creates a module that contains all the directives and the given routes, but does not include the router service.

    也就是forRoot() 里包含了router Service 在forChild()里边没有router service . why ? 我们知道懒加载的模块会重新生成一个injector ,如果我们在一个app 中有多个router Service ,他们就会共用 一个资源 Location. 我们因此不能创建多个router Service.

    对于其他的Module 除了刚才的例子里边的RouterModule.

    forRoot 来给App module 用. forchild()来给 懒加载模块来用.

    相关文章

      网友评论

          本文标题:Angular 模板解惑

          本文链接:https://www.haomeiwen.com/subject/mqebnftx.html