Angular Custom Elements - aakash14goplani/FullStack GitHub Wiki

Introduction

  • Angular elements is a feature of the angular framework that allows you to turn your normal angular components into native web components.

  • The Web Components build with angular can only be used in angular project. We can have question - what is the use of angular elements if you can only use these elements inside of an angular app. The answer is they are useful for loading dynamic content.

  • Example, let's say you got a content management system on your back and content editors can create HTML code that gets loaded into your angular app. As long as they are using plain HTML, it is fine. But what if you actually want to enable them to also use some of your Angular components in the HTML code they prepare.

  • If they do that if they use your angular components selectors and you then load this content dynamically in your angler's app it will actually not work because your angular app is compiled ahead of time or even with just in time compilation it's compiled before the content is loaded. So if the content contains the selector off angular element of angular component does will not work it will not recognize that.

    <div>content</div>
    <p>content</p>
    <app-custom-component>content</app-custom-component>    <!-- <= fail -->

Installation

  1. Install custom elements package: npm install --save @angular/elements
  2. Install rxjs-compat package: npm install --save rxjs-compat
  3. Install webcomponents elements package: npm install --save @webcomponents/custom-elements
  4. Add following imports to polyfills.ts
    import '@webcomponents/custom-elements/custom-elements.min';
    import '@webcomponents/custom-elements/src/native-shim';

Implementation

  • Assume we have a component which we want use in our CMS

    import { Component, Input } from '@angular/core';
    @Component({
       selector: 'app-custom-alert-element',
       template: `
          <div class="row">
              <div class="col-sm-12">
                  This is an alert. {{ message }}
              </div>
          </div>`,
       styles: [`
          div {
             border: 1px solid black;
             background: salmon;
             padding: 10px;
             font-family: sans-sarif;
             text-align: center;
          }
       `]
    })
    export class CustomAlertElementComponent {
        @Input() message: string = '';
    }
  • Now if I try to use this from another component, I can't:

    content: string = 'custom-elements works!';
    ...
    template: `
       <app-custom-alert-element message="content"></app-custom-alert-element>   <!--fails-->
       <div [innerHTML]="content"></div>   <!--prints successfully-->
    `
  • So only div with innerHTML gets rendered and for our custom component, nothing gets rendered here. And the reason for this is that I rendered code as HTML but it's not recognized as an valid Angular Component. It will be considered as a normal native element, but since we don't have any native element that is named as "app-custom-alert-element", so this is ignored by browser parser as well.

  • Angular elements fixes this issue. It allows us to basically take our angular component and put it into a totally encapsulated self bootstrapping custom element which you can dump into your angular app.

  • We can use createCustomElement() method that ships with angular custom elements package. This method requires two arguments: The first is component name that we want to use and second is the injector that angular uses for dependency injection which we provided to this custom element so that the element behind the scenes is able to connect itself to our apps.

    constructor(private injector: Injector) { }
    const alertComponent = createCustomElement(CustomAlertElementComponent, { injector: this.injector });
  • Now we can define the component. First of all define the task by which you want to select it and just doesn't have to be the same one as component selector app-custom-alert-element, we can use any name, e.g. random-tag. And we then pass-in our component instance.

    customElements.define('random-tag', alertComponent);
  • And now if we save this it compiles and if we go to our application nothing happens. But if we inspect the console we actually got an error that doesn't find a component factory. Angular elements are only available in angular apps. To make this component available, we need to define entryComponent to app.module file

    entryComponents: [CustomAlertElementComponent]
  • And now if we save the app and reload, we get at least a warning not an error: Sanitizing HTML stripped some content. Now the custom element is in the end a bunch of JavaScript code that is prevented to display just as a security mechanism to a avoid cross-site scripting attacks.

  • We wanted this mechanism but in this case we know our content is safe and therefore we want to be able to output it. We can do this with the help of the DomSanitizer.

    constructor(private injector: Injector, private domSanitizer: DomSanitizer) { }
    this.content = this.domSanitizer
                      .bypassSecurityTrustHtml('<hr><random-tag message="Dynamic Content Update from Server!!!"></random-tag>');
  • So now with that if we reload this we do see our alert after a second but this time it's loaded as custom element.

    import { Component, OnInit, Injector } from '@angular/core';
    import { createCustomElement } from '@angular/elements';
    import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
    import { CustomAlertElementComponent } from './custom-alert-element';
    @Component({
       selector: 'app-custom-elements',
       templateUrl: './custom-elements.component.html',
       styleUrls: ['./custom-elements.component.css']
    })
    export class CustomElementsComponent implements OnInit {
       constructor(private injector: Injector, private domSanitizer: DomSanitizer) { }
       content: SafeHtml = '';
       ngOnInit() {
          const alertComponent = createCustomElement(CustomAlertElementComponent, { injector: this.injector });
          customElements.define('random-tag', alertComponent);
          setTimeout(() => {
             // this.content = '<hr><div>Dynamic Content Update from Server!!!</div>';
             this.content = this.domSanitizer
                      .bypassSecurityTrustHtml('<hr><random-tag message="Dynamic Content Update from Server!!!"></random-tag>');
          }, 1000);
       }
    }
⚠️ **GitHub.com Fallback** ⚠️