成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[譯] 關(guān)于 Angular 動(dòng)態(tài)組件你需要知道的

lcodecorex / 581人閱讀

摘要:第一種方式是使用模塊加載器,如果你使用加載器的話,路由在加載子路由模塊時(shí)也是用的作為模塊加載器。還需注意的是,想要使用還需像這樣去注冊它你當(dāng)然可以在里使用任何標(biāo)識,不過路由模塊使用標(biāo)識,所以最好也使用相同。

原文鏈接:Here is what you need to know about dynamic components in?Angular

本文主要解釋如何在 Angular 中動(dòng)態(tài)創(chuàng)建組件(注:在模板中使用的組件可稱為靜態(tài)地創(chuàng)建組件)。

如果你之前使用 AngularJS(第一代 Angular 框架)來編程,可能會(huì)使用 $compile 服務(wù)生成 HTML,并連接到數(shù)據(jù)模型從而獲得雙向綁定功能:

const template = "generated on the fly: {{name}}"
const linkFn = $compile(template);
const dataModel = $scope.$new();
dataModel.name = "dynamic";

// link data model to a template
linkFn(dataModel);

AngularJS 中指令可以修改 DOM,但是沒法知道修改了什么。這種方式的問題和動(dòng)態(tài)環(huán)境一樣,很難優(yōu)化性能。動(dòng)態(tài)模板當(dāng)然不是 AngularJS 性能慢的主要元兇,但也是重要原因之一。

我在看了 Angular 內(nèi)部代碼一段時(shí)間后,發(fā)現(xiàn)這個(gè)新設(shè)計(jì)的框架非常重視性能,在 Angular 源碼里你會(huì)經(jīng)常發(fā)現(xiàn)這幾句話(注:為清晰理解,不翻譯):

Attention: Adding fields to this is performance sensitive!

Note: We use one type for all nodes so that loops that loop over all nodes of a ViewDefinition stay monomorphic!

For performance reasons, we want to check and update the list every five seconds.

所以,Angular 設(shè)計(jì)者決定犧牲靈活性來獲得巨大的性能提升,如引入了 JIT 和 AOT Compiler,靜態(tài)模板(static templates),指令/模塊工廠(ComponentFactory),工廠解析器(ComponentFactoryResolver)。對 AngularJS 社區(qū)來說,這些概念很陌生,甚至充滿敵意,不過不用擔(dān)心,如果你之前僅僅是聽說過這些概念,但現(xiàn)在想知道這些是什么,繼續(xù)閱讀本文,將讓你茅塞頓開。

注:實(shí)際上,JIT/AOT Compiler 說的是同一個(gè) Compiler,只是這個(gè) Compiler 在 building time 階段還是在 running time 階段被使用而已。

至于 factory,是 Angular Compiler 把你寫的組件如 a.component.ts 編譯為 a.component.ngfactory.js,即 Compiler 使用 @Component decorator 作為原材料,把你寫的組件/指令類編譯為另一個(gè)視圖工廠類。

回到剛剛的 JIT/AOT Compiler,如果 a.component.ngfactory.js 是在 build 階段生成的那就是 AOT Compiler,這個(gè) Compiler 不會(huì)被打包到依賴包里;如果是在 run 階段生成,那 Compiler 就需要被打包到依賴包里,被用戶下載到本地,在運(yùn)行時(shí) Compiler 會(huì)編譯組件/指令類生成對應(yīng)的視圖工廠類,僅此而已。下文將會(huì)看下這些 *.ngfactory.js 文件代碼是什么樣的。

至于 factory resolver,那就更簡單了,就是一個(gè)對象,通過它拿到那些編譯后的 factory 對象。

組件工廠和編譯器

Angular 中每一個(gè)組件是由組件工廠創(chuàng)建的,組件工廠又是由編譯器根據(jù)你寫的 @Component 裝飾器里的元數(shù)據(jù)編譯生成的。如果你在網(wǎng)上讀了大量的 decorator 文章還有點(diǎn)迷惑,可以參考我寫的這篇 Medium 文章 Implementing custom component decorator 。

Angular 內(nèi)部使用了 視圖 概念,或者說整個(gè)框架是一顆視圖樹。每一個(gè)視圖是由大量不同類型節(jié)點(diǎn)(node)組成的:元素節(jié)點(diǎn),文本節(jié)點(diǎn)等等(注:可查看 譯 Angular DOM 更新機(jī)制)。每一個(gè)節(jié)點(diǎn)都有其專門作用,這樣每一個(gè)節(jié)點(diǎn)的處理只需要花很少的時(shí)間,并且每一個(gè)節(jié)點(diǎn)都有 ViewContainerRefTemplateRef 等服務(wù)供使用,還可以使用 ViewChild/ViewChildrenContentChild/ContentChildren 做 DOM 查詢這些節(jié)點(diǎn)。

注:簡單點(diǎn)說就是 Angular 程序是一顆視圖樹,每一個(gè)視圖(view)又是有多種節(jié)點(diǎn)(node)組成的,每一個(gè)節(jié)點(diǎn)又提供了模板操作 API 給開發(fā)者使用,這些節(jié)點(diǎn)可以通過 DOM Query API 拿到。

每一個(gè)節(jié)點(diǎn)包含大量信息,并且為了性能考慮,一旦節(jié)點(diǎn)被創(chuàng)建就生效,后面不容許更改(注:被創(chuàng)建的節(jié)點(diǎn)會(huì)被緩存起來)。節(jié)點(diǎn)生成過程是編譯器搜集你寫的組件信息(注:主要是你寫的組件里的模板信息),并以組件工廠形式封裝起來。

假設(shè)你寫了如下的一個(gè)組件:

@Component({
  selector: "a-comp",
  template: "A Component"
})
class AComponent {}

編譯器根據(jù)你寫的信息生成類似如下的組件工廠代碼,代碼只包含重要部分(注:下面整個(gè)代碼可理解為視圖,其中 elementDef2jit_textDef3 可理解為節(jié)點(diǎn)):

function View_AComponent_0(l) {
  return jit_viewDef1(0,[
      elementDef2(0,null,null,1,"span",...),
      jit_textDef3(null,["My name is ",...])
    ]

上面代碼基本描述了組件視圖的結(jié)構(gòu),并被用來實(shí)例化一個(gè)組件。其中,第一個(gè)節(jié)點(diǎn) elementDef2 就是元素節(jié)點(diǎn)定義,第二個(gè)節(jié)點(diǎn) jit_textDef3 就是文本節(jié)點(diǎn)定義。你可以看到每一個(gè)節(jié)點(diǎn)都有足夠的參數(shù)信息來實(shí)例化,而這些參數(shù)信息是編譯器解析所有依賴生成的,并且在運(yùn)行時(shí)由框架提供這些依賴的具體值。

從上文知道,如果你能夠訪問到組件工廠,就可以使用它實(shí)例化出對應(yīng)的組件對象,并使用 ViewContainerRef API 把該組件/視圖插入 DOM 中。如果你對 ViewContainerRef 感興趣,可以查看 譯 探索 Angular 使用 ViewContainerRef 操作 DOM。應(yīng)該如何使用這個(gè) API 呢(注:下面代碼展示如何使用 ViewContainerRef API 往視圖樹上插入一個(gè)視圖):

export class SampleComponent implements AfterViewInit {
    @ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;

    ngAfterViewInit() {
        this.vc.createComponent(componentFactory);
    }
}

好的,從上面代碼可知道只要拿到組件工廠,一切問題就解決了。現(xiàn)在,問題是如何拿到 ComponentFactory 組件工廠對象,繼續(xù)看。

模塊(Modules)和組件工廠解析器(ComponentFactoryResolver)

盡管 AngularJS 也有模塊,但它缺少指令所需要的真正的命名空間,并且會(huì)有潛在的命名沖突,還沒法在多帶帶的模塊里封裝指令。然而,很幸運(yùn),Angular 吸取了教訓(xùn),為各種聲明式類型,如指令、組件和管道,提供了合適的命名空間(注:即 Angular 提供的 Module,使用裝飾器函數(shù) @NgModule 裝飾一個(gè)類就能得到一個(gè) Module)。

就像 AngularJS 那樣,Angular 中的組件是被封裝在模塊中。組件自己并不能獨(dú)立存在,如果你想要使用另一個(gè)模塊的一個(gè)組件,你必須導(dǎo)入這個(gè)模塊:

@NgModule({
    // imports CommonModule with declared directives like
    // ngIf, ngFor, ngClass etc.
    imports: [CommonModule],
    ...
})
export class SomeModule {}

同樣道理,如果一個(gè)模塊想要提供一些組件給別的模塊使用,就必須導(dǎo)出這些組件,可以查看 exports 屬性。比如,可以查看 CommonModule 源碼的做法(注:查看 L24-L25):

const COMMON_DIRECTIVES: Provider[] = [
    NgClass,
    NgComponentOutlet,
    NgForOf,
    NgIf,
    ...
];

@NgModule({
    declarations: [COMMON_DIRECTIVES, ...],
    exports: [COMMON_DIRECTIVES, ...],
    ...
})
export class CommonModule {
}

所以每一個(gè)組件都是綁定在一個(gè)模塊里,并且不能在不同模塊里申明同一個(gè)組件,如果你這么做了,Angular 會(huì)拋出錯(cuò)誤:

Type X is part of the declarations of 2 modules: ...

當(dāng) Angular 編譯程序時(shí),編譯器會(huì)把在模塊中 entryComponents 屬性注冊的組件,或模板里使用的組件編譯為組件工廠(注:在所有靜態(tài)模板中使用的組件如 ,即靜態(tài)組件;在 entryComponents 定義的組件,即動(dòng)態(tài)組件,動(dòng)態(tài)組件的一個(gè)最佳示例如 Angular Material Dialog 組件,可以在 entryComponents 中注冊 DialogContentComp 組件動(dòng)態(tài)加載對話框內(nèi)容)。你可以在 Sources 標(biāo)簽里看到編譯后的組件工廠文件:

從上文中我們知道,如果我們能拿到組件工廠,就可以使用組件工廠創(chuàng)建對應(yīng)的組件對象,并插入到視圖里。實(shí)際上,每一個(gè)模塊都為所有組件提供了一個(gè)獲取組件工廠的服務(wù) ComponentFactoryResolver。所以,如果你在模塊中定義了一個(gè) BComponent 組件并想要拿到它的組件工廠,你可以在這個(gè)組件內(nèi)注入這個(gè)服務(wù)并使用它:

export class AppComponent {
  constructor(private resolver: ComponentFactoryResolver) {
    // now the `factory` contains a reference to the BComponent factory
    const factory = this.resolver.resolveComponentFactory(BComponent);
  }

這是在兩個(gè)組件 AppComponentBComponent 都定義在一個(gè)模塊里才行,或者導(dǎo)入其他模塊時(shí)該模塊已經(jīng)有組件 BComponent 對應(yīng)的組件工廠。

動(dòng)態(tài)加載和編譯模塊

但是如果組件在其他模塊定義,并且這個(gè)模塊是按需加載,這樣的話是不是完蛋了呢?實(shí)際上我們照樣可以拿到某個(gè)組件的組件工廠,方法同路由使用 loadChildren 配置項(xiàng)按需加載模塊很類似。

有兩種方式可以在運(yùn)行時(shí)加載模塊。第一種方式 是使用 SystemJsNgModuleLoader 模塊加載器,如果你使用 SystemJS 加載器的話,路由在加載子路由模塊時(shí)也是用的 SystemJsNgModuleLoader 作為模塊加載器。SystemJsNgModuleLoader 模塊加載器有一個(gè) load 方法來把模塊加載到瀏覽器里,同時(shí)編譯該模塊和在該模塊中申明的所有組件。load 方法需要傳入文件路徑參數(shù),并加上導(dǎo)出模塊的名稱,返回值是 NgModuleFactory

loader.load("path/to/file#exportName")
注:NgModuleFactory 源碼是在 packages/core/linker 文件夾內(nèi),該文件夾里的代碼主要是粘合劑代碼,主要都是一些接口類供 Core 模塊使用,具體實(shí)現(xiàn)在其他文件夾內(nèi)。

如果沒有指定具體的導(dǎo)出模塊名稱,加載器會(huì)使用默認(rèn)關(guān)鍵字 default 導(dǎo)出的模塊名。還需注意的是,想要使用 SystemJsNgModuleLoader 還需像這樣去注冊它:

providers: [
    {
      provide: NgModuleFactoryLoader,
      useClass: SystemJsNgModuleLoader
    }
  ]

你當(dāng)然可以在 provide 里使用任何標(biāo)識(token),不過路由模塊使用 NgModuleFactoryLoader 標(biāo)識,所以最好也使用相同 token。(注:NgModuleFactoryLoader 注冊可查看源碼 L68,使用可查看 L78

模塊加載并獲取組件工廠的完整代碼如下:

@Component({
  providers: [
    {
      provide: NgModuleFactoryLoader,
      useClass: SystemJsNgModuleLoader
    }
  ]
})
export class ModuleLoaderComponent {
  constructor(private _injector: Injector,
              private loader: NgModuleFactoryLoader) {
  }

  ngAfterViewInit() {
    this.loader.load("app/t.module#TModule").then((factory) => {
      const module = factory.create(this._injector);
      const r = module.componentFactoryResolver;
      const cmpFactory = r.resolveComponentFactory(AComponent);
      
      // create a component and attach it to the view
      const componentRef = cmpFactory.create(this._injector);
      this.container.insert(componentRef.hostView);
    })
  }
}

但是在使用 SystemJsNgModuleLoader 時(shí)還有個(gè)問題,上面代碼的 load() 函數(shù)內(nèi)部(注:參見 L70)其實(shí)是使用了編譯器的 compileModuleAsync 方法,該方法只會(huì)為在 entryComponents 中注冊的或者在組件模板中使用的組件,去創(chuàng)建組件工廠。但是如果你就是不想要把組件注冊在 entryComponents 屬性里,是不是就完蛋了呢?仍然有解決方案 —— 使用 compileModuleAndAllComponentsAsync 方法自己去加載模塊。該方法會(huì)為模塊里所有組件生成組件工廠,并返回 ModuleWithComponentFactories 對象:

class ModuleWithComponentFactories {
    componentFactories: ComponentFactory[];
    ngModuleFactory: NgModuleFactory;

下面代碼完整展示如何使用該方法加載模塊并獲取所有組件的組件工廠(注:這是上面說的 第二種方式):

ngAfterViewInit() {
  System.import("app/t.module").then((module) => {
      _compiler.compileModuleAndAllComponentsAsync(module.TModule)
        .then((compiled) => {
          const m = compiled.ngModuleFactory.create(this._injector);
          const factory = compiled.componentFactories[0];
          const cmp = factory.create(this._injector, [], null, m);
        })
    })
}

然而,記住,這個(gè)方法使用了編譯器的私有 API,下面是源碼中的 文檔說明

One intentional omission from this list is?@angular/compiler, which is currently considered a low level api and is subject to internal changes. These changes will not affect any applications or libraries using the higher-level apis (the command line interface or JIT compilation via?@angular/platform-browser-dynamic). Only very specific use-cases require direct access to the compiler API (mostly tooling integration for IDEs, linters, etc). If you are working on this kind of integration, please reach out to us first.
運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建組件

從上文中我們知道如何通過模塊中的組件工廠來動(dòng)態(tài)創(chuàng)建組件,其中模塊是在運(yùn)行時(shí)之前定義的,并且模塊是可以提前或延遲加載的。但是,也可以不需要提前定義模塊,可以像 AngularJS 的方式在運(yùn)行時(shí)創(chuàng)建模塊和組件。

首先看看上文中的 AngularJS 的代碼是如何做的:

const template = "generated on the fly: {{name}}"
const linkFn = $compile(template);
const dataModel = $scope.$new();
dataModel.name = "dynamic"

// link data model to a template
linkFn(dataModel);

從上面代碼可以總結(jié)動(dòng)態(tài)創(chuàng)建視圖的一般流程如下:

定義組件類及其屬性,并使用裝飾器裝飾組件類

定義模塊類,在模塊類中申明組件類,并使用裝飾器裝飾模塊類

編譯模塊和模塊中所有組件,拿到所有組件工廠

模塊類也僅僅是帶有模塊裝飾器的普通類,組件類也同樣如此,而由于裝飾器也僅僅是簡單地函數(shù)而已,在運(yùn)行時(shí)可用,所以只要我們需要,就可以使用這些裝飾器如 @NgModule()/@Component() 去裝飾任何類。下面代碼完整展示如何動(dòng)態(tài)創(chuàng)建組件:

@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;

constructor(private _compiler: Compiler,
            private _injector: Injector,
            private _m: NgModuleRef) {
}

ngAfterViewInit() {
  const template = "generated on the fly: {{name}}";

  const tmpCmp = Component({template: template})(class {
  });
  const tmpModule = NgModule({declarations: [tmpCmp]})(class {
  });

  this._compiler.compileModuleAndAllComponentsAsync(tmpModule)
    .then((factories) => {
      const f = factories.componentFactories[0];
      const cmpRef = this.vc.createComponent(tmpCmp);
      cmpRef.instance.name = "dynamic";
    })
}

為了更好的調(diào)試信息,你可以使用任何類來替換上面代碼中的匿名類。

Ahead-of-Time Compilation

上文中說到的編譯器說的是 Just-In-Time(JIT) 編譯器,你可能聽說過 Ahead-of-Time(AOT) 編譯器,實(shí)際上 Angular 只有一個(gè)編譯器,它們僅僅是根據(jù)編譯器使用在不同階段,而采用的不同叫法。如果編譯器是被下載到瀏覽器里,在運(yùn)行時(shí)使用就叫 JIT 編譯器;如果是在編譯階段去使用,而不需要下載到瀏覽器里,在編譯時(shí)使用就叫 AOT 編譯器。使用 AOT 方法是被 Angular 官方推薦的,并且官方文檔上有詳細(xì)的 原因解釋 —— 渲染速度更快并且代碼包更小。

如果你使用 AOT 的話,意味著運(yùn)行時(shí)不存在編譯器,那上面的不需要編譯的示例仍然有效,仍然可以使用 ComponentFactoryResolver 來做,但是動(dòng)態(tài)編譯需要編譯器,就沒法運(yùn)行了。但是,如果非得要使用動(dòng)態(tài)編譯,那就得把編譯器作為開發(fā)依賴一起打包,然后代碼被下載到瀏覽器里,這樣做需要點(diǎn)安裝步驟,不過也沒啥特別的,看看代碼:

import { JitCompilerFactory } from "@angular/compiler";

export function createJitCompiler() {
  return new JitCompilerFactory([{
    useDebug: false,
    useJit: true
  }]).createCompiler();
}

import { AppComponent }  from "./app.component";

@NgModule({
  providers: [{provide: Compiler, useFactory: createJitCompiler}],
  ...
})
export class AppModule {
}

上面代碼中,我們使用 @angular/compilerJitCompilerFactory 類來實(shí)例化出一個(gè)編譯器工廠,然后通過標(biāo)識 Compiler 來注冊編譯器工廠實(shí)例。以上就是所需要修改的全部代碼,就這么點(diǎn)東西需要修改添加,很簡單不是么。

組件銷毀

如果你使用動(dòng)態(tài)加載組件方式,最后需要注意的是,當(dāng)父組件銷毀時(shí),該動(dòng)態(tài)加載組件需要被銷毀:

ngOnDestroy() {
  if(this.cmpRef) {
    this.cmpRef.destroy();
  }
}

上面代碼將會(huì)從視圖容器里移除該動(dòng)態(tài)加載組件視圖并銷毀它。

ngOnChanges

對于所有動(dòng)態(tài)加載的組件,Angular 會(huì)像對靜態(tài)加載組件一樣也執(zhí)行變更檢測,這意味著 ngDoCheck 也同樣會(huì)被調(diào)用(注:可查看 Medium 這篇文章 If you think ngDoCheck means your component is being checked?—?read this?article)。然而,就算動(dòng)態(tài)加載組件申明了 @Input 輸入綁定,但是如果父組件輸入綁定屬性發(fā)生改變,該動(dòng)態(tài)加載組件的 ngOnChanges 不會(huì)被觸發(fā)。這是因?yàn)檫@個(gè)檢查輸入變化的 ngOnChanges 函數(shù),只是在編譯階段由編譯器編譯后重新生成,該函數(shù)是組件工廠的一部分,編譯時(shí)是根據(jù)模板信息編譯生成的。因?yàn)閯?dòng)態(tài)加載組件沒有在模板中被使用,所以該函數(shù)不會(huì)由編譯器編譯生成。

Github

本文的所有示例代碼存放在 Github。

注:本文主要講了組件 b-comp 如何動(dòng)態(tài)加載組件 a-comp,如果兩個(gè)在同一個(gè) module,直接調(diào)用 ComponentFactoryResolver 等 API 就行;如果不在同一個(gè) module,就使用 SystemJsNgModuleLoader 模塊加載器就行。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/107750.html

相關(guān)文章

  • [] 關(guān)于 Angular 依賴注入需要知道

    摘要:注入器樹大多數(shù)開發(fā)者知道,會(huì)創(chuàng)建根注入器,根注入器內(nèi)的服務(wù)都是單例的。也會(huì)被添加到這個(gè)根注入器對象內(nèi),它主要用來創(chuàng)建動(dòng)態(tài)組件,因?yàn)樗鎯?chǔ)了屬性指向的組件數(shù)組。為了理解依賴解析算法,我們首先需要知道視圖和父視圖元素概念。 What you always wanted to know about Angular Dependency Injection tree showImg(https...

    Edison 評論0 收藏0
  • [] 關(guān)于 `ExpressionChangedAfterItHasBeenCheckedErro

    摘要:本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。這一次過程稱為。這個(gè)程序設(shè)計(jì)為子組件拋出一個(gè)事件,而父組件監(jiān)聽這個(gè)事件,而這個(gè)事件會(huì)引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...

    andong777 評論0 收藏0
  • [] 別再對 Angular Modules 感到迷惑

    摘要:大多數(shù)初學(xué)者會(huì)認(rèn)為也有封裝規(guī)則,但實(shí)際上沒有。第二個(gè)規(guī)則是最后導(dǎo)入模塊的,會(huì)覆蓋前面導(dǎo)入模塊的。 原文鏈接:Avoiding common confusions with modules in Angular showImg(https://segmentfault.com/img/remote/1460000015298243?w=270&h=360); Angular Modul...

    LMou 評論0 收藏0
  • [] 探索 Angular 使用 ViewContainerRef 操作 DOM

    摘要:在探索抽象類前,先了解下如何在組件指令中獲取這些抽象類。下面示例描述在組建模板中如何創(chuàng)建如同其他抽象類一樣,通過屬性綁定元素,比如上例中,綁定的是會(huì)被渲染為注釋的元素,所以輸出也將是。你可以使用查詢模板引用變量來獲得抽象類。 原文鏈接:Exploring Angular DOM manipulation techniques using ViewContainerRef如果想深入學(xué)習(xí) ...

    wind5o 評論0 收藏0
  • [] 為何 Angular 內(nèi)部沒有發(fā)現(xiàn)組件

    摘要:本質(zhì)上,本文主要解釋內(nèi)部是如何定義組件和指令的,并引入新的視圖節(jié)點(diǎn)定義指令定義。大多數(shù)指令使用屬性選擇器,但是有一些也選擇元素選擇器。實(shí)際上,表單指令就是使用元素選擇器來把特定行為附著在元素上。但是由于編譯器會(huì)為每一個(gè) 原文鏈接:Here is why you will not find components inside Angular showImg(https://segmen...

    LiveVideoStack 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<