Octane - A Paradigm Shift in EmberJS

A presentation at EmberConf 2020 in March 2020 in by Suchita Doshi

Slide 1

Slide 1

A paradigm shift Suchita Doshi suchitadoshi suchitadoshi1987 suchita009 suchita009 1

Slide 2

Slide 2

When I am away from my computer… 2

Slide 3

Slide 3

When I am away from my computer… 2

Slide 4

Slide 4

When I am away from my computer… 2

Slide 5

Slide 5

When I am away from my computer… 2

Slide 6

Slide 6

When I am away from my computer… 2

Slide 7

Slide 7

Agenda 3

Slide 8

Slide 8

Agenda Journey Of EmberJS Ember 1.x Ember 2.x Ember 3.x 3

Slide 9

Slide 9

Agenda Journey Of EmberJS Ember Octane Native Classes Glimmer Components Templates in Octane Tracked Properties Modifiers & Decorators 3

Slide 10

Slide 10

Agenda Journey Of EmberJS Ember Octane Classic vs Octane Epic comparison 3

Slide 11

Slide 11

Agenda Journey Of EmberJS Ember Octane Classic vs Octane Epic comparison Migration tools & References 3

Slide 12

Slide 12

The JOURNEY Of 4

Slide 13

Slide 13

Ember 1.x 5

Slide 14

Slide 14

Ember 1.x Convention over configuration (A new mental model) 5

Slide 15

Slide 15

Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability 5

Slide 16

Slide 16

Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data 5

Slide 17

Slide 17

Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data View Driven architecture 5

Slide 18

Slide 18

Ember 1.x Convention over configuration (A new mental View model) Controller Route Model

  • Templates Built-in Routing capability Model
  • Templates 1 export default Ember.View.extend({ 2 classNameBindings: [‘isActive’], 3 isActive: true, 4 firstName: ‘John’, 5 lastName: ‘Doe’, 6 // Computed Property 7 fullName: function() { 8 return this.get(‘firstName’) + ’ ’ + this.get(‘lastName’); 9 }.property(‘firstName’, ‘lastName’), 10 11 fullNameChanged: function() { 12 // deal with the change 13 }.observes(‘fullName’) 14 15 }); Ember-data View Driven architecture 5

Slide 19

Slide 19

Ember 1.x Convention over configuration (A new mental model) Built-in Routing capability Ember-data View Driven architecture Two way data bindings 5

Slide 20

Slide 20

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

Slide 21

Slide 21

Ember 2.x 6

Slide 22

Slide 22

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

Slide 23

Slide 23

Ember 2.x Component driven “Glimmer rendering engine” adoption 6

Slide 24

Slide 24

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

Slide 25

Slide 25

Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} Better template scoping 6

Slide 26

Slide 26

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

Slide 27

Slide 27

Ember 2.x Component driven “Glimmer rendering engine” adoption Better binding with properties {{bind-attr}} Better template scoping “Data Down, Actions Up” approach 6

Slide 28

Slide 28

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

Slide 29

Slide 29

Ember 3.x (Road to Octane) 7

Slide 30

Slide 30

Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup 7

Slide 31

Slide 31

Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. 7

Slide 32

Slide 32

Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support 7

Slide 33

Slide 33

Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes 7

Slide 34

Slide 34

Ember 3.x (Road to Octane) Cleanup Cleanup Cleanup Remove support for IE9, IE10 and PhantomJS. Remove bower support Native Classes Glimmer Components 7

Slide 35

Slide 35

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

Slide 36

Slide 36

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

Slide 37

Slide 37

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

Slide 38

Slide 38

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

Slide 39

Slide 39

Evolution of EmberJS 8

Slide 40

Slide 40

Evolution of EmberJS 1.x 8

Slide 41

Slide 41

Evolution of EmberJS 1.x 2.x 8

Slide 42

Slide 42

Evolution of EmberJS 1.x 2.x 3.x 8

Slide 43

Slide 43

Evolution of EmberJS 1.x 2.x 3.x Octane 8

Slide 44

Slide 44

9

Slide 45

Slide 45

Native Classes 10

Slide 46

Slide 46

Native Classes ES6 Class syntax 10

Slide 47

Slide 47

Native Classes ES6 Class syntax Increased performance 10

Slide 48

Slide 48

Native Classes ES6 Class syntax Increased performance Smooth learning curve 10

Slide 49

Slide 49

Native Classes ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript community 10

Slide 50

Slide 50

Native Classes ES6 Class syntax Increased performance Smooth learning curve More aligned Javascript community Ability to share code more easily 10

Slide 51

Slide 51

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

Slide 52

Slide 52

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

Slide 53

Slide 53

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

Slide 54

Slide 54

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

Slide 55

Slide 55

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

Slide 56

Slide 56

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

Slide 57

Slide 57

Glimmer Components 11

Slide 58

Slide 58

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

Slide 59

Slide 59

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

Slide 60

Slide 60

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

Slide 61

Slide 61

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

Slide 62

Slide 62

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

Slide 63

Slide 63

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

Slide 64

Slide 64

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

Slide 65

Slide 65

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

Slide 66

Slide 66

Templating in Octane 12

Slide 67

Slide 67

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

Slide 68

Slide 68

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

Slide 69

Slide 69

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

Slide 70

Slide 70

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

Slide 71

Slide 71

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

Slide 72

Slide 72

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

Slide 73

Slide 73

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

Slide 74

Slide 74

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

Slide 75

Slide 75

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

Slide 76

Slide 76

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

Slide 77

Slide 77

Modifiers and Decorators 14

Slide 78

Slide 78

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

Slide 79

Slide 79

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

Slide 80

Slide 80

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

Slide 81

Slide 81

15

Slide 82

Slide 82

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

Slide 83

Slide 83

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

Slide 84

Slide 84

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

Slide 85

Slide 85

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

Slide 86

Slide 86

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

Slide 87

Slide 87

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

Slide 88

Slide 88

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

Slide 89

Slide 89

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

Slide 90

Slide 90

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

Slide 91

Slide 91

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

Slide 92

Slide 92

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

Slide 93

Slide 93

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

Slide 94

Slide 94

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

Slide 95

Slide 95

Tools & Goodness 16

Slide 96

Slide 96

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

Slide 97

Slide 97

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

Slide 98

Slide 98

Thank you! 17