Prototype based controller with Angular 1.4

in Front-End Web Technologies

I just encountered a brilliant post written by Eyal Vardi (Hebrew only)

Eyal talks about how we can use Angular metadata stored inside $inject to automatically inject the controller’s dependencies into the controller instance

This way you don’t need to manually copy local dependency parameters into the controller instance.

Consider the following Typescript controller + HTML view

class HomeCtrl {
    $scope;
    $http;
    items: any[];
 
    constructor($scope, $http) {
        this.$scope = $scope;
        this.$http = $http;
    }
 
    refresh() {
        this.$http.get("/api/item").then((items) => {
            this.items = items;
        });
    }
}
 
angular.module("MyApp").controller("HomeCtrl", ["$scope", "$http"]);
<div ng-controller="HomeCtrl as ctrl">
    <ul>
        <li ng-repeat="item in ctrl.items">
            <span>{{item.name}}</span>
        </li>
    </ul>
 
    <div>
        <button ng-click="ctrl.refresh()">Refresh</button>
    </div>
</div>

As you can see all dependencies specified by angular.controller function are copied manually into the controller instance so we can later use them inside the refresh function.

When writing “plain” Angular controller based on simple function (without prototype) the dependencies are specified as local parameters and are shared by all functions injected into the $scope.

I guess that this is the main reason why developers prefer the simple (but less efficient method) way of writing controllers.

Using a module I created named oc-controller-di you no longer need to manually copy the dependencies into the controller instance.

So after installing the oc-controller-di module into your project you can write the following Typescript controller

class HomeCtrl {
    $scope;
    $http;
    items: any[];
 
    constructor() {
    }
 
    refresh() {
        this.$http.get("/api/item").then((items) => {
            this.items = items;
        });
    }
}

Now, the controller is much simpler and much resembles an Angular 2.0 component 🙂

The magic resides inside the oc-controller-di module. Here is the full implementation of the  module copied from the Github repository

(function (angular) {
    var isObject = angular.isObject;
    var extend = angular.extend;
 
    angular.module("ocControllerDI", []).config(["$injector", "$controllerProvider", function (providerInjector, $controllerProvider) {
        //
        //  A regular expression to parse controller name (for examle, "HomeCtrl as ctrl")
        //  Is copied from from angular source code
        //
        var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
 
        //
        //  A map of all registered controllers
        //  Is uses later to extract $inject for new controller
        //
        var controllers = {};
 
        //
        //  We are going to monkey patch $controllerProvider so save it before overriding
        //
        var originalControllerProvider = {
            $get: $controllerProvider.$get,
            register: $controllerProvider.register
        };
 
        //
        //  Monkey patch register
        //
        $controllerProvider.register = function (name, constructor) {
            originalControllerProvider.register.apply(this, arguments);
 
            if (isObject(name)) {
                extend(controllers, name);
            }
            else {
                controllers[name] = constructor;
            }
        }
 
        //
        //  Monkey patch $controller
        //  Every request to create a controller is now delegated to our implementation
        //
        $controllerProvider.$get = ["$injector", function ($injector) {
            //
            //  Get a reference to the real $controller
            //
            var $controller = $injector.invoke(originalControllerProvider.$get, this);
 
            //
            //  Return new $controller
            //
            return function (expression, locals, later, ident) {
                //
                //  Invoke the original $controller and get a reference to the controller instance
                //  this instance is not initialized yet (the ctor was not invoked)
                //
                var res = $controller.apply(this, arguments);
 
                if (!later) {
                    //
                    //  Current Angular implementation uses a "later" technique
                    //  Which means that Angular first creates the controller instance and only later invokes the ctor
                    //  We can only do our magic if angular works in a "later" mode
                    //
                    return res;
                }
 
                //
                //  A controller instance (ctor was not invoked yet)
                //
                var controller = res.instance;
 
                var dependencies = null;
 
                //
                //  This is a copy of Angular code to handle complex controller expression like "HomeCtrl as ctrl"
                //
                var match = expression.match(CNTRL_REG);
                var constructor = match[1];
                var expression = controllers[constructor];
                if (angular.isArray(expression)) {
                    //
                    //  Extract only the dependencies
                    //
                    dependencies = expression.slice(0, expression.length - 1);
                }
 
                if (dependencies) {
                    //
                    //  Build a local function with controller dependencies and copy them into the controller instance
                    //
                    initController.$inject = dependencies;
                    function initController() {
                        for (var i = 0; i < dependencies.length; i++) {
                            controller[dependencies[i]] = arguments[i];
                        }
                    }
 
                    $injector.invoke(initController, this, locals);
                }
 
                return res;
            }
        }];
    }]);
})(angular);

Enjoy, …

Contact us
You might also like
Share: