【问题标题】:How to Compile Angular Element Into Web Component w/ Webpack or Angular CLI?如何使用 Webpack 或 Angular CLI 将 Angular 元素编译为 Web 组件?
【发布时间】:2018-11-10 15:37:45
【问题描述】:

我使用 Pascal Precht 的 tutorial 通过 Angular 构建了一个简单的 Web 组件,您可以看到它在工作 HERE。它在链接中的 Stackblitz 上自动神奇地编译,但不是在本地编译。

我的最终目标是将生成的 Web 组件的代码放在本地的单独文件中。最终,我会将其上传到某个地方并通过单个 <script> 标记将其拉入,就像普通的 raw-html/javascript Web 组件。我认为这个问题不言自明,但如果您愿意,可以阅读以下详细信息:


详情

总结一下上面链接中的代码,我有一个非常基本的组件:

import { Component } from '@angular/core';

@Component({
  selector: 'hello-world',
  template: `<h1>Hello world</h1>`
})

export class HelloComponent  {}

我有一个模块:

import { NgModule, Injector } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements'
import { HelloComponent } from './hello.component';

@NgModule({
  imports: [BrowserModule],
  declarations: [HelloComponent],
  entryComponents: [HelloComponent]
})

export class AppModule { 
  constructor(private injector: Injector) {}
  ngDoBootstrap() {
    const HelloElement = createCustomElement(HelloComponent, {
      injector: this.injector 
    });

    customElements.define('hello-world', HelloElement);
  }
}

下面是对上面模块的解释:

  1. 将我的组件添加到 entryComponents 数组中,这样它就不会被 angular tree-shaker 取出(因为它在应用程序启动时无法访问: entryComponents: [HelloComponent]
  2. 通过createCustomElement 函数运行我的组件,这样我就可以将它用作普通的html Web Component

    const HelloElement = createCustomElement(HelloComponent, { 注射器:this.injector });

  3. 最后,我要求 Angular 在main.ts 中编译这个组件:

    platformBrowserDynamic().bootstrapModule(AppModule);


这是我完整阅读/观看的内容(在许多其他链接中 - 其中大部分都已过时,例如原始的 Angular Elements 介绍):

Web Components from Scratch by Tomek Sułkowski(他从不单独编译)
Web Components with CLI(同样的问题)
Web Components by Academind(再一次,这家伙也在 Angular 应用程序中使用它们)

感谢您的帮助。

【问题讨论】:

  • @PrasannaSasne 我没有发现它是解决问题的方法,尽管它确实帮助我理解了问题,这就是你获得赏金的原因。

标签: angular webpack angular-cli angular-elements


【解决方案1】:

当前 Angular 版本不提供将组件导出为单个本地文件的选项,该文件可用于任何非 Angular 应用程序。但是,可以通过更改构建和部署步骤来实现。在我的示例中,我创建了两个角度元素,一个按钮和警报消息。这两个组件都被编译并导出为单个本地文件,我使用 javascript 将其加载到纯 html 文件中。

步骤如下: 1、在entryComponent列表中添加ButtonComponent和AlertComponent。在 ngDoBootstrap 中并将它们定义为自定义元素。这就是我的 app.module 的样子:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';

import { AppComponent } from './app.component';
import { ButtonComponent } from './button/button.component';
import { AlertComponent } from './alert/alert.component';

@NgModule({
  declarations: [AppComponent, ButtonComponent, AlertComponent],
  imports: [BrowserModule],
  entryComponents: [ButtonComponent, AlertComponent]
})
export class AppModule {
  constructor(private injector: Injector) {
  }

  ngDoBootstrap() {
    const customButton = createCustomElement(ButtonComponent, { injector: this.injector });
    customElements.define('my-button', customButton);

    const alertElement = createCustomElement(AlertComponent, { injector: this.injector});
    customElements.define('my-alert', alertElement);
  }
}

  1. 这是我的按钮组件:
import {
  Input,
  Component,
  ViewEncapsulation,
  EventEmitter,
  Output
} from '@angular/core';

@Component({
  selector: 'custom-button',
  template: `<button (click)="handleClick()">{{label}}</button>`,
  styles: [
    `
    button {
      border: solid 3px;
      padding: 8px 10px;
      background: #bada55;
      font-size: 20px;
    }
  `
  ],
  encapsulation: ViewEncapsulation.Native
})
export class ButtonComponent {
  @Input() label = 'default label';
  @Output() action = new EventEmitter<number>();
  private clicksCt = 0;

  handleClick() {
    this.clicksCt++;
    this.action.emit(this.clicksCt);
  }
}
  1. 这是我的警报组件:
import { Component, Input, OnInit } from '@angular/core';
@Component({
  selector: 'alert-message',
  template: '<div>Alert Message: {{message}}</div>',
  styles: [
    `
    div {
      border: 1px solid #885800;
      background-color: #ffcd3f;
      padding: 10px;
      color: red;
      margin:10px;
      font-family: Arial;

    }
    `]
})
export class AlertComponent {
  @Input () message: string;
}
  1. 在 angular.json 中构建配置:
"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    "outputPath": "dist",
    "index": "src/index.html",
    "main": "src/main.ts",
    "polyfills": "src/polyfills.ts",
    "tsConfig": "src/tsconfig.app.json",
    "assets": ["src/favicon.ico", "src/assets"],
    "styles": ["src/styles.css"],
    "scripts": [
      {
        "input":
          "node_modules/document-register-element/build/document-register-element.js"
      }
    ]
  },
  "configurations": {
    "production": {
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.prod.ts"
        }
      ],
      "optimization": true,
      "outputHashing": "all",
      "sourceMap": false,
      "extractCss": true,
      "namedChunks": false,
      "aot": true,
      "extractLicenses": true,
      "vendorChunk": false,
      "buildOptimizer": true
    }
  }
},
"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
  "options": {
    "browserTarget": "angular6-elements:build"
  },
  "configurations": {
    "production": {
      "browserTarget": "angular6-elements:build:production"
    }
  }
},
"extract-i18n": {
  "builder": "@angular-devkit/build-angular:extract-i18n",
  "options": {
    "browserTarget": "angular6-elements:build"
  }
}
  1. 构建后,我将runtime, polyfills, script js 文件连接成单个脚本文件并导出包含自定义元素的elements.js(可选:gzip 这些文件)使用 http-server deploy --gzip 服务它
"scripts": {
  "ng": "ng",
  "start": "ng serve",
  "build": "ng build --prod --output-hashing=none",
  "package": "npm run package-base && npm run package-elements",
  "package-base": "cat dist/{runtime,polyfills,scripts}.js | gzip > deploy/script.js.gz",
  "package-elements": "cat dist/main.js | gzip > deploy/elements.js.gz",
  "serve": "http-server deploy --gzip",
  "test": "ng test",
  "lint": "ng lint",
  "e2e": "ng e2e"
}
  1. 最后,我在 index.html(部署目录中)中包含 script.jselements.js,以告知浏览器自定义元素。现在 my-button 和 my-alert 可以包含在 index.html 中。在此示例中,按钮在加载时显示,并在单击按钮时动态添加警报消息(带有计数器编号)。这是代码:
    <!DOCTYPE html>
    <html lang="en">

    <head>
      <meta charset="UTF-8">
      <title>Custom Button Test Page</title>
      <script src="script.js"></script>
      <script src="elements.js"></script>
    </head>

    <body>
      <my-button label="Show Alert Message!"></my-button>

      <p></p>

      <div id="message-container"></div>

      <script>
        const button = document.querySelector('my-button');
        const msgContainer = document.querySelector('#message-container');
        button.addEventListener('action', (event) => {
          console.log(`"action" emitted: ${event.detail}`);
          button.setAttribute("label", "Show Next Alert Message!");
          msgContainer.innerHTML += `<my-alert message="Here is a message #${event.detail} created dynamically using ng elements!!!"></my-alert>`;
        });

      </script>
    </body>

    </html>

这是my git repo的链接

希望这会有所帮助!

谢谢。

【讨论】:

    【解决方案2】:

    你好。

    如果我理解正确,您想生成一个 Web 组件(比如说&lt;my-component&gt;&lt;/my-component),然后使用一个简单的脚本标签来获取 .js 文件来初始化该组件并添加它在您想要的任何 html 页面上。

    在我的GitHub repository 中,我创建了一个简单的待办事项列表组件。该组件遵循 Angular Elements 原则,此外,我还为 webpack 安装了一些文件管理库,以便将 JS 打包到一个 JS 文件中。

    您可以查看此存储库,看看是否对您有帮助。只需克隆它,然后运行 ​​npm install 然后运行 ​​npm run build:elements 如果有任何问题,请随时与我联系。

    也可以查看guide。这个人帮了我很多。

    祝你好运

    【讨论】:

      【解决方案3】:

      根据我阅读的内容,Angular Elements 组件的特定包装以便在 Angular 之外轻松使用将随 Angular 7 一起提供。

      您现在可以做的是使用 cli 创建 Angular 应用程序。

      ng new YourAppName
      

      添加 Angular Elements 库:

      ng add @angular/elements
      

      这也添加了官方 Angular Documentation 中描述的所有必需的 polyfill。

      然后您将 AppModule 更改为不是引导模块,而只是注册自定义元素。您从 NgModule 中删除引导程序并将组件添加为入口组件。然后将组件注册为ngDoBootstrap 钩子中的自定义元素。我制作了默认的 AppComponent 和 HelloComponent 自定义元素。这是我的应用模块的样子:

      import { BrowserModule } from '@angular/platform-browser';
      import { NgModule, Injector } from '@angular/core';
      import { createCustomElement } from '@angular/elements';
      
      import { AppComponent } from './app.component';
      import { HelloComponent } from '../hello/hello.component';
      
      @NgModule({
        declarations: [
          AppComponent,
          HelloComponent
        ],
        imports: [
          BrowserModule
        ],
        providers: [],
        entryComponents: [AppComponent, HelloComponent]
      })
      export class AppModule {
        constructor(private injector: Injector) {
        }
      
        ngDoBootstrap() {
          customElements.define('app-root', createCustomElement(AppComponent, {injector: this.injector}));
          customElements.define('hello-world', createCustomElement(HelloComponent, {injector: this.injector}));
        }
       }
      

      然后你可以使用 index.html 中的元素,比如像这样的元素:

      <!doctype html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>ElementsTest</title>
        <base href="/">
      
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
      </head>
      <body>
        <div>
          <app-root></app-root>
        </div>
        <div>
          <hello-world></hello-world>
        </div>
      </body>
      </html>
      

      如果您使用ng build --prod 构建它,您将获得最小化的包,您现在也可以在其他 html 页面中使用这些包,方法是在 index.html 文件中包含编译器包含的包脚本。

      我已将我的示例添加到GitHub。在历史记录中,您可以看到我从最初的 cli 应用程序中所做的更改。

      【讨论】:

        【解决方案4】:
        import { NgModule} from '@angular/core';
        import { BrowserModule } from '@angular/platform-browser';
        import { HelloComponent } from './hello.component';
        import { AppComponent } from './app.component';
        
        @NgModule({
          imports: [BrowserModule],
          declarations: [AppComponent, HelloComponent],
          entryComponents: [HelloComponent],
          bootstrap: [AppComponent]
        })
        export class AppModule { }
        

        确保你使用

        npm install --save @angular/elements 并在 package.json 中添加 "@webcomponents/custom-elements" : "^1.0.8"。 之后运行npm install & 除此之外,您还需要从 polyfills.ts 中取消注释以下行

        这添加了自定义元素工作所需的 polyfill。

        import '@webcomponents/custom-elements/custom-elements.min'; import '@webcomponents/custom-elements/src/native-shim';

        &lt;my-tag message="This is rendered dynamically"&gt;stack Overflow&lt;/my-tag&gt;

        Angular 不会编译上述代码,但 Angular 元素通过允许获取我们的 Angular 组件并将其放入完全封装的自引导 HTML 元素中来解决此问题,您可以通过以下方式将其转储到您的 Angular 应用程序中,例如这仍然有效。

        在 AppComponent.ts 文件中

         import { Component, Injector } from '@angular/core'; 
         import { createCustomElement } from '@angular/elements'
         import { DomSanitizer } from '@angular/platform-browser';
        
         import { HelloComponent } from './hello.component';
        
         @Component({
          selector: 'app-root',
          template: '<div [innerHtml]="title"></div>',
          styleUrls: ['./app.component.css']
         })
         export class AppComponent {
         title = null;
        
         constructor(injector: Injector, domsanitizer: DomSanitizer){
           const customElement = createCustomElement(HelloComponent, {injector: 
           injector});
        
           //this feature is not provided by angular it is provided by javascript
           //this allows us to register custom web component
           customElements.define('my-tag', customElement);
           //instead of 'hello-world' i've used 'my-tag'    
           setTimeout(() => {
            //security mechanism is used to avoid cross site attacks
            this.title = domsanitizer.bypassSecurityTrustHtml('<my-tag message="This 
            is rendered dynamically">stack Overflow</my-tag>');     
            }, 1000);
           }
         }
        

        在你的HelloComponent里面

         import { Component, OnInit, Input } from '@angular/core';
        
         @Component({
         selector: 'hello-world',
         template: `<div> hello component -- {{ message }}</div>`,
         styles: [`
          div {
            border: 1px solid black;
            background-color: red;
            padding: 1%;
           }
         `]
        })
        export class HelloComponent implements OnInit {
        @Input() message : string;
        
        constructor() { }
        
        ngOnInit() {
        }
        }
        

        现在这是作为原生 Web 组件加载的。仍然只能在 Angular 项目中使用,但已经可以用于像这样的动态内容。

        我希望这将帮助您在本地运行您的代码

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-07-19
          • 2016-01-20
          • 2017-05-19
          • 1970-01-01
          • 2016-12-12
          • 1970-01-01
          • 1970-01-01
          • 2017-01-13
          相关资源
          最近更新 更多