Mike North discusses composability in Ember and highlights four areas where composability can be applied today: style, tests, computed properties, and components. For each area, he provides examples of how to make elements more composable, such as using atomic CSS classes to encapsulate styles, creating page objects to reduce test verbosity, combining computed properties, and implementing a card component with sub-components that project content. He emphasizes that composable elements are self-contained, built around established contracts, and promote reuse.
4. //TODO
• The state of ember at yahoo
• What’s composability, and why do we care?
• 4 areas where you can compose today
• Style
• CPMs
• Components
• Tests
5. @MichaelLNorth
Yahoo Ads & Data
• 14 Ember Apps
• 68 Ember-focused developers
• A “flagship” app that ’s huge (70K lines JS)
• An internal collection of add ons
Ember @ Yahoo
14. @MichaelLNorth
Great places to startComputed Properties
How does a computed property work?
GET
Has cached
value?
Recalculate
No
Yes
X
Allows
caching?
Cache
X
XYes
No
ReturnX
15. @MichaelLNorth
Great places to startComputed Properties
How does a computed property work?
DEPENDENT CHANGED
Cache
X
I’ve
changed!
ViewProperty
obj.get(‘val’)
16. @MichaelLNorth
Great places to startComputed Properties
CPs can be thought of as filters
CP
r
g
b
#ff1a99
get() set()
(sometimes)
CP
r
b
g
#ff1a99
17. @MichaelLNorth
Great places to startComputed Properties
• Ember.computed.*
Macros make this even easier
function product(prop, coeff) {
return Ember.computed(prop, {
get() {
return this.get(prop) * coeff;
}
});
}
18. @MichaelLNorth
Great places to startComputed Properties
totalAmount: sum(
'subtotal',
'tipAmount',
'taxAmount',
product('discount', -1)
),
Composable CPs can be mixed and
matched
ember-cpm
21. @MichaelLNorth
Great places to startComponents
• Not source of truth for state ✔
• Promotes Reuse ✔
• Recombinant ?
Components are pretty close…
ember-cli-materialize
22. @MichaelLNorth
Great places to startComponents
Looking for this
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
23. @MichaelLNorth
Great places to startComponents
One option - lowest common element
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
{{#wge-card}}
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
{{#wge-card-action}}
<span {{action "accept"}}>
Accept
</span>
{{/wge-card-action}}
{{#wge-card-action}}
<span {{action "cancel"}}>
Cancel
</span>
{{/wge-card-action}}
</div>
{{/wge-card}}
24. @MichaelLNorth
Great places to startComponents
One option - lowest common element
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
{{#wge-card}}
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
{{#wge-card-action}}
<span {{action "accept"}}>
Accept
</span>
{{/wge-card-action}}
{{#wge-card-action}}
<span {{action "cancel"}}>
Cancel
</span>
{{/wge-card-action}}
</div>
{{/wge-card}}
Not Useful
25. @MichaelLNorth
Great places to startComponents
Another option - parent does everything
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
{{#wge-card
title="Wicked Good Ember"
cardActions=myCardActions}}
This will go in the body
of the card
{{/wge-card}}
26. @MichaelLNorth
Great places to startComponents
Another option - parent does everything
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
{{#wge-card
title="Wicked Good Ember"
cardActions=myCardActions}}
This will go in the body
of the card
{{/wge-card}}
Not Composable
27. @MichaelLNorth
Great places to startComponents
The expressive option
{{#wge-card title="Wicked Good Ember"}}
This will go in the body of the card
{{#wge-card-action}}
<span {{action "accept"}}>Accept</span>
{{/wge-card-action}}
{{#wge-card-action}}
<span {{action "cancel"}}>Cancel</span>
{{/wge-card-action}}
{{/wge-card}}
28. @MichaelLNorth
Great places to start
{{#wge-card
title="Wicked Good Ember"}}
This will go in the
body of the card
{{#wge-card-action}}
<span {{action “accept"}}>
Accept
</span>
{{/wge-card-action}}
{{#wge-card-action}}
<span {{action “cancel”}}>
Cancel
</span>
{{/wge-card-action}}
{{/wge-card}}
Components
Content projection - ruh roh
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
4 distinct pieces of content
29. @MichaelLNorth
Great places to start
{{#wge-card
title="Wicked Good Ember"}}
This will go in the
body of the card
{{#wge-card-action}}
<span {{action “accept"}}>
Accept
</span>
{{/wge-card-action}}
{{#wge-card-action}}
<span {{action “cancel”}}>
Cancel
</span>
{{/wge-card-action}}
{{/wge-card}}
Components
Content projection - ruh roh
<div class="card blue-grey darken-1">
<div class="card-content white-text">
<div class="card-title">
Wicked Good Ember
</div>
This will go in the body of the card
</div>
<div class="card-action">
<a>
<span {{action “accept”}}>Accept</span>
</a>
<a>
<span {{action “cancel”}}>Cancel</span>
</a>
</div>
</div>
4 distinct pieces of content
Sub-components
project into parent
{{yield}}
Simple property binding
30. @MichaelLNorth
Great places to startComponents
Content projection approach
{{#wge-card
title="Wicked Good Ember"}}
This will go in the
body of the card
{{#wge-card-action}}
<span {{action “accept"}}>
Accept
</span>
{{/wge-card-action}}
{{#wge-card-action}}
<span {{action “cancel”}}>
Cancel
</span>
{{/wge-card-action}}
{{/wge-card}}
• Child components won’t
render directly
• Parent will handle
rendering of children
• Register/unregister to
parent
35. @MichaelLNorth
Great places to startTests
A lot of tests are verbose and ugly
• Sensitivity to order and/or timing
• Brittle selectors to interact with the DOM
• 1 change —> break N tests
36. @MichaelLNorth
Great places to startTests
test(“authorized user should end up at account's search list page”, function(assert) {
server.get(`${apiHost.url}/me`, json(200, me));
server.get(`${apiHost.url}/campaigns/:id`, json(200, campaign1));
server.get(`${apiHost.url}/seats/2`, json(200, seat2));
server.get(`${apiHost.url}/seats/1`, json(200, seat1));
server.get(`${apiHost.url}/account/:id`, json(200, account1));
server.get(`${apiHost.url}/breadcrumbs`, json(200, breadcrumbs.campaign));
visit('/app/account/1/campaigns');
andThen(function() {
assert.equal(currentPath(), ‘app.account.campaign', 'Current url is search list page for campaig
assert.equal(Ember.$('.top-navbar .brand-logo .active-title').text().trim(), campaign1.campaign
assert.equal(Ember.$('.resource-tiles-container .card').length, campaigns.campaigns.length, 'On
assert.deepEqual(Ember.$('.resource-tiles-container .resource-tile:first-child .card .card-cont
['Created', 'Updated'], 'Columns are correct');
assert.equal(Ember.$('.new-campaign-button').length, 1, 'New Campaign button is on the screen')
});
});
A lot of tests are verbose and ugly
37. @MichaelLNorth
Great places to startTests
A wild PageObject appears
• Prime pretender
• Access to controls
• Specific asserts
fillInclick
currentURL
andThenvisit
triggerEvent
$().val()$
$().click()$().trigger()
setFirstName
clickResetButton setAge
openSettings
PageObject API
Ember Testing API
DOMSee:
38. @MichaelLNorth
Great places to startTests
Writing PageObjects
• Return this
• To assert or not to assert?
• Build PageObjects for components
Example
The recombinant
part!
39. @MichaelLNorth
Great places to startSome final thoughts
Even more composability on the way!
• Add-ons
• Ember.Service
• Engines (TBD)
• Components ( {{yield}}, block params,
etc…)