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

An​gular dynamic pages talk - AngularConnect 2017

Ward Bell and Jesus Rodriguez

Angular docs - angular.io

Angular Dynamic Pages

An​gular 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

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

Made with Slides.com