A look into the future:
Angular Elements and Ionic Stencil
Andrei Antal
07.12.2017
ngBucharest
Angular
You have no excuse for not using Angular in your next project!
But...
Angular might be tricky for:
- enhancing HTML static pages
- CMS
- widgets
- mixed environments (React, Vue, jQuery, Vanilla etc.)
Angular Dynamic Pages
Angular dynamic pages talk - AngularConnect 2017
Ward Bell and Jesus Rodriguez
Angular docs - angular.io
Angular Dynamic Pages
Angular dynamic pages talk - AngularConnect 2017
Ward Bell and Jesus Rodriguez
Angular docs - angular.io
Angular Dynamic Pages
- Embeddable components
- the public api for the component is simple
-
creating component factories and wiring them up
-
dynamic boostrap
-
manually trigger change detection
-
manually clean up when components are destroyed
...difficult but not impossible
Using Angular Components is difficult to use outside Angular templates and even more difficult to use outside Angular
Can we do better?
How can we make it easier?
WEB COMPONENTS
WEB COMPONENTS
A set of 4 standard specifications:
- HTML Templates
- Shadow DOM
- HTML Imports
- Custom Elements
WEB COMPONENTS
A set of 4 standard specifications:
- HTML Templates
- Shadow DOM
- HTML Imports
- Custom Elements
<template>
<h1> Hello <h1>
<div>
...content
</div>
</template>
WEB COMPONENTS
A set of 4 standard specifications:
- HTML Templates
- Shadow DOM
- HTML Imports
- Custom Elements
<my-app>
<#shadow-root>
<div class="main">
<h1 class="title">
Hello
</h1>
...
<div>
</shadow-root>
</my-app>
WEB COMPONENTS
A set of 4 standard specifications:
- HTML Templates
- Shadow DOM
- HTML Imports
- Custom Elements
<html>
<head>
<link
rel="import"
href="file.html">
</link>
</head>
</html>
WEB COMPONENTS
A set of 4 standard specifications:
- HTML Templates
- Shadow DOM
- HTML Imports
- Custom Elements
<my-app>
<custom-header />
<main-content />
</my-app>
WEB COMPONENTS
WEB COMPONENTS
<my-datepicker></my-datepicker>
WEB COMPONENTS
class MyDatepicker extends HTMLElement {
static observedAttributes = [‘my-date’];
attributeChangedCallback(oldvalue, newvalue, key) {
// update the DOM
}
}
Attributes
<my-datepicker date="7/12/2017"> </my-datepicker>
const myPicker = document.querySelector('my-dateicker');
myPicker.setAttribute('my-date', new Date().toString());
WEB COMPONENTS
class MyElement extends HTMLElement {
set someValue(value) { ... }
get someValue() { return ... }
}
- Properties
const myPicker = document.querySelector('my-dateicker');
elem.somevalue = "...";
WEB COMPONENTS
class MyDatepicker extends HTMLElement {
emitDateChange() {
let datechangeEv =
new CustomEvent(‘date-change’, {dateDetails});
this.dispatchEvent(datechangeEv);
}
}
- Events
const myPicker = document.querySelector('my-dateicker');
elem.addEventListener(‘some-change’,() => { /* change */});
WEB COMPONENTS
class MyDatepicker extends HTMLElement {
connectedCallback() { ... }
disconnectedCallback() { ... }
attributeChangedCallback(
attributeName,
oldValue,
newValue,
namespace ) { ... }
adoptedCallback(oldDocument, newDocument)
}
- Lifecycle hooks
WEB COMPONENTS
<my-angular-app>
<my-datepicker>
[attr.someAttr]="someAttr"
[someProp]="someProp"
(someEvent)="handleEvent()"
</my-datepicker>
</my-angular-app>
- Custom elements in Angular
Why isn't everyone doing it?
It would be great if we could still use Angular...
The Angular platform
ANGULAR ELEMENTS
...or best of both worlds
ANGULAR ELEMENTS
Angular Components
packaged as
Web Components
ANGULAR ELEMENTS
-
Hosting Angular Components inside Custom Elements (NgElement) - Angular on the inside, standards on the outside
-
Bridge between Angular Components and DOM
-
@Inputs - properties
-
@Outputs - events
-
@HostBinding - attributes
-
-
Zones optional
-
Self bootstraping - drop on a page, and it works
-
Anyone can use it
Demo time
Rob Wormald video: https://www.youtube.com/watch?v=vHI5C-9vH-E
Angular repo: https://github.com/angular/angular/tree/labs/elements)
IONIC FRAMEWORK
IONIC FRAMEWORK
The good
- Open source, cross-platform UI framework
- Based 100% on web technologies (HTML5, CSS, JS)
- Used to develop Native mobile apps, alongside Cordova
The not so good
- Only uses Angular
- Big bundles - problematic for websites or PWA
STENCIL
What is it?
- Stencil is a compiler that generates Web Components (Custom elements)
- Some features:
- Written in TypeScript
- Uses Virtual DOM
- Async rendering (inspired by React Fiber)
- Reactive data-binding
- JSX for templating
STENCIL
A simple component
import { Component, Prop } from '@stencil/core';
@Component({
tag: 'my-component',
styleUrl: 'my-first-component.scss'
})
export class MyComponent {
@Prop() name: string;
render() {
return (
<p>
Hello {this.name}
</p>
);
}
}
<my-component name="World"></my-component>
STENCIL
Using JSX
render() {
return (
<div>Hello {this.name}</div>
)
}
JSX - Conditional rendering
render() {
return (
<div>
{this.name
? <p>Hello {this.name}</p>
: <p>Hello World</p>
}
</div>
);
}
STENCIL
JSX - Looping
render() {
return (
<div class="todo-list">
{this.todos.map((todo) =>
<div class="todo-item">
<div>{todo.taskName}</div>
<div>{todo.isCompleted}</div>
</div>
)}
</div>
)
}
<div class="todo-list">
<div class="todo-item">...</div>
<div class="todo-item">...</div>
<div class="todo-item">...</div>
<div class="todo-item">...</div>
...
</div>
result:
STENCIL
JSX - Handle events
export class MyComponent {
handleClick(event: UIEvent) {
alert('Received the button click!');
}
render() {
return (
<button onClick={this.handleClick(event).bind(this)}>
Click Me!
</button>
);
}
}
STENCIL
Methods
import { Method } from '@stencil/core';
...
export class TodoList {
@Method()
showPrompt() {
// show a prompt
}
}
const todoListElement = document.querySelector('todo-list');
todoListElement.showPrompt();
STENCIL
State
import { State } from '@stencil/core';
...
export class TodoList {
@State() completedTodos: Todo[];
completeTodo(todo: Todo) {
this.completedTodos = [...this.completedTodos, todo];
}
render() {
// render the todo list
}
}
STENCIL
Change detection
// our original array
this.items = ['ionic', 'stencil', 'webcomponents'];
// update the array
this.items = [
...this.items,
'awesomeness'
]
// our original object
let myCoolObject = {first: '1', second: '2'}
// update our object
myCoolObject = { ...myCoolObject, third: '3' }
STENCIL
Events
import { Event, EventEmitter } from '@stencil/core';
...
export class TodoList {
@Event() completed: EventEmitter;
completedHandler(todo: Todo) {
this.todoCompleted.emit(todo);
}
}
const todoListElement = document.querySelector('todo-list');
todoListElement.addEventListener('completed', (ev) => {/*handle*/})
STENCIL
Embedding/nesting components
import { Component } from '@stencil/core';
@Component({
tag: 'my-parent-component'
})
export class MyParentComponent {
render() {
return (
<div>
<my-embedded-component color="red"></my-embedded-component>
</div>
);
}
}
STENCIL
Component lifecycle
import { Component } from '@stencil/core';
@Component({
tag: 'my-component'
})
export class MyComponent {
componentWillLoad() {
console.log('The component is about to be rendered');
}
componentDidLoad() {
console.log('The component has been rendered');
}
componentWillUpdate() {
console.log('The component will update');
}
componentDidUpdate() {
console.log('The component did update');
}
componentDidUnload() {
console.log('The view has been removed from the DOM');
}
}
STENCIL
Routing (add-on)
<stencil-router>
<stencil-route url="/" component="landing-page" exact={true}/>
<stencil-route url="/demos" component="demos-page"/>
<stencil-route url="/demos/rendering" component="fiber-demo"/>
<stencil-route url="/docs" component="docs"/>
<stencil-route url="/components" component="basics-components"/>
</stencil-router>
<stencil-route-link url="/">
<stencil-route-link url="/demos">
<stencil-route-link url="/docs/getting-started">
import { RouterHistory } from '@stencil/router';
export class askPage {
@Prop() history: RouterHistory;
}
STENCIL
Configuration
exports.config = {
bundles: [
{ components: ['stencil-site', 'site-header', 'landing-page'] },
{ components: ['getting-started', 'code-splitting', 'stencil-ssr'] },
{ components: ['demos-page'] }
],
collections: [
{ name: '@stencil/router' }
],
serviceWorker: {
globPatterns: [
'**/*.{js,css,json,html,ico,png,jpeg}'
],
globIgnores: [
'build/app/svg/*.js'
]
},
copy: [
{ src: 'images' },
{ src: 'styles', dest: 'css' }
]
};
STENCIL
Others
- Server side rendering
- Easy unit testing
- Service worker
- Shadow DOM
Demo time
A look into the future. Thank you!
See you all next year!
ngBucharest
A look into the future: Angular Elements and Ionic Stencil
By Andrei Antal
A look into the future: Angular Elements and Ionic Stencil
- 1,836