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

Source : https://www.webcomponents.org
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
Source : https://i.gifer.com/KdY7.gif

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/elementsAdds 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...
Using Custom Elements:
Further down the rabbit hole:
<thank-you></thank-you>


Demo Code: https://jamb.it/ng-element-code
angular elements meetup
By Ajit Kumar Singh
angular elements meetup
- 173