使用Angular 10 建立Web Component全解析
摘要:使用原生js,html,css封裝web component比較麻煩,在angular 10.0中,提供了一種使用angular elements把angular component轉換成web component的能力。
這是一篇介紹如何使用angular建立web component的文章。
你將會看到:
-
打包專案
-
優化打包過程
-
在原本的angular專案中使用
-
在其他angular專案中使用
-
在普通的html中使用
-
在react專案中使用
- 使用angular來封裝web component的一些問題
-
如何使用web component實現微前端
版本:
demo使用的是新版 angular 10.04版本。
一.工作原理:
angular提供了createCustomElement()函式,它把angular component的各種功能轉換成web component標準的各種介面。
然後使用原生的customElements.define()函式把自定義的元素註冊到瀏覽器的CustomElementRegistry中。
這樣,當頁面上出現這個自定義的元素時,瀏覽器會把CustomElementRegistry中的相對應的元素例項化,例項化出的物件其實是使用angular component的語法,包括資料繫結和變更檢測。
二. 具體步驟:
1.新建angular工程:ng new custom-elements-demo
執行:ng new custom-elements-demo
等待安裝包完成:
看一下現在的目錄結構:
專案初始化已經完成了。
此處正常情況應該有嘆息的,畢竟安裝包需要挺長長長長時間的的的的~~~
2.刪除不必要的app.component
在原來的angular專案中,app.component是作為入口元件的,現在我們只是為了生成web component,就沒有必要有入口元件了。刪除與app.component相關的內容。
-
刪除src/app/app.component.html
-
刪除src/app/app.component.scss
-
刪除src/app/app.component.ts
-
刪除src/app/app.component.spec.ts
-
更新src/app/app.module.ts
目錄結構:
app.module.ts內容
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; @NgModule({ declarations: [], imports: [ BrowserModule ], providers: [], bootstrap: [] }) export class AppModule { }
3.安裝依賴包:ng add @angular/elements
@angular/elements是angular自定義元素的工具包,我們來安裝一下。
執行:ng add @angular/elements
可以看到,當你安裝@angular/elements,會預設安裝document-register-element膩子指令碼,並且新增到src/polyfills.ts中。
那麼為什麼需要新增膩子指令碼呢?原因是:目前不是所有瀏覽器都支援自定義元素的,要想讓不支援自定義元素的瀏覽器正常使用,需要新增膩子指令碼。
我們來對比一下程式碼:
會安裝2個依賴:@angular/elements和document-register-element
修改了src/polyfills.ts這個檔案,新增膩子指令碼到專案中。
4.新建angular component: ng g c custom-card
新建一個空的angular component
執行: ng g c custom-card
完成custom-card元件,這裡就直接貼一下程式碼了。
custom-card.component.html
1 <span>{{name}}</span> 2 <div class="info"> 3 <span>{{info.age}}</span> 4 <span>{{info.phone}}</span> 5 </div> 6 <button class="select" (click)="onSelectUser()">選擇使用者</button>
custom-card.component.scss
1 :host { 2 display: flex; 3 flex-direction: column; 4 width: 200px; 5 height: 200px; 6 border: 1px solid gray; 7 8 padding: 20px; 9 .info { 10 padding: 20px; 11 span { 12 display: block; 13 } 14 } 15 }
custom-card.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { UtilService } from '../util.service'; @Component({ selector: 'app-custom-card', templateUrl: './custom-card.component.html', styleUrls: ['./custom-card.component.scss'] }) export class CustomCardComponent implements OnInit { @Input() name = ''; @Input() info = { age: 0, phone: '' }; @Output() selectUser = new EventEmitter(); constructor( private util: UtilService ) { } ngOnInit(): void { this.util.log(); } onSelectUser(): void { this.selectUser.emit({...this.info, name: this.name}); } }
這裡card顯示user的name age phone,其中name是單獨作為字串傳遞進card 元件的,而age 和 phone是包裝在info物件中的,只是為了演示傳遞值的區別。
當用戶點選選擇user的button時,會把當前的user資訊傳送出來,這是為了演示怎麼響應web component的事件。
5.新增到entryComponent
有一類元件被包含在模板中,它們是宣告式載入的;另一類元件你會命令式載入它,這就是入口元件。對於入口元件,需要新增到module.entryComponent中。
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { CustomCardComponent } from './custom-card/custom-card.component'; @NgModule({ declarations: [CustomCardComponent], imports: [ BrowserModule ], providers: [], bootstrap: [], entryComponents: [CustomCardComponent] }) export class AppModule { }
6.使用createCustomElement()函式建立自定義元素
@angular/elements包匯出的createCustomElement()函式原來把angular component轉換成瀏覽器能夠識別的自定義元素。
const customCardEle = createCustomElement(CustomCardComponent, {injector});
7.使用customElements.define()函式把自定義元素掛載到瀏覽器上的customElementRestory
customElements.define('custom-card', customCardEle);
第一個引數是自定義元素的tag名稱,第2個引數就是自定義元素。
建立自定義元素的程式碼放在哪裡?
angular專案都需要有一個啟動元件,一般的angular專案定義在NgModule.bootstrap中,對於對於web component的元件,NgModule.bootstrap是空的。
這個時候,啟動元件其實是AppModule這個類中的ngDoBootstrap()函式,我們可以把定義web component的程式碼放到這個函式中。
import { BrowserModule } from '@angular/platform-browser'; import { NgModule, Injector } from '@angular/core'; import { createCustomElement } from '@angular/elements'; import { CustomCardComponent } from './custom-card/custom-card.component'; @NgModule({ declarations: [CustomCardComponent], imports: [ BrowserModule ], providers: [], bootstrap: [], entryComponents: [CustomCardComponent] }) export class AppModule { constructor( private injector: Injector ) { } ngDoBootstrap(): void { const customCardEle = createCustomElement(CustomCardComponent, {injector: this.injector}); customElements.define('custom-card', customCardEle); } }
至此,在angular中建立web component已經完成了,下面我們來看一下如何打包及使用。
三. 打包專案
1.執行:ngbuild--prod=true--outputHashing=none
--prod=true可以讓angular優化打包的專案,例如tree-shaking
--outputHashing=none原本打包的檔名有hash值,使用這個設定可以去掉hash
可以看到生成3個js檔案,一個css檔案。
我們新建一個html檔案,把生成的runtime.js main.js polyfills.js和styles.css匯入到這個html中。
test.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 <style href="dist/custom-elements-demo/style.css"></style> 8 <script src="dist/custom-elements-demo/main.js"></script> 9 <script src="dist/custom-elements-demo/polyfills.js"></script> 10 <script src="dist/custom-elements-demo/runtime.js"></script> 11 </head> 12 <body> 13 <div> 14 <span>正被選擇的使用者:</span> 15 <span id="user"></span> 16 </div> 17 <custom-card id="1"></custom-card> 18 <custom-card id="2"></custom-card> 19 <script> 20 const user = document.getElementById('user'); 21 const card = document.getElementById('1'); 22 card.name = 'wt'; 23 card.info = {age: 25, phone: '111-222-3333'}; 24 card.addEventListener('selectUser', (e) => { 25 user.innerText = e.detail.name; 26 }); 27 const card1 = document.getElementById('2'); 28 card1.name = 'syc' 29 card1.info = {age: 18, phone: '555-666-3333'}; 30 card1.addEventListener('selectUser', (e) => { 31 user.innerText = e.detail.name; 32 }); 33 </script> 34 </body> 35 </html>
可以看到,custom-card這個web component的使用和一般的html元素並沒有區別。
下面是顯示的結果:
我們可以看到,預設情況下,angular使用ng build是把專案打成4個檔案的,3個js檔案和一個css檔案,一般的專案使用沒問題,但是定義web component專案,4個檔案有點不方便,如果只使用一個檔案就好了。
我們來使用webpack來再次打包一下,目標是合成一個檔案。
修改build 命令:ngbuild--prod=true--outputHashing=none--single-bundle