Chapter 2. Understanding Ember.js Object-oriented Patterns

JavaScript is a multi paradigm dynamic language that supports all of the object-oriented, procedural, and functional styles of writing code. This makes it one of the most powerful, as well as the most misunderstood language of all times.

Often one aims to write code that is modular, extensible, and testable. But not everyone is able to write good quality code because it requires deep understanding of the language, design principles, as well as discipline to write code that consistently follows a particular school of thought.

Ember.js framework helps in creating applications which are highly modular, extensible, and can be tested easily, and the Ember object model lies in the heart this framework.

In this chapter, we shall cover the following topics:

  • Ember.js object model
  • Reuse functions via mixins
  • Bindings
  • Computed properties
  • Observers
  • Prototype add on helpers

Ember.js object model

Almost all of the objects in Ember are derived from Ember.Object. This serves as the base object of models, views, controllers, and even the application. Hence, it is also responsible for consistency across the application, adding in similar capabilities to all the objects.

Most of the popular object-oriented languages such as Ruby, Java, C#, and Python follow classical inheritance patterns. These languages have a concept of classes and objects. Classes define the properties and behavior. Objects are instances of these classes. For inheritance to work, classes inherit properties and behavior from other classes.

JavaScript, on the other hand follows a prototypal inheritance pattern instead of the classical inheritance pattern. JavaScript has no concept of classes. It has objects, and objects inherit properties and behavior from other objects. Going forward, this might change with the release of ECMAScript 6 JavaScript standard (http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts). We might see "class" and classical inheritance features built in the language itself.

Ember.js tries to bridge this gap in JavaScript prototypal inheritance, and the classical inheritance by providing a library that emulates classical inheritance. This makes it easy for developers working in languages supporting classical inheritance to pick up Ember.js patterns really quickly and easily.

For example, in JavaScript if you had to implement a simple form of inheritance, you could do the following:

var base = { baseProperty: true }
var derived = Object.create(base)
console.log(derived.baseProperty) // true

In Ember.js, if you had to do the same, that is implement a simple form of inheritance, you would do the following:

var base = Ember.Object.extend({
  baseProperty: true
});

var derived = base.extend({
  derivedProperty:false
});

var derivedObject = derived.create();

console.log(derivedObject.get('baseProperty'));
console.log(derivedObject.get('derivedProperty'));

To run the above example using Ember CLI, let's create a new file inheritance.js inside the project's app directory. In our case, that would be chapter-2/example1/app/inheritance.js.

Now, since the general convention used in Ember CLI is to export your ECMAScript 6 module, and then import the same wherever you want to use it by using the import keyword. Lets first create and export our module, which will contain the preceding JavaScript and Ember.js inheritance code.

import Ember from 'ember';
export default function(){
  // Using Plain JavaScript
  console.log("using plain JavaScript");
  var base = {
    baseProperty: true
  };
  var derived = Object.create(base);
  console.log(derived.baseProperty);

  // Using Ember.js
  console.log("using Ember.js");
  var base = Ember.Object.extend({
    baseProperty: true
  });

  var derived = base.extend({
    derivedProperty:false
  });

  var derivedObject = derived.create();

  console.log(derivedObject.get('baseProperty'));
  console.log(derivedObject.get('derivedProperty'));
}

The contents of inheritance.js are present in chapter-2/example1/app/inheritance.js

You can see that we have wrapped our code inside a JavaScript function that is exported from the file. Since we would want to use the Ember.Object.extend, we import the Ember module using:

import Ember from 'ember'

Now the only part remaining is to include our inheritance module, inside app.js present inside the project's app directory, for the example that would be chapter-2/example1/app/app.js. So let's add the following line inside the app.js file:

import inheritance from './inheritance';

Doing this will import the exported module from app/inheritance.js, and assign the module to the local variable named inheritance.

Now we can just call our exported function using inheritance(), inside app/app.js.

To run the example, navigate to the chapter-2/example1 folder and run the following commands in the same order:

npm install
bower install
ember serve

This should start the development server at http://localhost:4200/. Open the http://localhost:4200/ location in your browser, and you should see Welcome to Ember.js on your screen.

Tip

Once all the dependencies are installed, you can run your development server with only ember serve command. This should work fine unless you change any of your project dependencies.

In order to see the console log, open the development tools of your browsers and open the console to see the logs.

Tip

Please note that we will be using the same pattern described above to create and run the rest of the example in this chapter.

Coming back to the code, let's see what is happening here:

var base = Ember.Object.extend({
  baseProperty: true
});

Here, we defined a variable base that inherits the properties and behavior of Ember.Object, using the extend method present in the Ember.Object class.

The extend method is a way to implement classical inheritance in Ember.js framework. extend behaves like a classical inheritance static method, and exists in the Ember.Object class. This method takes in variable arguments, starting with zero or more mixins, followed by an object containing new properties that we want to define on the object. In the above example, we did not provide any mixins and only provided the new properties we want on the class. We will be discussing more about mixins later, in the Reuse via mixins section of this chapter.

The base object extends the Ember.Object class. Doing this makes base inherit the properties and behavior from Ember.Object. Ember.Object adds in additional capabilities, such as emulating classical inheritance by using the extend method, bindings, observer, computed properties, and much more to the base object.

In a similar fashion, the derived object inherits its properties from the base object. As you can see, we have not defined any method on the base object and still we are able to use base.extend({}). This is because when a method is called on an object, the object looks for its definition in its defining class. If the definition is not found in the class, the object will look for the definition in the superclass, this process continues till a matching definition is found.

In the next step, we create an instance of the derived class using the derived.create() method. The create method is inherited from the Ember.Object class, and it returns a new instance of the class, which is derived in our case.

The create method also takes in an optional argument object containing the properties we want to initialize on the object. It is important to understand the difference between create and extend. The extend method is used to extend the behavior and properties of an existing class to define a new one, it does not create any instances of the definition. The create method, on the other hand, is used to create an instance of a defined class.

Let's see another, a bit more complex example of the above inheritance code. The following example can be found in chapter-2/example2/ directory:

import Ember from 'ember';
export default function(){
  
  var base = Ember.Object.extend({
    baseProperty: true
  });

  var derived = base.extend({});

  var derviedObject = derived.create({
    derivedProperty: true
  });

  console.log(derviedObject.get('baseProperty'));//true
  console.log(derviedObject.get('derivedProperty'));//true

  var anotherDerivedObject = derived.create();
  console.log(anotherDerivedObject.get('derivedProperty'));//undefined
}

The contents of inheritance2.js are present in chapter-2/example2/app/inheritance2.js

As you can see that when we pass in the argument object to the create method, it adds those properties only to the newly instantiated object and not to the class definition:

var derviedObject = derived.create({
  derivedProperty: true
});

The derivedProperty, which we will add on the derivedObject, will be accessible on to only this object and hence, any other instances of type derived will not be able to access the derivedPoperty property, and will return undefined for the same.

In order to add a property that is accessible to all the instances of the class, we need to either define the property, when we define the class, as shown:

var derived = base.extend({
  derivedProperty: true
});

Or you can also use the reopen method, once you have defined the class using the extend method:

import Ember from 'ember';

export default function(){

  var base = Ember.Object.extend({
    baseProperty: true
  });

  var derived = base.extend({
  });

  derived.reopen({
    derivedProperty: true
  });

  var derviedObject = derived.create({
  });

  console.log(derviedObject.get('baseProperty'));//true
  console.log(derviedObject.get('derivedProperty'));//true

  var anotherDerivedObject = derived.create();
  console.log(anotherDerivedObject.get('derivedProperty'));//true
}

The contents of inheritance3.js are present in chapter-2/example3/app/inheritance3.js

The reopen method is particularly useful when you want to modify the class definition after it has been defined. Once the reopen method is called, all new instances of the class will have the additional properties.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.209.131