autoscale: true slidenumbers: true
- Opinionated architecture
- "All-in-one" solution π
- Templating
- Routing
- Composable Components
- Data/Model layer
- Build tooling/CLI
- Testing framework
- Addon ecosystem
- Community
-
Project creation
ember new project-name
-
Local development
ember server
-
Builds
ember build
-
Generators
ember g component my-component
-
Testing
ember test
- POEOs (Ember Object)
- Routes
- Components
- Controllers (Routable Components)
- Models (Ember-data)
- Services
import Ember from 'ember';
const { computed } = Ember;
let jedi = Ember.Object.create({
firstName: 'Luke',
lastName: 'Skywalker',
fullName: computed('firstName', 'lastName', function() {
return `${this.get('firstName')} ${this.get('lastName')}`;
})
});
jedi.get('fullName');
// => "Luke Skywalker"
jedi.set('firstName', 'Anakin');
jedi.get('fullName');
// => "Anakin Skywalker"
- Pure HTML is perfectly valid Handlebars
<div class="header">
<h1>Hello World!</h1>
</div>
- Ember is a "Routing first" framework
- URLs are meant to represent state
// in app/router.js
Router.map(function() {
this.route('index', { path: '/' }); // This comes implicitly
this.route('jedis'); // => /jedis
});
// app/routes/jedis.js
import Ember from 'ember';
const { Route } = Ember;
export default Route.extend({
model() {
return [
"Luke Skywalker",
"Obi-Wan Kenobi",
"Yoda",
"Mace Windu"
];
}
});
Router.map(function() {
this.route('index', { path: '/' }); // This comes implicitly
this.route('jedis', function() {
this.route('jedi', { path: '/jedis/:jedi_id' })
});
});
- One controller backs one route
- "Routeable component" -- The top-level component for this route
// app/controllers/jedis.js
import Ember from 'ember';
const { Controller, computed } = Ember;
export default Controller.extend({
// Serves the template @ app/templates/jedis.hbs
// Has access to whatever is returned in route's model() hook via `model` prop
jedisModel: computed.alias('model')
});
- The core of Ember π
- Composable π΅
- Two files
- Component definition (.js)
- Component template (.hbs)
// app/components/my-component.js
import Ember from 'ember';
const { Component } = Ember;
export default Ember.Component.extend({
tagName: 'div',
classNames: ['class-name-one', 'class-name-two'],
classNameBindings: ['isShowingFoo:true-class:false-class']
foo: 'hello world!',
isShowingFoo: false
})
// app/components/lightsaber-picker.js
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
lightsaberColor: 'green'
isShowingLightsaber: false,
actions: {
toggleShowingLightsaber() {
// Ember sugar that toggles boolean values
this.toggleProperty('isShowingLightsaber');
},
setLightsaberColor(color) {
this.set('lightsaberColor', color);
}
}
});
// app/components/han-solo.js
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
classNames: ['han-solo']
classNameBindings: ['isFrozenInCarbonite:frozen:unfrozen'],
isFrozenInCarbonite: false,
click() {
this.toggleProperty('isFrozenInCarbonite');
}
});
// app/components/x-wing.js
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
shipHealth: 100,
firingTorpedos: false,
actions: {
fireProtonTorpedos() {
this.set('firingTorpedos', true);
}
}
});
// app/components/luke-skywalker.js
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
usingTheForce: false,
// shipHealth passed in by parent
shouldAbortMission: computed('shipHealth', function() {
return this.get('shipHealth') < 50;
}),
actions: {
useTheForce() {
this.set('usingTheForce', true);
// Closure action -- passed in by parent
this.get('fire')(); // or this.attrs.fire()
}
}
});
didUpdateAttrs
: invoked when a component's attributes have changed but before the component is rendered.willUpdate
: invoked before a component will rerender, whether the update was triggered by new attributes or by rerender.didUpdate
: invoked after a component has been rerendered.didReceiveAttrs
: invoked when a component gets attributes, either initially or due to an update.willRender
: invoked before a component will render, either initially or due to an update, and regardless of how the rerender was triggered.didRender
: invoked after a component has been rendered, either initially or due to an updatedidInsertElement
willDestroyElement
- Model/Data Library
- Backend agnostic (json-api compliant works out of the box)
store
interface- Plus
- Relationship Support for Models
- Adapters - Talks Endpoints
- Serializers - Talks Payloads
// app/models/jedi.js
import DS from 'ember-data';
const { Model, attr, hasMany, belongsTo } = DS;
export default Model.extend({
firstName: attr('string'),
lastName: attr('string'),
hasBothHands: attr('boolean'),
shotsDeflected: attr('number'),
midichlorians: hasMany('midichlorians'),
lightsaber: belongsTo('lightsaber')
});
// app/routes/jedis.js
import Ember from 'ember';
const { Route } = Ember;
export default Route.extend({
model() {
// Store => Adapter => "/jedis" => payload => Serializer => Model
return this.store.findAll('jedi');
}
});
// In a route, controller, or component action
let jedi = this.store.createRecord('jedi', {
firstName: 'Luke',
lastName: 'Skywalker',
hasBothHands: true,
shotsDeflected: 99999999
});
jedi.save(); // => POST /jedis
jedi.get('isDirty'); // => false
jedi.set('hasBothHands', false);
jedi.get('isDirty'); // => true
jedi.save(); // => PATCH/PUT /jedis/:id
jedi.destroyRecord(); // => DELETE /jedis
- QUnit or Mocha
- Acceptance, Integration, & Unit
- Ember helpers
- Acceptance
- Feature workflow and user interaction from start to finish.
- Unit
- Common to test the methods, computed properties, and observers of Components, Models, Controllers, Services, and Utility Objects.
- Integration
- Sit between Unit tests and Acceptance tests
- Since components are never truly isolated in an Ember application, these work well for testing a components behavior
- Route hooks are promise-aware
beforeModel
model
afterModel
- Returned promise resolving === Loading state
- Error states
- Actions hash just like components!
- Extremely powerful construct for sharing state across application
- Example uses of services include
- Logging
- User/session authentication
- Geolocation
- Third-party APIs
- Web Sockets
- Server-sent events or notifications
- Server-backed API calls that may not fit Ember Data
- Drop in libraries tailored for ember apps
- Robust ecosystem
ember install addon <addon-name>
- Popular addons include
- ember-cli-mirage
- emberx-select
- ember-cli-simple-auth
- Async loading outside of routes
- Two way bindings
- Testing story & Async testing
- Doesn't like inconsistent APIs
- Learning Curve?
- Contextual components
- Mixins
- Initializers
- Ember run loop (Backburner.js)
- Ember inspector
- Standalone Library
- Extracted from Ember's component layer
- Typescript
- "Ember-flavored React"
- Only 34kb π