Complete Angular Elements Example


Lars Christian Stokholm von Qualen

In our previous article, we promised to deliver a better example of how one would go about using Angular Elements in production. So now we deliver!

At the moment there is still a lot of pitfalls when using Angular like this. For starters in order to get it working in IE11, we need to use some serious polyfilling. Which unfortunately takes a great big swing at initial load time, but do not worry, we have you covered. In the next article we will look into how we manage to load more than 100 web components in Internet Explorer 11, and still load fast.

First things first

Before we dive into this proof of concept, you should get yourself situated with a fresh Angular CLI project, and maybe a cup of coffee or a Club Mate.

I wont go into detail about generating a new CLI project, if you are looking into this stuff, you are probably already an expert on the Angular CLI.

Dependencies

We need to add a couple of new dependencies:

package.json

 ...
  "dependencies": {
    "@angular/animations": "7.2.0",
    "@angular/common": "7.2.0",
    "@angular/compiler": "7.2.0",
    "@angular/core": "7.2.0",
    "@angular/elements": "7.2.9",
    "@angular/forms": "7.2.0",
    "@angular/platform-browser": "7.2.0",
    "@angular/platform-browser-dynamic": "7.2.0",
    "@angular/router": "7.2.0",
    "@webcomponents/webcomponentsjs": "2.2.7",
    "core-js": "2.5.4",
    "rxjs": "6.3.3",
    "tslib": "1.9.0",
    "zone.js": "0.8.26",
    "document-register-element": "1.7.2",
    "elements-zone-strategy": "7.0.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "0.13.0",
    "@angular/cli": "7.3.5",
    "@angular/compiler-cli": "7.2.0",
    "@angular/language-service": "7.2.0",
    "@types/node": "8.9.4",
    "ts-node": "7.0.0",
    "tslint": "5.11.0",
    "typescript": "3.2.2"
  }
}

Now the versions are the latest at the time of writing, if you run into trouble i would recommend trying to update the versions shown above.

Notice how we include @angular/elements, @webcomponents/webcomponentsjs, document-register-element and especially elements-zone-strategy.

Now go ahead and do a npm install.

My Greeter Component

Because this is just a proof of concept, i wont bother you with a giant complex component. But it does use the @Input() decorator, which is freaking awesome that it actually works in the web components context!

src/app/my-greeter/my-greeter.component.ts

 import { Component, Input } from '@angular/core';
 
@Component({
  // The selector becomes obsolete. Unless you intend to use the component in
  // a Angular context.
  selector: 'app-my-greeter',
  template: `
    <h1>Hello {{ lastName }}, {{ firstName }}</h1>
  `
})
export class MyGreeterComponent {
  @Input() public firstName: string;
  @Input() public lastName: string;
}

Pretty basic hello world component.

Wire up app.module.ts

It is probably easiest to just show you the goodies.

src/app/app.module.ts

import { Injector, NgModule } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { BrowserModule } from '@angular/platform-browser';
import { ElementZoneStrategyFactory } from 'elements-zone-strategy';
import { AppComponent } from './app.component';
import { MyGreeterComponent } from './my-greeter/my-greeter.component';
 
@NgModule({
  declarations: [
    AppComponent,
    // We still need to declare the component
    MyGreeterComponent
  ],
  entryComponents: [
    // The component even becoms an entry component!
    MyGreeterComponent
  ],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(injector: Injector) {
    /**
     * IMPORTANT
     *
     * There is a bug with zone.js, this fixes that.
     */
    const strategyFactory = new ElementZoneStrategyFactory(
      MyGreeterComponent,
      injector
    );
    const myGreeterContructor = createCustomElement(MyGreeterComponent, {
      injector,
      strategyFactory
    });
    customElements.define('my-greeter', myGreeterContructor);
  }
}

So what sticks out? Hopefully a couple of things.

Polyfills

So in order for this to work in Internet Explorer 11, we need some polyfilling. Hopefully you get to only support evergreen browsers. Then by all means, skip this and save yourself some kilobytes of bundle size.

So we need to edit both angular.json and polyfill.ts

angular.json

"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    ...
    "scripts": [
      {
        "input": "node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js",
        "bundleName": "custom-elements-es5-adapter"
      },
      {
        "input": "node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"
      }
    ],
  },
  "configurations": {
    ...
  }
}

So we need to add some files to our scripts bundle. The custom-elements-es5-adapter is there because Web Components actually needs to be proper ES6 classes. But this magically fixes that.

Then we need to add the straight up webcomponents-bundle.js, to fix other stuff with IE.

src/polyfills.ts

import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'document-register-element/build/document-register-element';

After zone.js has been imported, it is very important to add document-register-element.

Add the component in index.html

Okay, so everything should be fixed by now. Lets try and add the component in our src/index.html.

src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Angular Elements Example</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>
    <app-root></app-root>
 
    <my-greeter first-name="John" last-name="Doe"></my-greeter>
  </body>
</html>

Now serve up the app and feel the magic

Just npm start and you should see something like:

example of working angular web component

Now as i mentioned, if this needs to scale we have to do some tricks. I will go through this in the next article. The entire codebase is available at Github.

I hope you are enjoying the articles, and find them useful. LVQ Consult is at the moment a small danish consultancy, but we have ambitions of expanding a lot! So if you think this was a piece of cake drop me a line at [email protected], so we can talk business.