ANGULAR MEETUP - LEIPZIG #10

PRACTICAL INTRODUCTION TO ANGULAR ELEMENTS

Who am I ?

Even Stack Overflow can't answer! 😱

Ajit Kumar Singh

Doing Software Stuff 

Doing frontend stuff.

I ❤️ web with all its weirdness.

First time speaking in a Meetup. 😰

OUTLINE

  • Framework hell
  • Web Components specifications
    • Shadow DOM
    • Custom Elements
  • Angular component as Web Component
    • Custom Elements and Angular
    • Angular Elements
  • More to explore

FRAMEWORK HELL

Hard to keep up.

Locked into, very hard upgrade path — causing application re-write.

FRAMEWORK HELL

The lifespan of frameworks.

Different teams, different needs, different frameworks.

Our components cannot be used by other teams not using the same framework.

FRAMEWORK HELL

Idea: To make it easier to build components that are reusable across frameworks.

Problem: It is impossible to make all different frameworks to arrive at and agree

on a common contract.

FRAMEWORK HELL

FRAMEWORK HELL

Proposal: Since all these frameworks rely on DOM, in order to be reusable across frameworks, our components should be indistinguishable from HTML/DOM elements.

FRAMEWORK HELL

What benefits would there be?

  • Interoperability :  Components can be framework free and be used in multiple projects of different technology stacks.
     
  • Lifespan : Since components are interoperable there is less need to re-write them to fit into newer technologies, hence they have a longer life.
     
  • Productivity: Using already built components means faster development and more productivity.

FRAMEWORK HELL

Is there any standard and non "hacky" way of writing components reusable across frameworks?

WEB COMPONENTS

WEB COMPONENTS

  • Custom elements: Creating and using new types of DOM elements.
  • Shadow DOM: Attach an encapsulated DOM tree to an element.
  • HTML Template: Markup that goes unused at page load, but can be instantiated later.
  • ES Module: Include and reuse of JS documents in other JS documents.

Umbrella term for collection of specifications:

WEB COMPONENTS

<audio src="sound.mp3" controls></audio>

Browsers already provide quite a few such reusable tags.

WEB COMPONENTS

SHADOW DOM

SHADOW DOM

The global scope of DOM and the Multiple-actor Problem (MAP):

(MAP) occurs when multiple bits of code assume that they are the only actor and these assumptions conflict with each other.

DOM, internal representation of a web page, is global in nature.

This lack of encapsulation results in conflicting CSS, overlapping IDs and so on.

SHADOW DOM

In a nutshell, Shadow DOM enables local scoping for HTML & CSS.

SHADOW DOM

Isolated DOM: A component's DOM is self-contained. Not accessible directly outside the shadow boundary.

// query over regular document
document.getElementById('inShadow'); // null
// Can only access via shadowRoot
const shadowRoot=document.getElementById('container').shadowRoot;
shadowRoot.getElementById('inShadow').innerText; // "Say Please?"

Benefits:

SHADOW DOM

Scoped CSS: CSS defined inside shadow DOM is scoped to it.

  • No CSS bleeding.
  • Can have simple names.

Benefits:

SHADOW DOM

This is how browsers have been doing it:

<audio src="audio.mp3" controls></audio>

The host to put all this in is still missing.

SHADOW DOM

Where is the shadow host, how about some behaviours?

CUSTOM ELEMENTS

CUSTOM ELEMENTS

Create your own reusable custom HTML tags.

// in JS
class MyCustomTag extends HTMLElement {
  ...
}

 window.customElements.define('my-custom-tag', MyCustomTag);

Until the upgrade element gets parsed as HTMLUnknownElement.

The process of giving class definition to the tag is called "element upgrades".

Custom tag must be lower case with a hyphen.

Defined using an ES2015 class which extends HTMLElement.

CUSTOM ELEMENTS

// in JS
class MyCustomTag extends HTMLElement {

    constructor() {
    // If you define a constructor, always call super() first!
        super();
    }

    //Fired every time the component is added anywhere in the DOM.
    connectedCallback() {...}

    //Fired every time the component is removed from the DOM.
    disconnectedCallback() {...}

    //Called when an observed attribute has been added, removed, updated.
    attributeChangedCallback(name, oldValue, newValue) {...}
}

Reactions: fired at different points in the element's lifecycle.

CUSTOM ELEMENTS

Communication with custom elements:

Attributes

<my-custom-tag id="myTag" increment="1"></my-custom-tag>

Properties

Methods

Events

const myTag=document.getElementById('myTag');
myTag.time=new Date();
const myTag=document.getElementById('myTag');
myTag.incrementBy(10);
const detail={increment: this.increment};
const event = new CustomEvent("doAdd", {detail});
this.dispatchEvent(event);

const myTag=document.getElementById('myTag')
myTag.addEventListener('doAdd',(e)=>{console.log(e)});

CUSTOM ELEMENTS

// in JS
class MyCustomTag extends HTMLElement {
  ...
  
  // Return array of strings where each string is the name of the attribute to observe.
  // If attribute is NOT in this array,
  // component will not respond to ADD/REMOVE/CHANGE of that attribute.
  static get observedAttributes() {
    return ['increment'];
  }

  // Fired when any of the attributes listed in "observedAttributes()" change.
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'increment') {
      // do something with newValue
    }
  }
}

Attributes:

<my-custom-tag id="myTag" increment="1"></my-custom-tag>

CUSTOM ELEMENTS

// in JS
class MyCustomTag extends HTMLElement {

    constructor() {
        super(); // always call super() first in the constructor.
        this._time = new Date();
    }

    get time() {
        return this._time;
    }

    set time(time) {
        this._time = time;
    }
}

Properties:

const myTag=document.getElementById('myTag');
myTag.time=new Date();

CUSTOM ELEMENTS

Can we take advantage of these specs while working with Angular?

CUSTOM ELEMENTS AND ANGULAR

CUSTOM ELEMENTS AND ANGULAR

Angular Component and Custom Elements analogy:

Angular Component Custom Element
​@Input() ​Attributes / Properties
​@Output() ​CustomEvent()
Lifecycle Events Reactions

CUSTOM ELEMENTS AND ANGULAR

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnChanges, OnDestroy {

    @Input() increment: number = 1;

    @Input() time: Date = new Date();

    @Output() doAdd = new EventEmitter<number>();

    constructor() {}
    ngOnInit(): void {}
    ngOnChanges(changes: SimpleChanges): void {}
    ngOnDestroy(): void {}
}

Reactions

Attribute

Property

Custom Event

CUSTOM ELEMENTS AND ANGULAR

So how can we convert our angular component to custom element and able to use it outside Angular?

ANGULAR ELEMENTS

ANGULAR ELEMENTS

Angular Element is a package, part of the Angular framework.

 

@angular/elements was introduced in Angular 6.

ANGULAR ELEMENTS

"Angular Component on the inside, standards on the outside."

Rob Wormald, Angular Team™

ANGULAR ELEMENTS

Convert your Angular component into a Custom Element.

Provides class and function that do the bridging between Angular and Custom Elements.

  • NgElement - Abstract class which extends the HTMLElement class and obligates to implement the necessary life-cycle hooks of a Custom Element.
  • createCustomElement - Function that takes a component and an optional configuration to create and return an NgElement implementation of the component.
createCustomElement<P>(component: Type<any>, config: NgElementConfig): NgElementConstructor<P>

ANGULAR ELEMENTS

ng add @angular/elements

Adds elements & polyfill

Using Angular CLI

Getting dependencies:

  • Installs @angular/elements package
  • Installs document-register-element polyfill
  • Adds the polyfill to scripts in angular.json

ANGULAR ELEMENTS

Build changes needed:

// in tsconfig.json
{
  "compilerOptions": {
    ...
    "target": "es2015",
    ...
  }
}
"build": "ng build --prod --output-hashing none"

In package.json

ANGULAR ELEMENTS

Build changes needed:

ng build command outputs 4 files:

  • runtime.js
  • scripts.js
  • polyfills.js
  • main.js

We’d like to distribute our component as a single javascript file.

"build": "ng build --prod --output-hashing none && <Your concatenation script>"

ANGULAR ELEMENTS

Angular component:

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    encapsulation: ViewEncapsulation.ShadowDom
})
export class AppComponent implements OnInit, OnChanges, OnDestroy {

    @Input() increment: number = 1;
    @Input() time: Date = new Date();
    @Output() doAdd = new EventEmitter<number>();

    constructor() {}
    ngOnInit(): void {}
    ngOnChanges(changes: SimpleChanges): void {}
    ngOnDestroy(): void {}
}

ANGULAR ELEMENTS

@NgModule({
    declarations: [AppComponent],

    imports: [BrowserModule],

    // bootstrap: [AppComponent],

    entryComponents: [AppComponent]

})
export class AppModule {

}

Angular module:

ANGULAR ELEMENTS

import {createCustomElement} from "@angular/elements";
...

@NgModule({...})
export class AppModule implements DoBootstrap {

    constructor(private  injector: Injector) {}

    ngDoBootstrap() {

            const ngElement = createCustomElement(AppComponent, {injector: this.injector});

            window.customElements.define('ng-element', ngElement);
    }
}

Method that transforms Angular component into Custom Element.

Angular component

Custom Element

Angular module:

ANGULAR ELEMENTS

DEMO TIME

ANGULAR ELEMENTS

@Component({
  selector: 'hello-world',
  template: '...',
  
})
export class HelloWorld {
  ...
}
customElement: true

SPECULATIVE

What were all the steps again?

ANGULAR ELEMENTS

What about the bundle size though?

Ivy: Angular's upcoming rendering engine.

SO MUCH MORE, SO LESS TIME...

SO MUCH MORE, SO LESS TIME...

Further down the rabbit hole:

SO MUCH MORE, SO LESS TIME...

Further down the rabbit hole:

<thank-you></thank-you>

angular elements meetup

By Ajit Kumar Singh