Suchita Doshi

A paradigm shift

 suchitadoshi

 suchitadoshi1987

 suchita009

 suchita009

When I am away from my computer...

Agenda

Journey Of EmberJS

  • Ember 1.x
  • Ember 2.x
  • Ember 3.x

Ember Octane

  • Native Classes
  • Glimmer Components
  • Templates in Octane
  • Tracked Properties
  • Modifiers & Decorators

Classic vs Octane Epic comparison

Migration tools & References

Of

The

JOURNEY

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}}
export default Ember.View.extend({
  classNameBindings: ['isActive'],
  isActive: true,
  firstName: 'John',
  lastName: 'Doe',
  
  // Computed Property
  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName'),
  
  fullNameChanged: function() {
    // deal with the change
  }.observes('fullName')
});
<div {{bind-attr title=post.popup}}></div>

Route

Model

Controller

+

Templates

Model

View

+

Templates

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
export default Ember.Component.extend({
  classNameBindings: ['isActive'],
  isActive: true,
  firstName: 'John',
  lastName: 'Doe',
  
  // Computed Property
  fullName: Ember.computed('firstName', 'lastName', function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }),
  
  fullNameChanged: Ember.observer('fullName', function() {
    // deal with the change
  })
});

Route

Controller

+

Templates

Component

+

Templates

Model

Model

// v2.x
<div title="{{post.popup}}"></div>
<div {{bind-attr title=post.popup}}></div>
// v1.x
// v1.x
{{!-- 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
{{!-- 2.x version --}}  
{{#each posts as |post|}}
  {{!--`post` references
  the current iteration --}}
  <p>{{post.id}} {{post.title}}</p>
{{/each}}

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

Evolution of EmberJS

Octane

2.x

1.x

3.x

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

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');
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' });
// Classic Ember Object Syntax
// Native Class Syntax
  • Offers simpler, ergonomic, and declarative approach

  • Easier to understand

  • Fewer hooks and properties

  • No more implicit wrappers

  • Namespaced arguments

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;
  }

}

Glimmer Components

import Component from '@ember/component';

export default Component.extend({
 
  tagName: 'label',
  
  noOfGuests: 5,

  isMaxGuestExceeded: computed('noOfGuests') {
    return this.noOfGuests > this.maxGuests;
  }
});
{{noOfGuests}}
<label> {{this.noOfGuests}} </label>
// Classic Component
// Glimmer Component

Templating in Octane

  • Angle Brackets syntax

    • In line with the HTML standards

    • Capital Case Syntax

    • Easy to distinguish from helpers, properties etc.

  • Named arguments

    • ​Denoted with `@` symbol

    • Easy differentiation from local and external properties

  • Required `this`

    • Provides clear point-of-origin

    • Easy to read and understand

{{employee-details
  name=employeeName
  empId=employeeId
  addEmployee=(action 'addEmployee')
}}
<EmployeeDetails
  @name={{this.employeeName}}
  @empId={{@employeeId}}
  @addEmployee={{this.addEmployee}}
/>
// Classic templating syntax
// Octane templating syntax

Tracked Properties

  • @tracked syntax

  • Explicit declarations

  • Cleaner code

  • Reduces complexity

  • No More `.set`'s

import EmberObject, { computed } from '@ember/object';

const Person = EmberObject.extend({
  firstName: 'Tom',
  lastName: 'Dale',
  count: 0,

  fullName: computed('firstName', 'lastName', function() {
    this.set('count', this.get('count') + 1);
    return `${this.firstName} ${this.lastName}`;
  }),
});
import { tracked } from '@glimmer/tracking';

class Person {
  @tracked firstName = 'Tom';
  @tracked lastName = 'Dale';
  @tracked count = 0;

  get fullName() {
    this.count = this.count + 1;
    return `${this.firstName} ${this.lastName}`;
  }
}
// Classic syntax
// @tracked syntax

Modifiers and Decorators

  • Modifiers:

    • Functions or classes used directly in templates

    • Applied directly to elements

    • Allows targeting specific elements more easily

    • Easy to reuse

  • Decorators:

    • Abstracts functionality

    • Improved Developer experience

<span>{{this.formattedCount}}</span>

<button {{on "click" this.increment}}>Increment</button>

<button {{on "click" this.decrement}}>Decrement</button>
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--
  }
}
import { readOnly } from '@ember/object/computed';
import Component from '@ember/component';
import { computed } from '@ember/object';
import 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'));
  }
});
{{#if hasBlock}}
  {{yield label}}
{{else}}
  {{label}}
{{/if}}
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);
  }
}

<label
  for="{{@switchId}}"
  {{on 'click' this.handleClick}}

  class="
    toggle-text
    toggle-prefix
    {{this.type}}-label
    {{if @show 'is-visible' 'is-hidden'}}
  "
  >
  {{#if @hasBlock}}
    {{yield @label}}
  {{else}}
    {{@label}}
  {{/if}}
</label>
// Classic Component syntax
// Octane Component syntax
{{x-toggle-label
    label=labelVal
    value=false
    sendToggle=(action 'sendToggle')
}}
<ToggleLabel
    @label={{this.labelVal}}
    @value=false
    sendToggle={{this.sendToggle}}
/>

Tools & Goodness

Thank you!

Made with Slides.com