Slide 1

Slide 1 text

Modern Web Development with Web Components Simon Gauvin CTO Consulting Inc. [email protected]

Slide 2

Slide 2 text

function showMessage() { console.log(“Hello World”); } .my-class { font-size: 20px; color: #217FF9; }
Header
Markup Declarative Functional 3 different language types!

Slide 3

Slide 3 text

function showMessage(msg) { var txt = document.getElementByID(“text”); txt.innerHTML = msg; txt.classList.add(“my-class”); } .my-class { font-size: 20px; color: #217FF9; }

Slide 4

Slide 4 text

Loose links between CSS/JS/HTML CSS leakage into DOM Code loading dependencies OOP/MVC hard to achieve No way to extend HTML

Slide 5

Slide 5 text

Hello world!

class CoolHeading extends HTMLElement { constructor() { super(); this.addEventListener('click', () => { this.style.color = 'red'; }); } connectedCallback() { this.style.color = 'blue'; } } customElements.define('cool-heading', CoolHeading); JavaScript HTML CSS

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Registration Lifecycle Local DOM Data binding Declared Properties Events Methods Utility Functions

Slide 8

Slide 8 text

/* CSS rules for your element */
{{greeting}}
// Define the element's API using an ES2015 class class XCustom extends Polymer.Element { static get is() { return 'x-custom'; } // Declare properties for the element's public API static get properties() { return { greeting: { type: String, value: "Hello!" } } } // Add methods to the element's public API greetMe() { console.log(this.greeting); } } // Register the x-custom element with the browser customElements.define(XCustom.is, XCustom); HTML Imports Custom Elements Shadow DOM Dataflow System ES5 Classes Utility Functions Events Bower package manager

Slide 9

Slide 9 text

import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; // Define the element's API using an ES2015 class class XCustom extends PolymerElement { // Define optional shadow DOM template static get template() { return html` /* CSS rules for your element */
[[greeting]]
`; } // Declare properties for the element's public API static get properties() { return { greeting: { type: String } } } constructor() { super(); this.greeting = 'Hello!'; } // Add methods to the element's public API greetMe() { console.log(this.greeting); } } // Register the x-custom element with the browser customElements.define('x-custom', XCustom); ES6 Modules - import ES5 Classes HTML/CSS template Events Dataflow System Custom Elements

Slide 10

Slide 10 text

import { LitElement, html, css} from 'lit-element'; class WebCom extends LitElement { static get styles() { return css` :host { display: block; } `;} render() { return html`
`;} constructor() { super(); this.greeting = 'Hello!'; } // Add methods to the element's public API greetMe() { console.log(this.greeting); } } customElements.define('web-com', WebCom); ES6 Modules - import ES5 Classes HTML/CSS template Events Dataflow System Custom Elements

Slide 11

Slide 11 text

HTML
JavaScript - JQuery $(function() { $(“#hello-world”).helloWorld(); });

Slide 12

Slide 12 text

HTML
JavaScript - React ReactDOM.render( React.createElement(HelloWorld, {name:”Moncton”}), document.getElementById(“hello-world”) );

Slide 13

Slide 13 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 14

Slide 14 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 15

Slide 15 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 16

Slide 16 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 17

Slide 17 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 18

Slide 18 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 19

Slide 19 text

import use

Slide 20

Slide 20 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } static get styles() { return css` :host { display: block; } div { color: red; } `;} render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld);

Slide 21

Slide 21 text

const element = new HelloWorld(); const element = document.createElement(“hello-world”); document.body.innerHTML = ‘’;

Slide 22

Slide 22 text

// Create Template const template = document.createElement(“hello-world”); template.innerHTML = ‘’; // Clone Template const content = document.importNode(template.content, true); const element = content.querySelector(“hello-world”);

Slide 23

Slide 23 text

import {Component} from ‘@angular/core’; import {HelloWorld} from ‘./hello-world’; @Component({ selector: ‘my-app’, template: `
This is a LitElement in an Angular component
` }) export class AppComponent { name = “Angular and LitElement”; }

Slide 24

Slide 24 text

import {Component} from ‘@angular/core’; import {HelloWorld} from ‘./hello-world’; @Component({ selector: ‘my-app’, template: `
This is a LitElement in an Angular component
` }) export class AppComponent { name = “Angular and LitElement”; }

Slide 25

Slide 25 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { name { type: String } }; } static get styles() { return css` :host { display: block; } div { color: red; } `;} render() { return html`
Hello ${this.name}
`; } } customElements.define('hello-world', HelloWorld); Declarative What vs. How

Slide 26

Slide 26 text

import { LitElement, html, css } from 'lit-element'; class HelloWorld extends LitElement { static get properties() { return { firstName { type: String }, lastName { type: String } }; } render() { return html`
Hello ${this.firstName} ${this.lastName}
`; } } customElements.define('hello-world', HelloWorld); //.. element.firstName = “James”; element.lastName = “Bond”; Reactive Async Rendering

Slide 27

Slide 27 text

● Uses Standard Web Components API/lifecycle ● Minimal Feature Set ● Full Typescript support ● 6.7K including lit-html ● Just JavaScript, no compilers required

Slide 28

Slide 28 text

requestUpdate() Queue Update Task update() render() Async / Batching Boundary

Slide 29

Slide 29 text

Connet to Document connectedCallback() requestUpdate() Queue Update Task

Slide 30

Slide 30 text

Set Property hasChanged() requestUpdate() Queue Update Task

Slide 31

Slide 31 text

Set Attribute attributeChangedCallback() requestUpdate() Queue Update Task

Slide 32

Slide 32 text

● Batches multiple state changes into one render ● Allows object-oriented interface with functional behavior ● Rendering can now be scheduled and prioritized ● No VDOM Diffing ● Preserves static vs dynamic structure ● Standard JavaScript ● 100% compatible with Custom Elements

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

html`
${title} ${content}
`;

Slide 35

Slide 35 text

let post = (title, content) =>
{title} {content}
;

Slide 36

Slide 36 text

let post = (title, content) => html`
${title} ${content}
`;

Slide 37

Slide 37 text

// Text html`

Hello ${name}

` // Attribute html`
` // Boolean Attribute html`` // Property html`` // Event Handler html` console.log('clicked')}>Click Me`

Slide 38

Slide 38 text

const listener = { handleEvent(e) { console.log('clicked'); }, capture: true, }; html`Click Me`

Slide 39

Slide 39 text

const header = html`

Header

`; const page = html` ${header}

This is some text

`;

Slide 40

Slide 40 text

const items = [1, 2, 3]; const list = () => html`items = ${items.map((i) => `item: ${i}`)}`; const items = { a: 1, b: 23, c: 456, }; const list = () => html`items = ${Object.entries(items)}`;

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

html` ${user.isloggedIn ? html`Welcome ${user.name}` : html`Please log in` } `;

Slide 43

Slide 43 text

getUserMessage() { if (user.isloggedIn) { return html`Welcome ${user.name}`; } else { return html`Please log in`; } } html` ${getUserMessage()} `;

Slide 44

Slide 44 text

const itemTemplates = []; for (const i of items) { itemTemplates.push(html`
  • ${i}
  • `); } html`
      ${itemTemplates}
    `;

    Slide 45

    Slide 45 text

    const employeeList = (employees) => html`
      ${repeat(employees, (employee) => employee.id, (employee, index) => html`
    • ${index}: ${employee.firstName}, ${employee.lastName}
    • `)}
    `;

    Slide 46

    Slide 46 text

    import {cache} from 'lit-html/directives/cache.js'; const detailView = (data) => html`
    ...
    `; const summaryView = (data) => html`
    ...
    `; html`${cache(data.showDetails ? detailView(data) : summaryView(data))} `;

    Slide 47

    Slide 47 text

    No content

    Slide 48

    Slide 48 text

    Markup Tags Shadow DOM Custom Elements Events Promises Functions Native API Styles Classes Properties/Attributes Mixins Decorators Templates Worker Threads

    Slide 49

    Slide 49 text

    Application Singletons Routes Localization Forms Security Async Tasks Templates Caching Components Dependency Injection State Management Services Reactivity Scheduling Module Loading

    Slide 50

    Slide 50 text

    No content

    Slide 51

    Slide 51 text

    DOM

    Slide 52

    Slide 52 text

    Orchastrator

    Slide 53

    Slide 53 text

    Orchastrator

    Slide 54

    Slide 54 text

    Many components in a page The App is a component

    Slide 55

    Slide 55 text

    Dependency Injection

    Slide 56

    Slide 56 text

    No content

    Slide 57

    Slide 57 text

    Properties can trigger code when value is changed. class Component extends LitElement { static get properties() { return { userName: { type: String, value: "" } } updated(change) { if (change && change.get('userName')) { this.shadowRoot.getElementById('userName'); // do something... } } render() { return html` `; } }

    Slide 58

    Slide 58 text

    DOM

    Slide 59

    Slide 59 text

    Data can flow into, or out of a component. Values of properties are read in the DOM with double square brackets [[x]] for 1-way data flow.
    [[name.first]]
    class Child extends Polymer.Element { static get is() { return "child"; } proporties() { name { Type: String } } } customElements.define(Child.is, Child); ...

    Slide 60

    Slide 60 text

    DOM

    Slide 61

    Slide 61 text

    Components can also communicate up the DOM by using Event system in browser. Kick Me class XCustom extends Polymer.Element { static get is() {return 'x-custom'} handleClick() { this.dispatchEvent( new CustomEvent('sign-out', {detail:{count:2}}, { bubbles: true, composed: true })); } } customElements.define(XCustom.is, XCustom);

    Slide 62

    Slide 62 text

    Components can also listen to Events dispatched by other components. class XCustom extends Polymer.Element { static get is() {return 'x-custom'} constructor() { super(); this.addEventListener('sign-out', function(event) { console.log(“event received”); }); } } customElements.define(XCustom.is, XCustom);

    Slide 63

    Slide 63 text

    Web Components philosophy “Use the Platform” Fixes the CSS/JS/HTML stack with integrated model Fixes CSS leakage with Shadow DOM Fixes code loading JavaScript with imports Fixes OOP/MVC with Components and Dataflow

    Slide 64

    Slide 64 text

    Not a native app Quick deploy via URL, avoid the App Store Feels Like an App - Add to Homescreen support Offline use & sync Immediate update for users No corporate data on users device Cross platform & one code base

    Slide 65

    Slide 65 text

    No content

    Slide 66

    Slide 66 text

    No content

    Slide 67

    Slide 67 text

    No content

    Slide 68

    Slide 68 text

    No content

    Slide 69

    Slide 69 text

    No content

    Slide 70

    Slide 70 text

    No content

    Slide 71

    Slide 71 text

    Model Functions Network Lib DB

    Slide 72

    Slide 72 text

    Redux Rematch Cloud Functions Observer RxJS JSON RTDB

    Slide 73

    Slide 73 text

    No content

    Slide 74

    Slide 74 text

    No content

    Slide 75

    Slide 75 text

    Build Test Monitor Marketing

    Slide 76

    Slide 76 text

    No content

    Slide 77

    Slide 77 text

    Create Firebase account Create firebase project Create polymer project Use firebase API directly in Polymer /or polymerfire $firebase init $firebase login $firebase serve $firebase deploy

    Slide 78

    Slide 78 text

    3rd Party API

    Slide 79

    Slide 79 text

    index.html

    Slide 80

    Slide 80 text

    No content

    Slide 81

    Slide 81 text

    No content

    Slide 82

    Slide 82 text

    Transfer the credentials into your app using polymerfire firebase-app component. class MyApp extends Polymer.Element { static get is() {return 'x-custom'} } customElements.define(VennChatApp.is, VennChatApp); bower install --save FirebaseExtended/polymerfire

    Slide 83

    Slide 83 text

    Login will use the <firebase-auth> to validate your Google account. Add login() and logout() methods to call auth object API. “dom-if” template used to show/hide components based on existence of user.

    Venn Chat App

    Login with Google class MyLogin extends Polymer.Element { static get is() {return 'my-login'} login() { return this.$.auth.signInWithPopup(); } logout() { return this.$.auth.signOut(); } } customElements.define(MyLogin.is, MyLogin); bower install --save PolymerElements/paper-button#^2

    Slide 84

    Slide 84 text

    No content

    Slide 85

    Slide 85 text

    No content

    Slide 86

    Slide 86 text

    Firebase Realtime Solve Problem of mobile client updates, push vs pull Client 1 Write HTTP RealTime DB Client n Update HTTP

    Slide 87

    Slide 87 text

    Firebase Realtime Structure : Big JSON Tree Named nodes Auto-gen nodes (timestamped) Mostly Typeless - Text, Numbers, Array, Objects

    Slide 88

    Slide 88 text

    No content

    Slide 89

    Slide 89 text

    No content

    Slide 90

    Slide 90 text

    Firestore Solve Scaling problems of Realtime Database, slow queries, data size, user count Client 1 Write HTTP Firestore Client n Update HTTP

    Slide 91

    Slide 91 text

    Firestore Data Collections Documents Collections contain Documents Documents contain data AND collections Nesting to 100 levels deep Each doc limited to 1MB in size.

    Slide 92

    Slide 92 text

    Documents Documents have a name, or auto-gen id Fields are a data type... Map is JSON object and can be any depth. Array is limited by doc size.

    Slide 93

    Slide 93 text

    No content

    Slide 94

    Slide 94 text

    No content

    Slide 95

    Slide 95 text

    exports.makeUppercase = functions.database.ref('/messages/{pushId}/text') .onCreate((snapshot, context) => { const text = snapshot.val(); const uppercase = text.toUpperCase(); return snapshot.ref.parent.child('text').set(uppercase); });

    Slide 96

    Slide 96 text

    const express = require('express'); const cors = require('cors'); const app = express(); app.use(cors({ origin: true })); app.use(myMiddleware); app.get('/:id', (req, res) => res.send(Widgets.getById(req.params.id))); app.post('/', (req, res) => res.send(Widgets.create())); app.put('/:id', (req, res) => res.send(Widgets.update(req.params.id, req.body))); app.delete('/:id', (req, res) => res.send(Widgets.delete(req.params.id))); app.get('/', (req, res) => res.send(Widgets.list()));

    Slide 97

    Slide 97 text

    Firebase Functions Realtime Server - Side Triggers

    Slide 98

    Slide 98 text

    Firestore Functions Server-Side Triggers

    Slide 99

    Slide 99 text

    No content

    Slide 100

    Slide 100 text

    RxJS Observers for User Event Handling

    Slide 101

    Slide 101 text

    RxFire Update List from subscribing to collection in Firebase import { Component, h } from 'preact'; import firebase from 'firebase/app'; import 'firebase/firestore'; import { collectionData } from 'rxfire/firestore'; class AppComponent extends LitElement { constructor() { this.app = firebase.initializeApp({ /* config */ }); this.todosRef = app.firestore().collection('todos'); collectionData(todosRef, 'id').subscribe(todos => { // re-render on each change this.setState({ todos }); }); } setState(...) render() { const lis = this.state.todos.map(t => Html `
  • ${t.title}
  • ` ); return html `
      ${lis}
    `;} }

    Slide 102

    Slide 102 text

    Realtime DB Use Case

    Slide 103

    Slide 103 text

    Design Mobile Web App (Polymer) Quick Deployment in Field Social Sharing of Hazzard Observations Real-time updates Inspections, Audits, Checklists Manage Task Lifecycle

    Slide 104

    Slide 104 text

    News Version 1 Multiple Lists - Single Model Each post type stored in single collection. Each read, then merge on client. Problems: 1. Query twice per view 2. Merge not scalable 3. Slow updates on merge 4. Can’t filter on state /posts/todo/{todoId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp } /posts/news/{newsId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp } this.todo = [] db.get(‘posts/todo’) .then(posts => { this.todo = posts; }) this.posts = [] db.get(‘posts/todo’) .then(posts => { this.todo = posts; }) this.posts.join(this.todo)

    Slide 105

    Slide 105 text

    News Version 2 Single Lists - Single Model Each post type stored in doc. Each read, then filtered on client. Problems: 1. Query not able to support multiple fields, site, user, state… at once. 2. Slow updates on filter /posts/{postId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp Type: post || todo State: “new” } this.posts = [] db.get(‘posts/’) .then(posts => { this.posts = posts; }) posts = this.posts.filter( item.type == post) tasks = this.posts.filter( item.type == todo)

    Slide 106

    Slide 106 text

    News Version 3 Single List - Multiple Models Each post type stored in doc. Multiple reads on query returns only docs that match. Tasks can be deferred until needed by user. /posts/{todoId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp Type: post || todo State: “new” } this.posts = [] db.get(‘posts/’) .where(type == post) .then(posts => { this.todo = posts; }) this.todo = [] db.get(‘posts/’) .where(type == todo) .then(posts => { this.todo = posts; })

    Slide 107

    Slide 107 text

    Doc List Items Create small docs for display in UX lists. Implementation: Create a seperate collection of small dos with minimum display fields and id to main doc. Common Use: List of products, inventory, lists with 1000+ items. Variation: for lists below 20K items use a single doc with Objects.

    Slide 108

    Slide 108 text

    Data Duplication Reduce number of reads by duplicating data for 1:1 relations. Implemenation: Create copies of fields and data into related doc from doc that would be queried by id. Common Use: User profile data (name, pic, id) attached to UX components, posts, tweets etc. Should be done for data that has low change rate, as changes are expensive.

    Slide 109

    Slide 109 text

    Data Aggregation Perform aggregate function on a collection of documents: Count() Implementation: Run a Cloud Function on collection and write results to doc. Common Use: Count the number of unread docs in a collection. Variation: Parent doc has col of child docs to compute aggregate values stored in parent.

    Slide 110

    Slide 110 text

    Firestore DB Use Case

    Slide 111

    Slide 111 text

    Re-Design Desktop Web App Data Migration from Linux Manage Financial Modules Multiple Levels of User Access High Transaction Rate Multi-tenant Rich Queries/Reports

    Slide 112

    Slide 112 text

    Financial Modules

    Slide 113

    Slide 113 text

    Array Search Maintain multiple states of a doc at the same time. Implementation: Strings are added to array field in a doc, used for search. Common uses: Tags for news posts. Variations: arrays can store ids vs strings, to be used on query in other col. /posts/{postId}/ { postId: “SjkSDLKJSD” Title: “My new Pic” Tags: [ ‘Funny’, ‘pic’, ‘selfie’ ] } db.collection(‘posts’) .where(‘Tags’, ‘array-contains’, ‘Funny’)

    Slide 114

    Slide 114 text

    Parent - Child A parent-child relation between docs Implementation: Subcollection, or Object fields. Common Use: Records for an account, Data scoped to parent. Multi-tenancy design. New: Can query across children col “All parents with child.doc.field = x” De-normalize using root collection.

    Slide 115

    Slide 115 text

    1:n Doc Relation One doc relates to n many docs. Implementation: Doc contains id to a collection that has many other docs. Common use: User has col of messages that belong to them. Variations: n Col can be subcol of user doc. N:1 where many docs refer to a single doc, such as userId. { Field: ID } ID/messages /users/{userId}/ { userId: “SjkSDLKJSD” Name: “Bob Smith” } /messages/{userId}/ { Message: “Message Text” } 1 Doc N messages

    Slide 116

    Slide 116 text

    n:m Doc Relation Many n docs relate to m many docs. Implementation: 2 types of doc contains ids to many other docs of each others type. Common use: Two objects that contain a set of each other Speakers-Sessions. Variation: Single doc points to 2 other kinds of docs. Sessions /speakers/{speakerId}/ { Name: “Bob Smith” Sessions: { S1: sessionId S2: sessionId } } /sessions/{sessionId}/ { Session: “S1” Speakers: { Bob Smith: speakerId Peter Orr: speakerId } } Speakers

    Slide 117

    Slide 117 text

    Form Docs - single read Forms to display and edit data. Implementation: Read a doc into a local data structure, to make modifications locally, then write back to doc. Common Uses: User Settings, Invoices, Accounts, Profiles. Variations: Use Realtime DB to write fields directly.

    Slide 118

    Slide 118 text

    Doc Cache For data that requires heavy processing that is performed periodically. Implementation: Store the results of a complex computation/data structure as a single doc to be read often. Common Use: Chart of Accounts tree computed from accounts. Variations: Statistics, analytics, graphs.

    Slide 119

    Slide 119 text

    Data Aggregation Firestore does not support native aggregation queries… so we us Cloud Functions as substitute. Situations where you have many reads, but few writes. Parent doc has col of child docs to compute aggregate values stored in parent. Updates to child trigger aggregation, without having to read all child col. accounts/ {XYZ} avgRate:24.5 numRate:2 ratings/ {jhadhkasd} rate:34 {jhadhkasd} rate:34

    Slide 120

    Slide 120 text