A paradigm shift Suchita Doshi suchitadoshi suchitadoshi1987 suchita009 suchita009 1
A presentation at EmberConf 2020 in March 2020 in by Suchita Doshi
A paradigm shift Suchita Doshi suchitadoshi suchitadoshi1987 suchita009 suchita009 1
When I am away from my computer… 2
When I am away from my computer… 2
When I am away from my computer… 2
When I am away from my computer… 2
When I am away from my computer… 2
Agenda 3
Agenda Journey Of EmberJS Ember 1.x Ember 2.x Ember 3.x 3
Agenda Journey Of EmberJS Ember Octane Native Classes Glimmer Components Templates in Octane Tracked Properties Modifiers & Decorators 3
Agenda Journey Of EmberJS Ember Octane Classic vs Octane Epic comparison 3
Agenda Journey Of EmberJS Ember Octane Classic vs Octane Epic comparison Migration tools & References 3
The JOURNEY Of 4
Ember 1.x 5
Ember 1.x Convention over configuration (A new mental model) 5
Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability 5
Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data 5
Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data View Driven architecture 5
Ember 1.x Convention over configuration (A new mental View model) Controller Route Model
Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data View Driven architecture Two way data bindings 5
Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data View Driven architecture Two way data bindings Attribute bindings using {{bind-attr}} 1 <div {{bind-attr title=post.popup}}></div> 5
Ember 2.x 6
Ember 2.x Component driven Route Model Controller + Templates Model Component + Templates 1 export default Ember.Component.extend({ 2 classNameBindings: [‘isActive’], 3 isActive: true, 4 firstName: ‘John’, 5 lastName: ‘Doe’, 6 // Computed Property 7 fullName: Ember.computed(‘firstName’, ‘lastName’, function() { 8 return this.get(‘firstName’) + ’ ’ + this.get(‘lastName’); 9 }), 10 11 fullNameChanged: Ember.observer(‘fullName’, function() { 12 // deal with the change 13 }) 14 15 }); 6
Ember 2.x Component driven “Glimmer rendering engine” adoption 6
Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} // v1.x 1 <div {{bind-attr title=post.popup}}></div> // v2.x 1 <div title=”{{post.popup}}”></div> 6
Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} Better template scoping 6
Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} Better template scoping // v1.x
1 2 3 4 5 6 7 8 9 10 11
{{!— 1.x version —}} {{#each post in posts}} {{!— post
references the current iteration —}} {{/each}} {{#each posts}} {{!— the outer context is no longer accessible —}} {{/each}}
// v2.x
1 {{!— 2.x version —}} 2 {{#each posts as |post|}} 3 {{!—post
references 4 the current iteration —}} 5 <p>{{post.id}} {{post.title}}</p> 6 {{/each}} 7
6
Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} Better template scoping “Data Down, Actions Up” approach 6
Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} Better template scoping “Data Down, Actions Up” approach Roadmap for a lot of further improvements: HTML syntax for component invocation Routes to drive Components approach 6
Ember 3.x (Road to Octane) 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes Glimmer Components 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes Glimmer Components Angle Brackets Invocation 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes Glimmer Components Angle Brackets Invocation @tracked properties 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes Glimmer Components Angle Brackets Invocation @tracked properties Modifiers & Decorators 7
Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes Glimmer Components Angle Brackets Invocation @tracked properties Modifiers & Decorators Lots of documentation 7
Evolution of EmberJS 8
Evolution of EmberJS 1.x 8
Evolution of EmberJS 1.x 2.x 8
Evolution of EmberJS 1.x 2.x 3.x 8
Evolution of EmberJS 1.x 2.x 3.x Octane 8
9
Native Classes 10
Native Classes ES6 Class syntax 10
Native Classes ES6 Class syntax Increased performance 10
Native Classes ES6 Class syntax Increased performance Smooth learning curve 10
Native Classes ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript community 10
Native Classes ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript community Ability to share code more easily 10
Native Classes ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript community Ability to share code more easily No more .get
’s
10
Native Classes ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript community Ability to share code more easily No more .get
’s Cleaner and easier to read
10
Native Classes // Classic Ember Object Syntax
ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import EmberObject, { computed } from ‘@ember/object’; const Person = EmberObject.extend({ init(props) { this._super(props); console.log(Created ${this.get('fullName')}...
); }, fullName: computed(‘firstName’, ‘lastName’, function() { return ${this.get('firstName')} ${this.get('lastName')}
; }) }); let phoenix = Person.create({ firstName: ‘Jean’, lastName: ‘Gray’ });
community Ability to share code more easily No more .get
’s Cleaner and easier to read
// Native Class Syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { computed } from ‘@ember/object’ class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; console.log(Created ${this.fullName}...
); } @computed(‘firstName’, ‘lastName’) get fullName() { return ${this.firstName} ${this.lastName}
; } } let phoenix = new Person(‘Jean’, ‘Gray’);
10
Native Classes // Classic Ember Object Syntax
ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import EmberObject, { computed } from ‘@ember/object’; const Person = EmberObject.extend({ init(props) { this._super(props); console.log(Created ${this.get('fullName')}...
); }, fullName: computed(‘firstName’, ‘lastName’, function() { return ${this.get('firstName')} ${this.get('lastName')}
; }) }); let phoenix = Person.create({ firstName: ‘Jean’, lastName: ‘Gray’ });
community Ability to share code more easily No more .get
’s Cleaner and easier to read
// Native Class Syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { computed } from ‘@ember/object’ class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; console.log(Created ${this.fullName}...
); } @computed(‘firstName’, ‘lastName’) get fullName() { return ${this.firstName} ${this.lastName}
; } } let phoenix = new Person(‘Jean’, ‘Gray’);
10
Native Classes // Classic Ember Object Syntax
ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import EmberObject, { computed } from ‘@ember/object’; const Person = EmberObject.extend({ init(props) { this._super(props); console.log(Created ${this.get('fullName')}...
); }, fullName: computed(‘firstName’, ‘lastName’, function() { return ${this.get('firstName')} ${this.get('lastName')}
; }) }); let phoenix = Person.create({ firstName: ‘Jean’, lastName: ‘Gray’ });
community Ability to share code more easily No more .get
’s Cleaner and easier to read
// Native Class Syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { computed } from ‘@ember/object’ class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; console.log(Created ${this.fullName}...
); } @computed(‘firstName’, ‘lastName’) get fullName() { return ${this.firstName} ${this.lastName}
; } } let phoenix = new Person(‘Jean’, ‘Gray’);
10
Native Classes // Classic Ember Object Syntax
ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import EmberObject, { computed } from ‘@ember/object’; const Person = EmberObject.extend({ init(props) { this._super(props); console.log(Created ${this.get('fullName')}...
); }, fullName: computed(‘firstName’, ‘lastName’, function() { return ${this.get('firstName')} ${this.get('lastName')}
; }) }); let phoenix = Person.create({ firstName: ‘Jean’, lastName: ‘Gray’ });
community Ability to share code more easily No more .get
’s Cleaner and easier to read
// Native Class Syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { computed } from ‘@ember/object’ class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; console.log(Created ${this.fullName}...
); } @computed(‘firstName’, ‘lastName’) get fullName() { return ${this.firstName} ${this.lastName}
; } } let phoenix = new Person(‘Jean’, ‘Gray’);
10
Glimmer Components 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand Fewer hooks and properties // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand Fewer hooks and properties No more implicit wrappers // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand 1 {{noOfGuests}} Fewer hooks and properties No more implicit wrappers // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand 1 {{noOfGuests}} Fewer hooks and properties No more implicit wrappers // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 1 <label> {{this.noOfGuests}} </label> 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand 1 {{noOfGuests}} Fewer hooks and properties No more implicit wrappers Namespaced arguments // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 1 <label> {{this.noOfGuests}} </label> 11
Glimmer Components // Classic Component Offers simpler, ergonomic, and declarative approach 1 import Component from ‘@ember/component’; 2 3 export default Component.extend({ 4 5 tagName: ‘label’, 6 7 noOfGuests: 5, 8 9 isMaxGuestExceeded: computed(‘noOfGuests’) { 10 return this.noOfGuests > this.maxGuests; 11 } 12 }); Easier to understand 1 {{noOfGuests}} Fewer hooks and properties No more implicit wrappers Namespaced arguments // Glimmer Component 1 2 3 4 5 6 7 8 9 10 11 12 import Component from “@glimmer/component”; import { tracked } from “@glimmer/tracking”; export default class AddRsvp extends Component { @tracked noOfGuests = 5; get isMaxGuestExceeded() { return this.noOfGuests > this.args.maxGuests; } } 1 <label> {{this.noOfGuests}} </label> 11
Templating in Octane 12
Templating in Octane Angle Brackets syntax In line with the HTML standards Capital Case Syntax Easy to distinguish from helpers, // Classic templating syntax 1 {{employee-details 2 name=employeeName 3 empId=employeeId 4 addEmployee=(action ‘addEmployee’) 5 }} 6 properties etc. // Octane templating syntax 1 <EmployeeDetails 2 @name={{this.employeeName}} 3 @empId={{@employeeId}} 4 @addEmployee={{this.addEmployee}} 5 /> 12
Templating in Octane Angle Brackets syntax In line with the HTML standards Capital Case Syntax Easy to distinguish from helpers,
// Classic templating syntax
1 {{employee-details 2 name=employeeName 3 empId=employeeId 4 addEmployee=(action ‘addEmployee’) 5 }} 6
properties etc. Named arguments Denoted with @
symbol Easy differentiation from local and external properties
// Octane templating syntax
1 <EmployeeDetails 2 @name={{this.employeeName}} 3 @empId={{@employeeId}} 4 @addEmployee={{this.addEmployee}} 5 />
12
Templating in Octane Angle Brackets syntax In line with the HTML standards Capital Case Syntax Easy to distinguish from helpers,
// Classic templating syntax
1 {{employee-details 2 name=employeeName 3 empId=employeeId 4 addEmployee=(action ‘addEmployee’) 5 }} 6
properties etc. Named arguments Denoted with @
symbol Easy differentiation from local and external properties
// Octane templating syntax
1 <EmployeeDetails 2 @name={{this.employeeName}} 3 @empId={{@employeeId}} 4 @addEmployee={{this.addEmployee}} 5 />
Required this
Provides clear point-of-origin Easy to read and understand
12
Templating in Octane Angle Brackets syntax In line with the HTML standards Capital Case Syntax Easy to distinguish from helpers,
// Classic templating syntax
1 {{employee-details 2 name=employeeName 3 empId=employeeId 4 addEmployee=(action ‘addEmployee’) 5 }} 6
properties etc. Named arguments Denoted with @
symbol Easy differentiation from local and external properties
// Octane templating syntax
1 <EmployeeDetails 2 @name={{this.employeeName}} 3 @empId={{@employeeId}} 4 @addEmployee={{this.addEmployee}} 5 />
Required this
Provides clear point-of-origin Easy to read and understand
12
Tracked Properties // Classic syntax
@tracked syntax Explicit declarations Cleaner code Reduces complexity No More .set
’s
1 import EmberObject, { computed } from ‘@ember/object’; 2 3 const Person = EmberObject.extend({ 4 firstName: ‘Tom’, 5 lastName: ‘Dale’, 6 count: 0, 7 8 fullName: computed(‘firstName’, ‘lastName’, function() { 9 this.set(‘count’, this.get(‘count’) + 1); 10 return ${this.firstName} ${this.lastName}
; 11 }), 12 });
// @tracked syntax 1 import { tracked } from ‘@glimmer/tracking’; 2 3 class Person { 4 @tracked firstName = ‘Tom’; 5 @tracked lastName = ‘Dale’; 6 @tracked count = 0; 7 8 get fullName() { 9 this.count = this.count + 1; 10 return ${this.firstName} ${this.lastName}
; 11 } 12 }
13
Tracked Properties // Classic syntax
@tracked syntax Explicit declarations Cleaner code Reduces complexity No More .set
’s
1 import EmberObject, { computed } from ‘@ember/object’; 2 3 const Person = EmberObject.extend({ 4 firstName: ‘Tom’, 5 lastName: ‘Dale’, 6 count: 0, 7 8 fullName: computed(‘firstName’, ‘lastName’, function() { 9 this.set(‘count’, this.get(‘count’) + 1); 10 return ${this.firstName} ${this.lastName}
; 11 }), 12 });
// @tracked syntax 1 import { tracked } from ‘@glimmer/tracking’; 2 3 class Person { 4 @tracked firstName = ‘Tom’; 5 @tracked lastName = ‘Dale’; 6 @tracked count = 0; 7 8 get fullName() { 9 this.count = this.count + 1; 10 return ${this.firstName} ${this.lastName}
; 11 } 12 }
13
Tracked Properties // Classic syntax
@tracked syntax Explicit declarations Cleaner code Reduces complexity No More .set
’s
1 import EmberObject, { computed } from ‘@ember/object’; 2 3 const Person = EmberObject.extend({ 4 firstName: ‘Tom’, 5 lastName: ‘Dale’, 6 count: 0, 7 8 fullName: computed(‘firstName’, ‘lastName’, function() { 9 this.set(‘count’, this.get(‘count’) + 1); 10 return ${this.firstName} ${this.lastName}
; 11 }), 12 });
// @tracked syntax 1 import { tracked } from ‘@glimmer/tracking’; 2 3 class Person { 4 @tracked firstName = ‘Tom’; 5 @tracked lastName = ‘Dale’; 6 @tracked count = 0; 7 8 get fullName() { 9 this.count = this.count + 1; 10 return ${this.firstName} ${this.lastName}
; 11 } 12 }
13
Tracked Properties // Classic syntax
@tracked syntax Explicit declarations Cleaner code Reduces complexity No More .set
’s
1 import EmberObject, { computed } from ‘@ember/object’; 2 3 const Person = EmberObject.extend({ 4 firstName: ‘Tom’, 5 lastName: ‘Dale’, 6 count: 0, 7 8 fullName: computed(‘firstName’, ‘lastName’, function() { 9 this.set(‘count’, this.get(‘count’) + 1); 10 return ${this.firstName} ${this.lastName}
; 11 }), 12 });
// @tracked syntax 1 import { tracked } from ‘@glimmer/tracking’; 2 3 class Person { 4 @tracked firstName = ‘Tom’; 5 @tracked lastName = ‘Dale’; 6 @tracked count = 0; 7 8 get fullName() { 9 this.count = this.count + 1; 10 return ${this.firstName} ${this.lastName}
; 11 } 12 }
13
Tracked Properties // Classic syntax
@tracked syntax Explicit declarations Cleaner code Reduces complexity No More .set
’s
1 import EmberObject, { computed } from ‘@ember/object’; 2 3 const Person = EmberObject.extend({ 4 firstName: ‘Tom’, 5 lastName: ‘Dale’, 6 count: 0, 7 8 fullName: computed(‘firstName’, ‘lastName’, function() { 9 this.set(‘count’, this.get(‘count’) + 1); 10 return ${this.firstName} ${this.lastName}
; 11 }), 12 });
// @tracked syntax 1 import { tracked } from ‘@glimmer/tracking’; 2 3 class Person { 4 @tracked firstName = ‘Tom’; 5 @tracked lastName = ‘Dale’; 6 @tracked count = 0; 7 8 get fullName() { 9 this.count = this.count + 1; 10 return ${this.firstName} ${this.lastName}
; 11 } 12 }
13
Tracked Properties // Classic syntax
@tracked syntax Explicit declarations Cleaner code Reduces complexity No More .set
’s
1 import EmberObject, { computed } from ‘@ember/object’; 2 3 const Person = EmberObject.extend({ 4 firstName: ‘Tom’, 5 lastName: ‘Dale’, 6 count: 0, 7 8 fullName: computed(‘firstName’, ‘lastName’, function() { 9 this.set(‘count’, this.get(‘count’) + 1); 10 return ${this.firstName} ${this.lastName}
; 11 }), 12 });
// @tracked syntax 1 import { tracked } from ‘@glimmer/tracking’; 2 3 class Person { 4 @tracked firstName = ‘Tom’; 5 @tracked lastName = ‘Dale’; 6 @tracked count = 0; 7 8 get fullName() { 9 this.count = this.count + 1; 10 return ${this.firstName} ${this.lastName}
; 11 } 12 }
13
Modifiers and Decorators 14
Modifiers and Decorators Modifiers: Functions or classes used directly in templates 1 <span>{{this.formattedCount}}</span> 2 3 <button {{on “click” this.increment}}>Increment</button> 4 5 <button {{on “click” this.decrement}}>Decrement</button> 6 Applied directly to elements Allows targeting specific elements more easily Easy to reuse 14
Modifiers and Decorators Modifiers: Functions or classes used directly in templates 1 <span>{{this.formattedCount}}</span> 2 3 <button {{on “click” this.increment}}>Increment</button> 4 5 <button {{on “click” this.decrement}}>Decrement</button> 6 Applied directly to elements Allows targeting specific elements more easily Easy to reuse Decorators: Abstracts functionality Improved Developer experience 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class Counter extends Component { @tracked count = 0; get formattedCount() { return this.count.toString().padStart(3, ‘0’); } @action increment() { this.count++ } @action decrement() { this.count-} } 14
Modifiers and Decorators Modifiers: Functions or classes used directly in templates 1 <span>{{this.formattedCount}}</span> 2 3 <button {{on “click” this.increment}}>Increment</button> 4 5 <button {{on “click” this.decrement}}>Decrement</button> 6 Applied directly to elements Allows targeting specific elements more easily Easy to reuse Decorators: Abstracts functionality Improved Developer experience 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class Counter extends Component { @tracked count = 0; get formattedCount() { return this.count.toString().padStart(3, ‘0’); } @action increment() { this.count++ } @action decrement() { this.count-} } 14
15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} {{yield label}} 2 3 {{else}} {{label}} 4 5 {{/if}} 6 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} {{yield label}} 2 3 {{else}} {{label}} 4 5 {{/if}} 6 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} {{yield label}} 2 3 {{else}} {{label}} 4 5 {{/if}} 6 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
1 {{x-toggle-label 2 label=labelVal 3 value=false 4 sendToggle=(action ‘sendToggle’) 5 }} // Classic Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import import import import { readOnly } from ‘@ember/object/computed’; Component from ‘@ember/component’; { computed } from ‘@ember/object’; layout from ‘./template’; export default Component.extend({ layout, tagName: ‘label’, attributeBindings: [‘for’], classNames: [‘toggle-text’, ‘toggle-prefix’], classNameBindings: [‘labelType’], for: readOnly(‘switchId’), isVisible: readOnly(‘show’), isAwesome: true, labelValue: computed(‘isAwesome’, function() { return this.get(‘isAwesome’) ? ‘awesome-label’ : ‘lesser-awesome-label’; }), type: computed(‘value’, { get() { return this.get(‘value’) ? ‘on’ : ‘off’; } }), click(e) { e.stopPropagation(); e.preventDefault(); this.sendToggle(this.get(‘value’)); } }); 1 {{#if hasBlock}} 2 {{yield label}} 3 {{else}} 4 {{label}} 5 {{/if}} 6 1 <ToggleLabel 2 @label={{this.labelVal}} 3 @value=false 4 sendToggle={{this.sendToggle}} 5 /> // Octane Component syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Component from ‘@glimmer/component’; import { tracked } from ‘@glimmer/tracking’; import { action } from ‘@ember/object’; export default class ToggleLabel extends Component { @tracked isAwesome = true; get type() { return this.args.value ? ‘on’ : ‘off’; } get labelValue() { return this.isAwesome ? ‘awesome-label’ : ‘lesser-awesome-label’; } @action handleClick(e) { e.stopPropagation(); e.preventDefault(); this.args.sendToggle(this.args.value); } } 1 <label 2 for=”{{@switchId}}” 3 {{on ‘click’ this.handleClick}} 4 5 class=” 6 toggle-text 7 toggle-prefix 8 {{this.type}}-label 9 {{if @show ‘is-visible’ ‘is-hidden’}} 10 ” 11 > 12 {{#if @hasBlock}} 13 {{yield @label}} 14 {{else}} 15 {{@label}} 16 {{/if}} 17 </label> 18 15
Tools & Goodness 16
Tools & Goodness Ember Codemods: https://github.com/ember-codemods Ember Atlas: https://www.notion.so/The-Ember-Atlas4094f81c86c34badb4a562ed29414ae1 The Octane Edition of Ember: https://emberjs.com/editions/octane/ Ember Blog - Octane is Here: https://blog.emberjs.com/2019/12/20/octane-is-here.html 16
Tools & Goodness Ember Codemods: https://github.com/ember-codemods Ember Atlas: https://www.notion.so/The-Ember-Atlas4094f81c86c34badb4a562ed29414ae1 The Octane Edition of Ember: https://emberjs.com/editions/octane/ Ember Blog - Octane is Here: https://blog.emberjs.com/2019/12/20/octane-is-here.html 16
Thank you! 17