摘要:以下是關(guān)于中一些模塊的概要以及它們與這篇文章的關(guān)聯(lián)性這個(gè)模塊實(shí)現(xiàn)了我們在這篇文章中討論的關(guān)于的幾個(gè)回調(diào)函數(shù),同時(shí)它還會初始化一個(gè)策略類,這個(gè)類會作為連接和的橋梁。
現(xiàn)在,Angular Elements 這個(gè)項(xiàng)目已經(jīng)在社區(qū)引起一定程度的討論。這是顯而易見的,因?yàn)?Angular Elements 提供了很多開箱即用的、十分強(qiáng)大的功能:
通過使用原生的 HTML 語法來使用 Angular Elements —— 這意味著不再需要了解 Angular 的相關(guān)知識
它是自啟動的,并且一切都可以按預(yù)期那樣運(yùn)作
它符合 Web Components 規(guī)范,這意味著它可以在任何地方使用
雖然你沒有使用 Angular 開發(fā)整個(gè)網(wǎng)站,但你仍然可以從 Angular Framework 這個(gè)龐大的體系中收益
@angular/elements這個(gè)包提供可將 Angular 組件轉(zhuǎn)化為原生 Web Components 的功能,它基于瀏覽器的 Custom Elements API 實(shí)現(xiàn)。Angular Elements 提供一種更簡潔、對開發(fā)者更友善、更快樂地開發(fā)動態(tài)組件的方式 —— 在幕后它基于同樣的機(jī)制(指創(chuàng)建動態(tài)組件),但隱藏了許多樣板代碼。
關(guān)于如何通過 @angular/elements 創(chuàng)建一個(gè) Custom Element,已經(jīng)有大量的文章進(jìn)行闡述,所以在這篇文章將深入一點(diǎn),對它在 Angular 中的具體工作原理進(jìn)行剖析。這也是我們開始研究 Angular Elements 的一系列文章的原因,我們將在其中詳細(xì)解釋 Angular 如何在 Angular Elements 的幫助下實(shí)現(xiàn) Custom Elements API。
Custom Elements(自定義元素)要了解更多關(guān)于 Custom Elements 的知識,可以通過 developers.google 中的這篇文章進(jìn)行學(xué)習(xí),文章詳細(xì)介紹了與 Custom Elements API 相關(guān)的內(nèi)容。
這里針對 Custom Elements,我們使用一句話來概括:
使用 Custom Elements,web 開發(fā)者可以創(chuàng)建一個(gè)新的 HTML 標(biāo)簽、增加已有的 HTML 標(biāo)簽以及繼承其他開發(fā)者所開發(fā)的組件。原生 Custom Elements
讓我們來看看下面的例子,我們想要創(chuàng)建一個(gè)擁有 name 屬性的 app-hello HTML 標(biāo)簽。可以通過 Custom Elements API 來完成這件事。在文章的后續(xù)章節(jié),我們將演示如何使用 Angular 組件的 @Input 裝飾器與 這個(gè) name 屬性保持同步。但是現(xiàn)在,我們不需要使用 Angular Elements 或者 ShadowDom 或者使用任何關(guān)于 Angular 的東西來創(chuàng)建一個(gè) Custom Element,我們僅使用原生的 Custom Components API。
首先,這是我們的 HTML 標(biāo)記:
要實(shí)現(xiàn)一個(gè) Custom Element,我們需要分別實(shí)現(xiàn)如下在標(biāo)準(zhǔn)中定義的 hooks:
callback | summary |
---|---|
constructor | 如果需要的話,可在其中初始化 state 或者 shadowRoot,在這篇文章中,我們不需要 |
connectedCallback | 在元素被添加到 DOM 中時(shí)會被調(diào)用,我們將在這個(gè) hook 中初始化我們的 DOM 結(jié)構(gòu)和事件監(jiān)聽器 |
disconnectedCallback | 在元素從 DOM 中被移除時(shí)被調(diào)用,我們將在這個(gè) hook 中清除我們的 DOM 結(jié)構(gòu)和事件監(jiān)聽器 |
attributeChangedCallback | 在元素屬性變化時(shí)被調(diào)用,我們將在這個(gè) hook 中更新我們內(nèi)部的 dom 元素或者基于屬性改變后的狀態(tài) |
如下是我們關(guān)于 Hello Custom Element 的實(shí)現(xiàn)代碼:
class AppHello extends HTMLElement { constructor() { super(); } // 這里定義了那些需要被觀察的屬性,當(dāng)這些屬性改變時(shí),attributeChangedCallback 這個(gè) hook 會被觸發(fā) static get observedAttributes() {return ["name"]; } // getter to do a attribute -> property reflection get name() { return this.getAttribute("name"); } // setter to do a property -> attribute reflection // 通過 setter 來完成類屬性到元素屬性的映射操作 set name(val) { this.setAttribute("name", val); } connectedCallback() { this.div = document.createElement("div"); this.text = document.createTextNode(this.name || ""); this.div.appendChild(this.text); this.appendChild(this.div); } disconnectedCallback() { this.removeChild(this.div); } attributeChangedCallback(attrName, oldVal, newVal) { if (attrName === "name" && this.text) { this.text.textContent = newVal; } } } customElements.define("hello-elem", AppHello);
這里是可運(yùn)行實(shí)例的鏈接。這樣我們就實(shí)現(xiàn)了第一版的 Custom Element,回顧一下,這個(gè) app-hellp 標(biāo)簽包含一個(gè)文本節(jié)點(diǎn),并且這個(gè)節(jié)點(diǎn)將會渲染通過 app-hello 標(biāo)簽 name 屬性傳遞進(jìn)來的任何內(nèi)容,這一切僅僅基于原生 javascript。
將 Angular 組件導(dǎo)出為 Custom Element既然我們已經(jīng)了解了關(guān)于實(shí)現(xiàn)一個(gè) HTML Custom Element 所涉及的內(nèi)容,讓我們來使用 Angular實(shí)現(xiàn)一個(gè)相同功能的組件,之后再使它成為一個(gè)可用的 Custom Element。
首先,讓我們從一個(gè)簡單的 Angular 組件開始:
import { Component, Input } from "@angular/core"; @Component({ selector: "app-hello", template: `{{name}}` }) export class HelloComponent { @Input() name: string; }
正如你所見,它和上面的例子在功能上一模一樣。
現(xiàn)在,要將這個(gè)組件包裝為一個(gè) Custom Element,我們需要創(chuàng)建一個(gè) wrapper class 并實(shí)現(xiàn)所有 Custom Elements 中定義的 hooks:
class HelloComponentClass extends HTMLElement { constructor() { super(); } static get observedAttributes() { } connectedCallback() { } disconnectedCallback() { } attributeChangedCallback(attrName, oldVal, newVal) { } }
下一步,我們要做的是橋接 HelloComponent 和 HelloComponentClass。它們之間的橋會將 Angular Component 和 Custom Element 連接起來,如圖所示:
要完成這座橋,讓我們來依次實(shí)現(xiàn) Custom Elements API 中所要求的每個(gè)方法,并在這個(gè)方法中編寫關(guān)于綁定 Angular 的代碼:
callback | summary | angular part |
---|---|---|
constructor | 初始化內(nèi)部狀態(tài) | 進(jìn)行一些準(zhǔn)備工作 |
connectedCallback | 初始化視圖、事件監(jiān)聽器 | 加載 Angular 組件 |
disconnectedCallback | 清除視圖、事件監(jiān)聽器 | 注銷 Angular 組件 |
attributeChangedCallback | 處理屬性變化 | 處理 @Input 變化 |
我們需要在 connectedCallback() 方法中初始化 HelloComponent,但是在這之前,我們需要在 constructor 方法中進(jìn)行一些準(zhǔn)備工作。
順便,關(guān)于如何動態(tài)構(gòu)造 Angular 組件可以通過閱讀Dynamic Components in Angular這篇文章進(jìn)行了解。它其中闡述的運(yùn)作機(jī)制和我們這里使用的一模一樣。
所以,要讓我們的 Angular 動態(tài)組件能夠正常工作(需要 componentFactory 能夠被編譯),我們需要將 HelloComponent 添加到 NgModule 的 entryComponents 屬性(它是一個(gè)列表)中去:
@NgModule({ imports: [ BrowserModule ], declarations: [HelloComponent], entryComponents: [HelloComponent] }) export class CustomElementsModule { ngDoBootstrap() {} }
基本上,調(diào)用 prepare() 方法會完成兩件事:
它會基于組件的定義初始化一個(gè) factoryComponent 工廠方法
它會基于 Angular 組件的 inputs 初始化 observedAttributes,以便我們在 attributeChangedCallback() 中完成我們需要做的事
class AngularCustomElementBridge { prepare(injector, component) { this.componentFactory = injector.get(ComponentFactoryResolver).resolveComponentFactory(component); // 我們使用 templateName 來處理 @Input("aliasName") 這種情形 this.observedAttributes = componentFactory.inputs.map(input => input.templateName); } }2. connectedCallback()
在這個(gè)回調(diào)函數(shù)中,我們將看到:
初始化我們的 Angular 組件(就如創(chuàng)建動態(tài)組件那樣)
設(shè)置組件的初始 input 值
在渲染組件時(shí),觸發(fā)臟檢查機(jī)制
最后,將 HostView 增加到 ApplicationRef
如下是實(shí)戰(zhàn)代碼:
class AngularCustomElementBridge { initComponent(element: HTMLElement) { // 首先我們需要 componentInjector 來初始化組件 // 這里的 injector 是 Custom Element 外部的注入器實(shí)例,調(diào)用者可以在這個(gè)實(shí)例中注冊 // 他們自己的 providers const componentInjector = Injector.create([], this.injector); this.componentRef = this.componentFactory.create(componentInjector, null, element); // 然后我們要檢查是否需要初始化組件的 input 的值 // 在本例中,在 Angular Element 被加載之前,user 可能已經(jīng)設(shè)置了元素的屬性 // 這些值被保存在 initialInputValues 這個(gè) map 結(jié)構(gòu)中 this.componentFactory.inputs.forEach(prop => this.componentRef.instance[prop.propName] = this.initialInputValues[prop.propName]); // 之后我們會觸發(fā)臟檢查,這樣組件在事件循環(huán)的下一個(gè)周期會被渲染 this.changeDetectorRef.detectChanges(); this.applicationRef = this.injector.get(ApplicationRef); // 最后,我們使用 attachView 方法將組件的 HostView 添加到 applicationRef 中 this.applicationRef.attachView(this.componentRef.hostView); } }3. disconnectedCallback()
這個(gè)十分容易,我們僅需要在其中注銷 componentRef 即可:
class AngularCustomElementBridge { destroy() { this.componentRef.destroy(); } }4. attributeChangedCallback()
當(dāng)元素屬性發(fā)生改變時(shí),我們需要相應(yīng)地更新 Angular 組件并觸發(fā)臟檢查:
class AngularCustomElementBridge { setInputValue(propName, value) { if (!this.componentRef) { this.initialInputValues[propName] = value; return; } if (this.componentRef[propName] === value) { return; } this.componentRef[propName] = value; this.changeDetectorRef.detectChanges(); } }5. Finally, we register the Custom Element
customElements.define("hello-elem", HelloComponentClass);
這是一個(gè)可運(yùn)行的例子鏈接。
總結(jié)這就是根本思想。通過在 Angular 中使用動態(tài)組件,我們簡單實(shí)現(xiàn)了 Angular Elements 所提供的基礎(chǔ)功能,重要的是,沒有使用 @angular/element 這個(gè)庫。
當(dāng)然,不要誤解 —— Angular Elements 的功能十分強(qiáng)大。文章中所涉及的所有實(shí)現(xiàn)邏輯在 Angular Elements 都已被抽象化,使用這個(gè)庫可以使我們的代碼更優(yōu)雅,可讀性和維護(hù)性也更好,同時(shí)也更易于擴(kuò)展。
以下是關(guān)于 Angular Elements 中一些模塊的概要以及它們與這篇文章的關(guān)聯(lián)性:
create-custom-element.ts:這個(gè)模塊實(shí)現(xiàn)了我們在這篇文章中討論的關(guān)于 Custom Element 的幾個(gè)回調(diào)函數(shù),同時(shí)它還會初始化一個(gè) NgElementStrategy 策略類,這個(gè)類會作為連接 Angular Component 和 Custom Elements 的橋梁。當(dāng)前,我們僅有一個(gè)策略 —— component-factory-strategy.ts —— 它的運(yùn)作機(jī)制與本文例子中演示的大同小異。在將來,我們可能會有其他策略,并且我們還可以實(shí)現(xiàn)自定義策略。
component-factory-strategy.ts:這個(gè)模塊使用一個(gè) component 工廠函數(shù)來創(chuàng)建和銷毀組件引用。同時(shí)它還會在 input 改變時(shí)觸發(fā)臟檢查。這個(gè)運(yùn)作過程在上文的例子中也有被提及。
下次我們將闡述 Angular Elements 通過 Custom Events 輸出事件。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96167.html
摘要:也放出地址,上面有完整工程以及在線演示地址相關(guān)閱讀教學(xué)向行代碼教你實(shí)現(xiàn)一個(gè)低配版的庫原理篇教學(xué)向行代碼教你實(shí)現(xiàn)一個(gè)低配版的庫代碼篇教學(xué)向再加行代碼教你實(shí)現(xiàn)一個(gè)低配版的庫設(shè)計(jì)篇教學(xué)向再加行代碼教你實(shí)現(xiàn)一個(gè)低配版的庫原理篇 書接上一篇: 150行代碼教你實(shí)現(xiàn)一個(gè)低配版的MVVM庫(1)- 原理篇 寫在前面 為了便于分模塊,和閱讀,我使用了Typescript來進(jìn)行coding,總行數(shù)是正好...
摘要:但是,有一件事是肯定的年對全棧開發(fā)者的需求量很大。有一些方法可以解決這個(gè)問題,例如模式,或者你可以這么想,其實(shí)谷歌機(jī)器人在抓取單頁應(yīng)用程序時(shí)沒有那么糟糕。谷歌正在這方面努力推進(jìn),但不要指望在年會看到任何突破。 對于什么是全棧開發(fā)者并沒有一個(gè)明確的定義。但是,有一件事是肯定的:2019 年對全棧開發(fā)者的需求量很大。在本文中,我將向你概述一些趨勢,你可以嘗試根據(jù)這些趨勢來確定你可能要投入的...
摘要:但是,有一件事是肯定的年對全棧開發(fā)者的需求量很大。有一些方法可以解決這個(gè)問題,例如模式,或者你可以這么想,其實(shí)谷歌機(jī)器人在抓取單頁應(yīng)用程序時(shí)沒有那么糟糕。谷歌正在這方面努力推進(jìn),但不要指望在年會看到任何突破。 對于什么是全棧開發(fā)者并沒有一個(gè)明確的定義。但是,有一件事是肯定的:2019 年對全棧開發(fā)者的需求量很大。在本文中,我將向你概述一些趨勢,你可以嘗試根據(jù)這些趨勢來確定你可能要投入的...
摘要:但是,有一件事是肯定的年對全棧開發(fā)者的需求量很大。有一些方法可以解決這個(gè)問題,例如模式,或者你可以這么想,其實(shí)谷歌機(jī)器人在抓取單頁應(yīng)用程序時(shí)沒有那么糟糕。谷歌正在這方面努力推進(jìn),但不要指望在年會看到任何突破。 對于什么是全棧開發(fā)者并沒有一個(gè)明確的定義。但是,有一件事是肯定的:2019 年對全棧開發(fā)者的需求量很大。在本文中,我將向你概述一些趨勢,你可以嘗試根據(jù)這些趨勢來確定你可能要投入的...
摘要:概述的解釋器優(yōu)化器代碼可能在字節(jié)碼或者優(yōu)化后的機(jī)器碼狀態(tài)下執(zhí)行,而生成字節(jié)碼速度很快,而生成機(jī)器碼就要慢一些了。比如有一個(gè)函數(shù),從獲取值引擎生成的字節(jié)碼結(jié)構(gòu)是這樣的指令是獲取參數(shù)指向的對象,并存儲在,第二步則返回。 1 引言 本期精讀的文章是:JS 引擎基礎(chǔ)之 Shapes and Inline Caches 一起了解下 JS 引擎是如何運(yùn)作的吧! JS 的運(yùn)作機(jī)制可以分為 AST 分...
閱讀 3759·2021-11-25 09:43
閱讀 2191·2021-11-23 10:13
閱讀 823·2021-11-16 11:44
閱讀 2369·2019-08-29 17:24
閱讀 1384·2019-08-29 17:17
閱讀 3480·2019-08-29 11:30
閱讀 2584·2019-08-26 13:23
閱讀 2345·2019-08-26 12:10