Angular 模板解惑

翻译来自国外大神

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

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()来给 懒加载模块来用.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345