Andrei Antal
07.12.2017
ngBucharest
Angular might be tricky for:
Angular dynamic pages talk - AngularConnect 2017
Ward Bell and Jesus Rodriguez
Angular docs - angular.io
Angular dynamic pages talk - AngularConnect 2017
Ward Bell and Jesus Rodriguez
Angular docs - angular.io
creating component factories and wiring them up
dynamic boostrap
manually trigger change detection
manually clean up when components are destroyed
...difficult but not impossible
A set of 4 standard specifications:
A set of 4 standard specifications:
<template>
<h1> Hello <h1>
<div>
...content
</div>
</template>
A set of 4 standard specifications:
<my-app>
<#shadow-root>
<div class="main">
<h1 class="title">
Hello
</h1>
...
<div>
</shadow-root>
</my-app>
A set of 4 standard specifications:
<html>
<head>
<link
rel="import"
href="file.html">
</link>
</head>
</html>
A set of 4 standard specifications:
<my-app>
<custom-header />
<main-content />
</my-app>
<my-datepicker></my-datepicker>
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());
class MyElement extends HTMLElement {
set someValue(value) { ... }
get someValue() { return ... }
}
const myPicker = document.querySelector('my-dateicker');
elem.somevalue = "...";
class MyDatepicker extends HTMLElement {
emitDateChange() {
let datechangeEv =
new CustomEvent(‘date-change’, {dateDetails});
this.dispatchEvent(datechangeEv);
}
}
const myPicker = document.querySelector('my-dateicker');
elem.addEventListener(‘some-change’,() => { /* change */});
class MyDatepicker extends HTMLElement {
connectedCallback() { ... }
disconnectedCallback() { ... }
attributeChangedCallback(
attributeName,
oldValue,
newValue,
namespace ) { ... }
adoptedCallback(oldDocument, newDocument)
}
<my-angular-app>
<my-datepicker>
[attr.someAttr]="someAttr"
[someProp]="someProp"
(someEvent)="handleEvent()"
</my-datepicker>
</my-angular-app>
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
Rob Wormald video: https://www.youtube.com/watch?v=vHI5C-9vH-E
Angular repo: https://github.com/angular/angular/tree/labs/elements)
The good
The not so good
What is it?
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>
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>
);
}
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:
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>
);
}
}
Methods
import { Method } from '@stencil/core';
...
export class TodoList {
@Method()
showPrompt() {
// show a prompt
}
}
const todoListElement = document.querySelector('todo-list');
todoListElement.showPrompt();
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
}
}
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' }
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*/})
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>
);
}
}
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');
}
}
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;
}
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' }
]
};
Others
ngBucharest