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!

Ember Octane - A Paradigm Shift

By Suchita Doshi

Ember Octane - A Paradigm Shift

Ember JS is a great choice when it comes to building web apps of any size. However, it does have a reputation for having a steep learning curve along with other issues like the size of the framework, not being productive out of the box, etc. Ember JS's newest edition Ember Octane addresses most of such concerns & rescues the troubled developers. Curious about what features Ember Octane offers? and why is it so different from the previous versions of Ember? This session would cover some important aspects of Ember Octane & an overview of Ember's transformation from its previous versions to Octane.

  • 1,972