Understanding Ember Objects, computed properties and observers - Ember.js Tutorial part 4

Sat Dec 10 2016

Paul Shan

I’m using Ember 2.10.0 here

So we are in the part 4 of out Ember.js tutorial series. By now you would have already know how to install & scaffold ember and also about ember template syntaxes. In this part we will discuss about ember objects and why instead of POJO you need to use Ember.Object only.

Objects are very important in ember. Every element in ember is extended from Ember.Object; be it a controller, model, router or component. Ember also provides ways to write constructors and mixins like normal JavaScript objects.

Class, Object, inheritance

While creating a class, you must inherit it from the base ember Object class, Ember.Object with the method .extend().

How to define a class

const Student = Ember.Object.extend({
    firstName: null,
    lastName: null,
    getFullName(){
        return `${this.firstName} ${this.lastName}`;
    },
    getStudentType(){
        return "General Student";
    }
});

As you can see, we’ve inherited the class Student from Ember.Object. So, the extend() method will be there in Student class as well. So let’s inherit another class PhDStudent from the class Student and try to override the methods.

const PhDStudent = Student.extend({
    title: "Dr.",
    getFullName(){
        let studentFullName = this._super(); //same method of parent class can be called by ._super()
        return `${this.title} ${studentFullName}`;
    },
    getStudentType(){
        return "PhD Student";
    }
});

Instance creation

An instance of any class can be created with the method .create(). Lets code some examples.

let jack = Student.create({
    firstName: "Jack",
    lastName: "Bauer"
});

let david = PhDStudent.create({
    firstName: "David",
    lastName: "Palmer"
});

jack.getFullName(); //Jack Bauer
david.getFullName(); //Dr. David Palmer

jack.getStudentType(); //General Student
david.getStudentType(); //PhD Student

Setters and getters

As I said previously also, that you must obey the Ember way. You must do things the way ember wants you to do. Setters and getters are the perfect example for this.
While retrieving or setting a property of an object you need to use .get() an .set().

jack.get('firstName'); //Jack
jack.set('firstName', 'Jacky');
jack.get('firstName'); //Jacky

If you don’t use setters and getters and try to set or retrieve the value directly just as normal JavaScript object, the observers and computed properties will not work properly.

Part wise class declaration - .reopen()

You don’t have to declare the entire class at one go. You can use the .reopen() to define the class partially. Below is an example.

const Student = Ember.Object.extend({
    firstName: null,
    lastName: null
});
Student.reopen({
    school: "Void Canvas"
});
let stu = Student.create();
stu.get('school'); //Void Canvas

Likewise you can .reopen() the class multiple times and add/override the properties and methods.

Static properties - .reopenClass()

Method .reopenClass() can be used to add/override static properties or methods to a class. The syntax is pretty similar to .reopen().

const Student = Ember.Object.extend({
});
Student.reopenClass({
    website: "http://voidcanvas.com/"
});
let stu = Student.create();
stu.get('website'); //undefined
Student.website; // http://voidcanvas.com/

As you can see, using .reopenClass() we’ve introduced a static property named website. As this is a static one, so it can not be accessed by the instance stu. However you can get that from the class itself, which is Student. You can also declare static methods using .reopenClass().

.reopen() vs .reopenClass()

The two methods .reopen() and .reopenClass() looks similar, but they do not do similar things.

  • .reopen() is used to add/override properties and methods of any class.
  • .reopenClass() is used to add/override static properties and methods.
  • Properties added using .reopen() can not be accessed by the class name, however the ones added using .reopenClass() can only be accessed using the class.

An example below will clear the confusion.

const Student = Ember.Object.extend({
});
Student.reopenClass({
  isActive: false
});
// override property of Student instance
Student.reopen({
  isActive: true
});

Student.isActive; // false - because it is static property
let stu = Student.create();
stu.get('isActive'); // true

Computed property

Computed properties are special kind of properties, which are dependent on other properties and calculate its value. Let me show you an example with our Student class.

const Student = Ember.Object.extend({
    firstName: null,
    lastName: null,
    fullName: Ember.computed('firstName', 'lastName', function(){
        return `${this.firstName} ${this.lastName}`;
    }),
    init(){
        this._super();
        setTimeout(()=>{
            this.set('firstName', 'David');
        }, 5000);
    }
});

let stu = Student.create({
    firstName: "Paul",
    lastName: "Shan"
});
stu.get('fullName');//Paul Shan
//after 5000 milliseconds 
stu.get('fullName');//David Shan

As you can see, fullName is not a function here. It’s a property. You can .get() the property. Whenever the firstName or lastName property of the Student instance stu will change, the value of fullName will also change.

watching array elements

The method above just observes a property of the ember object. But what if you want to observe each element of an array; or if someone inserted or deleted some elements from an array? Well, for that you can use @each. Below is an example.

const Student = Ember.Object.extend({
    marks: null,
    totalMarks: Ember.computed('[email protected]', function(){
        let total = 0;
        this.get('marks').forEach((mark)=>{
            total+=mark.score;
        });
        return total;
    }),
    init(){
        this._super();
        this.set('marks', [
          Ember.Object.create({subject: "Math", score: 60 }),
          Ember.Object.create({subject: "Chemistry",  score: 70 }),
          Ember.Object.create({subject: "Physics",  score: 80 })
        ]);

    }
});

let stu = Student.create();
stu.get('totalMarks'); //210

setters in computed properties

As computed properties are not normal properties where you can .set() a value; cause it’s a compute function; but there are ways to set the value. Here is an example.

const Student = Ember.Object.extend({
    firstName: null,
    lastName: null,
    fullName: Ember.computed('firstName', 'lastName', {
        get(key) {
          return `${this.get('firstName')} ${this.get('lastName')}`;
        },
        set(key, value) {
            let [firstName, lastName] = value.split(/\s+/);
            this.set('firstName', firstName);
            this.set('lastName',  lastName);
            return value;
        }
    })
});
let stu = Student.create({
    firstName: "Paul",
    lastName: "Shan"
});
stu.get('fullName');//Paul Shan
stu.set('fullName', 'David Palmer');
stu.get('fullName');//David Palmer

As you can see, here the last parameter of Ember.computed is not a function, but an object. This object has two methods get and set. The method get is just like the previous function in Ember.computed which just returns the aggregated value. However the set is a little tricky.
In set, it’s taking two parameters. First one is key which is the name of the computed property itself and the second one is value, which is the new value you want to set. So if we distribute the value properly to the properties on which our computed property is dependent upon, the problem will be solved. And that’s what we just did. We split the value and created two variables firstName and lastName and set them in the Ember Object’s firstName and lastName property.

Macro in computed properties

There are so many common scenarios of computed properties in the world; considering whom, ember gave you some predefined macro. Below is one example of how a macro works, with the macro match which helps matching regx.

const Student = Ember.Object.extend({
    email: null,
    hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
});

let stu = Student.create();
stu.get('hasValidEmail'); //false
stu.set('email','abcde');
stu.get('hasValidEmail'); //false
stu.set('email','[email protected]');
stu.get('hasValidEmail'); //true

The macros are really helpful and reduces the number of lines in your code. The list of the ember macros can be found in api documentation.

Observers

As you already know about computed properties, it won’t be harder for you to understand observers. As the name suggests, the observers are there to observe the change of value in any property, including the computed properties.
The difference between computed property and observer is, you can not .get() an observer. It’s just a function to be executed whenever any of the property it’s observing changes. And computed property is a property and which will only recalculate if anywhere in your application that computed property is being .get().

const Student = Ember.Object.extend({
    firstName: null,
    lastName: null,
    onFirstNameChange: Ember.observer('firstName', function(){
        console.log("first name changed!");
    }),
    init(){
        this._super();
        setTimeout(()=>{
            this.set('firstName', 'David');
        }, 5000);
    }
});

let stu = Student.create({
    firstName: "Paul",
    lastName: "Shan"
});
//after 5000 milliseconds the console will display
//first name changed!

Remember observer are heavy weighted elements; thus may cause performance issue if used excessively. Generally most of the problems can be solved using computed properties.

Enumerable and Array

In ember an enumerable is an object which can contain multiple child objects in it. The most used enumerable is Array.
As ember means data binding and it needs to observe whenever a data is changed, it always forces you to go with the ember way. Usage of .set() and .get() are the example of it. Now, likewise in case of Array enumerable, you need to use .pushObject() and .popObject() instead of the default .push() and .pop(). There are alternative for other methods too.

Standard Method Ember Method
push pushObject
pop popObject
reverse reverseObjects
shift shiftObject
unshift unshiftObject

You can also get the first and the last object of an array with .get('firstObject') and .get('lastObject').

Few examples are as below.

let students = [
    Student.create({
        firstName: "Paul",
        lastName: "Shan"
    }),
    Student.create({
        firstName: "Jack",
        lastName: "Bauer"
    }),
    Student.create({
        firstName: "Sandip",
        lastName: "Roy"
    }),
    Student.create({
        firstName: "David",
        lastName: "Palmer"
    })
];
students.get('firstObject').get('firstName'); //Paul
students.get('lastObject').get('firstName'); //David
students.pushObject(Student.create({
    firstName: "Rajinikanth"
}));
students.get('lastObject').get('firstName'); //Rajinikanth

You can check all Enumerable related apis in ember’s api documentation

Next Part

Now you are well aware of ember objects and how they work. If you went through the api links we shared inside the article, you are probably aware of all syntaxes related to Objects. In the next part of this Ember.js full tutorial series, we will talk about Routers.

SHARE THIS ARTICLE

post-thumbnail
Today everyone knows the importance of a lightning-fast website and how the speed impacts the conversion rate of a business. Today, everyone wants the site to be a PWA so that the mobile users can have an app-like experience with the website because, for the majority of the merchants, the customers come through mobile devices.
Tue Apr 20 2021
post-thumbnail
Here we are going to see how you can manage backup and restore of Postgres database with docker.
Thu Sep 03 2020
post-thumbnail
Image sliders or carousels always have increased the UI attraction of websites and they are pretty useful for reflecting the major roles/products too. In case, I am having a website that sells tee-shirts,
Mon Apr 30 2018

About VoidCanvas

This blog was created out of hobby and talks mostly about technology, web development, JavaScript, NodeJS and related topics. Thank you for reading my blog.

Copyright 2022 - www.voidcanvas.com

Popular Articles

Authentication using Google's oAuth api with node.js

Thu Mar 10 2016

OAuth authentications are pretty popular now a days and another thing which is popular is JavaScript. This article shows how to plugin google’s oAuth api for authentication in your own node application.

CSS3 Loader Snippet Collection: (Part 2 - Squares)

Sat Mar 01 2014

This is a continuation of my CSS3 loader snippet collection series. I've provided spinning css3 animation loader in the part 1 of this series and here in part 2, I'm providing various square type loading