Jul 8, 2015

Class in ES6, Typescript, Angular2 and Angular1

ES6 class

Class was proposed in ECMAScript 4, which was abandoned, and ES5 does not have Class. Now ES6 is finalized and it has class.

So what is the big deal of it? Should I use it? Will it change the nature of JavaScript? Here is an excerpt from ECMAScript 2015 specification

Although ECMAScript objects are not inherently class-based, it is often convenient to define class-like abstractions based upon a common pattern of constructor functions, prototype objects, and methods. The ECMAScript built-in objects themselves follow such a class-like pattern. Beginning with ECMAScript 2015, the ECMAScript language includes syntactic class definitions that permit programmers to concisely define objects that conform to the same class-like abstraction pattern used by the built-in objects.

It turns out, ES6 class is essentially a constructor function (which should be called with "new" keyword), it still uses prototype inheritance Here is some test case which is used in ES6 compatibility table, which also confirms this.

 class C {}
 return typeof C === "function";

return typeof class C {} === "function";

return typeof class {} === "function";

The question of "Should I use ES6 class or closure function?" is essentially "Should I use constructor/prototype or closure function". If you google constructor/prototype vs closure function, you can find lots of discussion about this, such as Efficiently creating JavaScript objects: closures versus prototypes, Some Javascript constructor patterns, and when to use them and of course Douglas Crockford's Private Members in JavaScript.

Developers of classical language tend to love ES6 class, maybe because class is the familiar way to create object. It is true that ES6 class(prototype) use less memory when you create thousands of instance of one class with shared methods (which can be factored out prototype object), but most of the time, I just need to create one instance adhocly. Also JavaScript runtime like V8 will create hidden class anyway when it finds that two objects has the same shape, and also memory is really cheap nowadays, so memory efficiency is not really important here.

Another advantage of ES6 class is that it simplify inheritance, if you really need to use inheritance as reusability vehicle like EmberJs or BackboneJs, this is really a good reason to use ES6 class. But the bigger problem is that inheritance is really bad idea.

Closure is confusing to classical language developer, but Douglas Crockford claim it is the best idea in the history of programming language. To me, closure is more flexible, readable, powerful and it supports encapsulation and does not require to use the "this" variable. I really like Douglas Crockford's class-free object-oriented programming.

Class in TypeScript and Angular 2.0

Angular team is now building Angular2 with Typescript. In Angular2, we can write application using ES6 class with typescript . If you follow the quick start guide of Angular2, you will find some code like the following.

/// <reference path="typings/angular2/angular2.d.ts" />

import {Component, View, bootstrap} from 'angular2/angular2';

@Component({
    selector: 'my-app'
})
@View({
    template: '<h1>My first Angular 2 App</h1>'
})
class AppComponent {

}
bootstrap(AppComponent);

In Angular1, we use closure to create service, controller, directive. In angular2, angular team seems to use class extensively. Why? In the Keynote on AtScript at ng-europe 2014, Misko (the creator of Angular) described the problem in angular1 is that angular1 API is too complicated to use. They want to have a better abstraction and simpler API. Angular team decided to use lots ES6 features such as class, module. But these features are not enought. So they want to extend the language by creating AtScript. The following is relationship between AtScript, TypeScript, ES6, and ES5. The feature they really want are "Annotation" and "Introspection", these features allow you declaratively add functionality to your class.

Later, Angular team communicate to typescript team their needs in typescript. Since typescript 1.5 start to support class with decorator ( which is essentially the annotation in AtScript), Angular team cancel AtScript and use typescript to write Angular2.

How can class with decorator simplify the Angular2 api? In the above code, the class is annotated with decorator, which is pretty neat. This code is transpiled to the following javascript (ES5)

var __decorate = //omit some implementation

var angular2_1 = require('angular2/angular2');
var AppComponent = (function () {
    function AppComponent() {
    }
    AppComponent = __decorate([
        angular2_1.Component({
            selector: 'my-app'
        }),
        angular2_1.View({
            template: '<h1>My first Angular 2 App</h1>'
        })
    ], AppComponent);
    return AppComponent;
})();
angular2_1.bootstrap(AppComponent);

We can see that the decorator can modify the class definition declaratively. So class with decorator in typescript is good. Actually decorator is already proposed in ES2016, so we may see decorator get standardized very soon.

Class in Angular1

So how about class in Angular1 ? Can we use class to simplify our code or improve performance in Angular1? After "Angular 2: Built on TypeScript" announced, people are thinking of future-proof way to write code with angular1. Obviously Angular2 is using class, a lot, should we use class now in Angular1. From the very beginning, we use closure to create Angular1 component, now some Angular1 projects switch to use class exclusively to replace closure. Suddenly, it seems that closure is obsolete in Angular1, is it?

First, angular1's architecture has not changed and will keep the same in the future, the change will be in Angular2. Angular1 is still using closure internally to create service, directive, controller. If you use class in Angular1, it will not simplify our code, it may even add some complexities to your code. Secondly, angular1 does not use class or class with decorator internally, there is no advantage in that sense.

In the following, I will implement a service, a controller and a directive using class and closure and compare them side by side. And the code is written in typescript, because it support ES6 class and optional type. The source code above can be found https://github.com/fredyang/class-or-closure-angular1, the demo page can be found here.

First let's see take a look on service. If we want to use class to define service in angular1, we should use module.service method, because I want to use the class directly. If we use closure, we should use module.factory.

// common interface of Backend service
// implementing it seems to be good idea, it is used by
//both ClassBackend and closureBackend
interface IBackend {
    login(userName: string, password: string) : ng.IPromise
}

///-------service implemented by class---
class ClassBackend implements IBackend {
    //save $q for reference
    constructor(private $q) {
    }
    login(userName: string, password: string) {
        //access $q via 'this' reference
        return this.$q.when(password === '123');
    }
}

//use module.service method, as it will use 'new' to call class ClassBackend
// which it is essentially a constructor function
angular.module('app').service('classBackend', ClassBackend);


///------service implemented by closure----
angular.module('app').factory('closureBackend', function ($q): IBackend {

    return {
        login: function(userName: string, password: string) {
            //access $q via closure
            return $q.when(password === '123')
        }
    };

});

Now let's implement controller. Here, we can use class to define controller directly, because angular1 treat controller as constructor.

///common interface of controller
//implementing IUser is optional, seems the expression
//in html view is not strong typed yet.
interface IUser {
    isAuthenticated: boolean;
    userName: string;
    password: string;
    login(): ng.IPromise
}

///------controller implemented by class-----------
class ClassController implements IUser{
    constructor(private classBackend:IBackend) {
    }

    isAuthenticated = false;
    userName = 'Fred Yang';
    password = '123';

    login() {
        return this.classBackend.login(this.userName, this.password)
            .then((isAuthenticated) => {
                //need to arrow function to access 'this'
                this.isAuthenticated = isAuthenticated;
                return isAuthenticated;
            });
    }
}

angular.module('app').controller('ClassController', ClassController);

/////---------controller implemented by closure--------------
angular.module('app').controller('ClosureController',
    function (closureBackend:IBackend) : IUser {

    //here I want to avoid to use "this.xxx = yyy"
    // and explicitly return an object instead implicitly return this
    // so that I can use closure
    var rtn = {
        isAuthenticated: false,
        userName: 'Fred Yang',
        password: '123',
        login: function () {
            return closureBackend.login(this.userName, this.password)
                .then(function (isAuthenticated) {
                    //access isAuthenticated via closure variable,
                    //without using arrow function
                    rtn.isAuthenticated = isAuthenticated;
                    return isAuthenticated
                });
        }
    };

    return rtn;
});

Now let's implement directive. I can not use the class directly to define directive here, I still use closure function, inside which I new an instance of the class.

///------------ directive "implemented" by class --------
//implementing ng.IDirective is optional
class CounterWidget implements ng.IDirective {

    constructor(private $timeout) {
        //all the dependencies have to be attached to
        // instance "this"
        //
        // "private" just make the compiler think it is 'private'
        // but it still accessible externally in the generated
        //javascript
    }

    restrict = "EAC";

    template = "<div>counter-widget-class:{{count}}</div>";

    scope = {
        delay: "="
    };

    link = (scope, $elem, attrs) => {
        //access dependencies via "this"
        var $timeout = this.$timeout;
        //
        var delay = scope.delay || 1000;
        scope.count = 1;
        (function repeat() {
            $timeout(function () {
                scope.count++;
                repeat();
            }, delay);
        })();
    }
}


angular.module('app').directive("counterWidgetClass", function ($timeout) {
    //directive is still created using closure under the hood
    return new CounterWidget($timeout);
});


//---- directive implemented by closure ---------
//implementing ng.IDirective is optional
angular.module('app').directive('counterWidgetClosure',
    function ($timeout):ng.IDirective {
        //$timeout is closured and it is accessible to inner function
        return {
            restrict: "EAC",

            template: "<div>counter-widget-closure:{{count}}</div>",

            scope: {
                delay: "="
            },

            link: (scope:any, $elem, attrs) => {
                //
                var delay = scope.delay || 1000;
                scope.count = 1;
                (function repeat() {
                    $timeout(function () {
                        scope.count++;
                        repeat();
                    }, delay);
                })();
            }
        };
    });

From the above samples, I find that

  • The implementation in class is more complicated, verbose, rigid, it does not really encapsulate private data, but the syntax may be more friendly to java or c# developer.
  • The implementation in class is more simple, terse, flexible, and it can encapsulate private data, but it feels wired to classical developer.

Because angular2 use lots class, it does not mean that you have to use class exclusively in Angular1. Using class will not put you better place in migrating to Angular2, because Angular2 is whole new architecture. Whatever you write today in Angular1, you need to rewrite in Angular2. Until I write code in Angular2, I should still keep writing components using closure function in angular1.

Thanks for taking the time to read this. Feedback, critiques and suggestions are welcomed.