Modern, Scalable, Ambitious apps with Ember.js

Post on 15-Apr-2017

239 views 0 download

Transcript of Modern, Scalable, Ambitious apps with Ember.js

Mike NorthLevanto Financial, Inc.

Ember.js – Modern, Scalable, Ambitious Apps

JS UI LIBRARIES99 2000 01 02 03 04 05 06 07 08 09 2010 11 12 13 14 15 16 17

2 2

6 7 8 9 10 11 12

ES3 ES5

@MichaelLNorth

NOW IMAGINE…

• All the micro-libraries

• All the build tools

• All the pre-processors and post-processors

• All of the permutations with typescript, babel, etc…

“BEST IN CLASS” OPTIONS FROM EACH CATEGORY?

WHAT ARE SOME OF THE MODERN CHOICES?

2

What our users need us to build

What we’re sometimes tempted to build

What our users need us to build

THE EMBER ECOSYSTEMember-cli

Code Generation

Asset Pipeline

Plugins

Extensible CLI

Test Runner

ember.jsSPA routing

Components

Templating

Data Binding

Prioritized Work Queue

ember-dataRequest Construction

JSON Serialization

Redux-like Data Flow

Async Relationships

liquid-fireanimations

ember-collectionvirtualized list

THE EMBER ECOSYSTEM

• Built on strong conventions & opinions

• Focus on developer productivity

• Aligned with web standards

• Abstractions hold up w/ large scale & complexity

• Constantly improving

TEMPLATING

• Handlebars

• Declarative markup, an extension of HTML

• Compiled at build time

• Extensible

Hello, <strong> {{firstName}} {{lastName}} </strong>!

define('examples/templates/index', ['exports'], function (exports) {

'use strict';

exports['default'] = Ember.HTMLBars.template((function() { return { ... buildFragment: function buildFragment(dom) { var el0 = dom.createDocumentFragment(); var el1 = dom.createTextNode("Hello, "); dom.appendChild(el0, el1); var el1 = dom.createElement("strong"); var el2 = dom.createComment(""); dom.appendChild(el1, el2); var el2 = dom.createTextNode(" "); dom.appendChild(el1, el2); var el2 = dom.createComment(""); dom.appendChild(el1, el2); dom.appendChild(el0, el1); var el1 = dom.createTextNode("!"); dom.appendChild(el0, el1); return el0; }, buildRenderNodes: function buildRenderNodes(dom, fragment, contextualElement) { var element0 = dom.childAt(fragment, [1]); var morphs = new Array(2); morphs[0] = dom.createMorphAt(element0,0,0); morphs[1] = dom.createMorphAt(element0,2,2); return morphs; }, statements: [ ["content","firstName",["loc",[null,[1,15],[1,28]]]], ["content","lastName",["loc",[null,[1,29],[1,41]]]] ],

... }; }()));});

TEMPLATING

• Helpers

• Block vs inline form

• Easy to read and reason about

My pet goes {{#if isDog}}arf

{{else}}meow

{{/if}}

My pet goes {{if isDog "arf" "meow"}}

My pet goes {{if isDog "arf" "meow"}}

function ifHelper(condition, ifTrue, ifFalse) { return condition ? ifTrue : ifFalse }

{{#if isEnabled}} <b>Switch is enabled</b> {{/if}}

function ifHelper(condition, callbackIfTrue) { return condition ? callbackIfTrue() : ''; }

<ul> {{#each arr as |item|}} <li>{{item.name}}</li> {{/each}} </ul>

function eachHelper(array, cb) { return array .map(item => cb(item)) .join(''); }

ROUTING

• Router - Finite State Machine

• Routes - Manage transitions between states

• URLs drive your app this is a core pillar of Ember

ROUTINGember

routing

URL changes

components templatesHTML++

ember-data

store adaptermakes requests

serializertransforms JSON

Talks to API

application

index

application

posts post

author comments

index

index index

indexindex

/URL:

application

posts

posts/index

application

posts post

author comments

index

index index

indexindex

/posts/URL:

application

post

post/index

application

posts post

author comments

index

index index

indexindex

/post/31URL:

application

post

application

posts post

author comments

index

index index

indexindex

{{outlet}}

/post/31/comments/URL:

application

post

application

posts post

author comments

index

index index

indexindex

post/comments

post/comments/index

/post/31/comments/URL:

COMPONENTS

How many of you have been using some type of web component for > 5 years?

COMPONENTS

<my-widget title='Enter your Info'> <my-field name='Username'/> <my-field name='Password'/> </my-widget>

W3C WEB COMPONENT SPEC IS INCOMPLETE!

…AND THIS IS A GOOD THING

WEB UI LIBS ARE JUST A GIANT SERIES OF HACKS

TC39

<my-widget title='Enter your Info'> <my-field name='Username'/> <my-field name='Password'/> </my-widget>

{{#my-widget title='Enter your Info’}} {{my-field name=‘Username’}} {{my-field name=‘Password’}} {{/my-widget}}

init on instantiation

willInsertElement before the component’s element is inserted into the DOM

didInsertElement after the component’s element has been inserted into the DOM

willDestroyElement before the component’s element is removed from the DOM

$(document).ready() for components

Clean up before tear down

EMBER.COMPONENT

EMBER-DATA

• Unidirectional data flow, single atom of state

• Can talk to any API

• Moves data massaging out of your business logic

• Built around fetch (important for SS rendering!)

• Saves tons of time if your API uses consistent conventions

EMBER-DATA: MODEL

• Representation of any (usually persistent) data

• Defined by attributes, and relationships to other models

• “model” is the factory that defines the structure of “records”

// app/models/book.js import DS from 'ember-data';

const { attr, hasMany } = DS;

export default DS.Model.extend({ title: attr('string'), publishedAt: attr('date'), chapters: hasMany('chapter') });

EMBER-DATA: STORE

• Where you get/create/destroy records

• A single source of truth

• This means that all changes are kept in sync!

• Similar concept in Facebook/flux, angular-data

EMBER-DATA: STORE

// (maybe) API request for all records of type "author" this.store.findAll('author'); // (maybe) API request for record of type "post" with id 37 this.store.findRecord('post', 37);

// API request for all records of type "author" this.store.fetchAll('author'); // API request for record of type "post" with id 37 this.store.fetchRecord('post', 37);

// look in cache for all records of type "author" this.store.peekAll('author'); // look in cache for record of type "post" with id 37 this.store.peekRecord('post', 37);

EMBER-DATA: ADAPTER

export default DS.RESTAdapter.extend({ host: 'https://api.github.com',

urlForQuery (query, modelName) { switch(modelName) { case 'repo': return `${this.get('host')}/orgs/${query.orgId}/repos`; default: return this._super(...arguments); } } });

EMBER-DATA: SERIALIZER

export default DS.RESTSerializer.extend({

normalizeResponse(store, modelClass, payload, id, requestType){ const newPayload = { org: payload }; return this._super(store, modelClass, newPayload, id, requestType); }

});

EMBER-CLI

ADOPTION

RECENT DEVELOPMENTS

FASTBOOT

ENGINES

GLIMMER 2

…AND MORE…

THANKS!

COME TO THE EMBER.JS WORKSHOP!