Tags

, , , , , , , , , , ,

Part 2 – Extending AngularJS to make it more simple and dynamic

In previous article we talked about some boilerplate code and issues that each developer has to face to work with Angular implementation and we also worked to abstract these common work into base services and controllers to come over these limitations. The main benefit of this approach is we can have core feature implementation of application in base so that its easily available to developer wherever they want.

Following were the key benefits we achieved so far

  1. Developer don’t have to inject any dependencies explicitly
  2. Common services can be made available anywhere required using ServiceLocator pattern implementation. That will reduce the time and effort of defining dependencies all over the application.
  3. Since core logic is inside the base classes, developer forced to adhere to application standards rather than implementing it their own way.


By the end of this article we will have achieve

  1. Loading Services on demand from server (lazy-loading)
  2. Simplifying components development similar to controller and services we did in last part

It can be a long talk, if I try to explain need of every design element, hence I’m going to keep the discussion to the point. Otherwise you can just download the source code and start playing around with it to get to know it deeper and even extend it further to make to work for your design the way you want.

Loading Services on demand from server (lazy-loading)

Generally small applications don’t really have to worry about this feature, you can just make sure all of your services are loaded at the page load. If your application is huge with lots of services being injected, then it makes total sense to have this implementation.

In previous article, we worked together to implement ServiceLocator that act as service provider for all over the application needs. This approach going to help us to implement lazy loading of any service form server easily.

Here is the example of service implementation before applying lazy loading of Service1 file from server with our previous work.

    class Service1 extends BaseService {
        GetItems(): angular.IPromise<string[]> {
             return this.Http().get("/api/products");
        }
    }
    class ServiceLocator extends BaseService {
        Service1(): Service1 {
             return this.Injector().get<Service1>("Service1");
        }
    }
    class HomeController extends BaseController<IHomeScope>{
        Init(): void {
             this.Scope.Items = ["I", "was", "loaded", "synchronously"];
             this.Services().Service1().GetItems()
                       .then((result) => {
                             this.Scope.Items = result.data;
             });
        }
    }

There are many ways to load files from servers, here I’m going to use ocLazyLoad library. Let’s add Load method to BaseService that will take care of loading the service from the server. This Load method will take the service name which also depicts the service file name and tells to ocLazyLoad library to load it from location /apps/services. You can use the path from your application directory structure.

    protected Load(service: string): ng.IPromise<any> {
           var lazy = this.Injector().get("$ocLazyLoad") as oc.ILazyLoad;
           return lazy.load("/apps/services/" + service + ".js")
                      .catch((reason) => {
                          throw service + " " + reason.description;
            });
    }

If you notice loading file from server is async process that means we cannot perform Service1().GetItems() call anymore, because on accessing Service1() method we first have to load file from server then only developer should be able to access service in their implementation.


Workaround to this is now have to get the action from the caller and execute it only once promise completes. Lets add helper method Invoke that will call our Load method and also handle the promise to execute action.

    protected Invoke<T, U>(service: string, action: (service: T) => ng.IPromise<U> | U): ng.IPromise<U> | U {
          return this.Load(service)
                     .then<U>((): ng.IPromise<U> | U => {
                          return action(this.Injector().get<T>(service));
          });
    }

Putting this all together out BaseService, ServiceLocator and HomeController code showed above will finally looks like

    abstract class BaseService {
        static $inject = ["$http", "$rootScope", "$injector"];
        private _http: angular.IHttpService;
        private _rootScope: angular.IRootScopeService;
        private _injector: angular.auto.IInjectorService;

        constructor(http: angular.IHttpService, rootScope: angular.IRootScopeService, injector: angular.auto.IInjectorService) {
              this._http = http;
              this._rootScope = rootScope;
              this._injector = injector;
              this.Init.bind(this);
              this.Init();
        }

        Init(): void { }

        Http(): angular.IHttpService {
              return this._http;
        }
        RootScope(): angular.IRootScopeService {
              return this._rootScope;
        }

        protected Injector(): angular.auto.IInjectorService {
              return this._injector;
        }

        protected Load(service: string): ng.IPromise<any> {
              var lazy = this.Injector().get("$ocLazyLoad") as oc.ILazyLoad;
              return lazy.load("/apps/services/" + service + ".js")
                         .catch((reason) => {
                                throw service + " " + reason.description;
              });
        }

        protected Invoke<T, U>(service: string, action: (service: T) => ng.IPromise<U> | U): ng.IPromise<U> | U {
              return this.Load(service)
                         .then<U>((): ng.IPromise<U> | U => {
                               return action(this.Injector().get<T>(service));
              });
        }
    }
    class HomeController extends BaseController<IHomeScope>{
        Init(): void {
              this.Scope.Items = ["I", "was", "loaded", "synchronously"];
              this.Services().Service1((service) => {
                    service.GetItems()
                           .then((result) => {
                                 this.Scope.Items = result;
              })});
        }
    }

Now you can see without having to do much code change we achieved to load service file on demand.

TypeScript Decorators

Before moving to our next topic for components, I want to add small thing to make it more interesting. Since components are little tricky to implement the way we have implemented services and controller, Component is mixture of both and has more capabilities to it.

That is where TypeScript decorator feature will help us to simplify defining components and minimize the coding efforts related to it. The reason I’m going to talk about TypeScript Decorator is because it adds more value to the JavaScript generation and runtime object creation. I recommend you to read about how decorators works before reading any further.

Now that you know what it does, one can easily think of its usage with respect to AngularJS. We haven’t really talked before how you register any objects in Angular to make it work with framework.

    class HomeController extends BaseController<IHomeScope>{
       . . . 
    }
    angular.module("portal").controller("HomeController", HomeController);

Let’s create decorator to handle that as well so that developer doesn’t really have to worry about registering it in Angular.

    function Service(name: string) {
         return (target: Function) => {
            angular.module("portal").service(name, target);
        };
    }

    function Controller(name: string) {
        return (target: Function) => {
            angular.module("portal").controller(name, target);
        };
    }

Now this can be used on any service and controller we want to registered with our angular module.

    @Service("Service1")
    class Service1 extends BaseService {
        GetItems(): angular.IPromise<string[]> {
              return this.Http().get("/api/products");
        }
    }

    @Service("ServiceLocator")
    class ServiceLocator extends BaseService {
         Service1<T>(action: (service: Service1) => ng.IPromise<T> | T): ng.IPromise<T> | T {
                 return this.Invoke("Service1", action);
         }
    }

    @Controller("HomeController")
    class HomeController extends BaseController<IHomeScope>{
         . . . 
    }

That’s it; amazingly, the decorator will take care of registering your functions at runtime. Basically we have pretty much isolated Angular dependencies from developer’s hand.

Simplifying components / directives

Let’s talk about components (and directives) in AngularJS and see what area of it we can improvise similar to what we did for Controllers and Services. Below is simple snippet of how components defined normally.

    interface IMyComponentScope extends IScope {
        Items: string[];
    }

    angular.module('portal').directive("MyComponent", function () {
        controller.$inject = ["$scope", "Service1"];
        var controller = function ($scope: IMyComponentScope, Service1: Service1) {
        
              $scope.Items = ["I", "was", "loaded", "synchronously"];
              Service1.GetItems().then((result) => {
                    $scope.Items = ["I", "was", "loaded", "asynchronously"];
              });        
        };
        
        return {
            templateUrl: "/apps/components/MyComponent/MyComponent.html",
            controller: controller,                
        };
    });

As you can see component comprises of controller and other directive options that drives how your component will function. Components not required having controller but we will make that default for our design framework as a standard.

So let’s first create the class to define our component that will act as controller as well

    abstract class BaseComponent<T extends IScope> {
        static $inject = ["$scope", "ServiceLocator"];
        private serviceLocator: ServiceLocator;
        public Scope: T;
        

        constructor(scope: T, services: ServiceLocator) {
            this.Scope = scope;
            this.serviceLocator = services;
            this.Init.bind(this);
            this.Init();
        }

        abstract Init(): void;


        Services(): ServiceLocator {
            return this.serviceLocator;
        }

        Parent(): IScope {
            return this.Scope["$parent"] as IScope;
        }
    }

That’s great so we have class to act as controller but if you notice that’s not how components are being setup. Angular expects the function that returns definition of the component. We cannot use above approach to register it as component as we did for services and controllers.

If you really compare Directives and Components are very similar in the way its setup, the only difference is Components are light weight and simple form of Directive. But I will consider Directives only approach for our design to support previous versions of Angular and anyway our end result is going to be much simpler than original Components setup recommended.

Below are the definitions you can define for directives:

    interface IDirective {
        compile?: IDirectiveCompileFn;
        controller?: any;
        controllerAs?: string;
        link?: IDirectiveLinkFn | IDirectivePrePost;
        multiElement?: boolean;
        name?: string;
        priority?: number;
        require?: string | string[] | {[controller: string]: string};
        restrict?: string;
        scope?: boolean | Object;
        template?: string | Function;
        templateNamespace?: string;
        templateUrl?: string | Function;
        terminal?: boolean;
        transclude?: boolean | string | {[slot: string]: string};
    }

Let’s create interface and include these options there

    interface ICompoenent {
        compile?: ng.IDirectiveCompileFn;
        multiElement?: boolean;
        priority?: number;
        require?: string | string[] | { [controller: string]: string };
        scope?: boolean | Object;
        terminal?: boolean;
        transclude?: boolean | string | { [slot: string]: string };
        template?: string | Function;
    }

I did not include some of properties since those will be automated and some are I never really needed. You can add or extend it as you need.

Lets define the decorator to register the component with definition as required by Angular.

    function Component(name: string, options: ICompoenent) {
        return (target: Function) => {
            var directiveOptions = options as ng.IDirective;

            directiveOptions.controller = target;
            directiveOptions.scope = (directiveOptions.scope || {});
            directiveOptions.templateUrl =
                (directiveOptions.templateUrl || "/apps/components/" + name + "/" + name + ".html");

            if (target.prototype.Link)
                directiveOptions.link = target.prototype.Link;
            angular.module('portal').directive(name, function () { return directiveOptions; });
        };
    }

Here I’m using pre decided path for all component’s template url. I do not like to define html templates inside the code and always tends to have dedicated template in-place for Angular to look for. You can change that behavior, as you like though.

Lets put all these together and see how our final implementation for component looks like

    var option: ICompoenent = {
        transclude: true
    };

    @Component("MyComponent", option)
    class MyComponent extends BaseComponent<IMyComponentScope>{
        Init(): void {
            this.Scope.Items = ["I", "was", "loaded", "synchronously"];
            this.Services().Service1((service) => {
            return service.GetItems()
                    .then((result) => {
                          this.Scope.Items = ["I", "was", "loaded", "asynchronously"];
                    });
            });
        }
    }

That’s it. Now we got the same goodies of controller and services in component as well.

So far what we achieved?

  1. Decorators that will take care of registering your controller, service and components
  2. No dependency required to provide in constructor
  3. All dependencies are always available for developer though out the application though base implementations
  4. Developer don’t need to worry about loading services from server, our framework will take care of loading it if it’s not already loaded.

Project structure

Now that we have our basic framework design ready we need to structure our project properly.

There are common two structures that I usually follow:

  1. Default MVC.net style
        app
            \controller
                \module1
                    \module1controller.ts
            \views
                \module1
                    \module1view.html
            \services
                \module1
                    \module1service.ts
            \components
  2. Modular structure (Uncle Bob’s screaming architecture)
        app
            \module
                \controller
                    \modulecontroller.ts
                \views
                    \moduleview.html
                \moduleservice.ts
            \components


    In the final source code provided with this article, I have used second one Modular structure with few modifications as shown below.

  3. Final standard structure of our framework
        app
            \moduleName
                \controllers
                    \moduleName-action1.controller.ts
                    \moduleName-action2.controller.ts
                \views
                    \moduleName-action.html
    		\moduleName-action2.html
                \moduleName.service.ts
            \components
                \componentsName
    		\componentsName.ts
    		\componentsName.html
    		\componentsName.css
    


    As you can see, I’m grouping controllers and views for that controller in modules (features). Modules are basically just high level group of relevant features. I have choose to have single service file per module approach here, but sometime depending on application if more services are required in one module then I group them into services folder just like controllers and views in relevant module. That approach is not covered here in this article, but if you choose to then its very simple to achieve that. You just have to change default lookup path in out Load method of BaseService class.

    And components are kept separate since those are common part of application and they are just grouped by their name. This convention has worked great so far to me and never has to worry about violating any thumb rules for structuring the application irrespective of any framework I choose to use.

File naming convention

Finally, since our base classes and decorator is doing guess work to decide which view or service to load automatically, we will need to have definite file naming standards in application.

Controller file name:
I generally prefer to name controller with their action, something like:
modulename-action.controller.ts

As you can see, module name and action is separated by hypen, that gives easy understanding by looking at just file what this particular file is for.

View file name:
View names follow similar convention
modulename-action.html

Service file name:
I usually have only one service file per module, but you can extend that behavior as you prefer.
modulename.service.ts

Putting this all together in example our project is structure as:

    app
        \employee
            \controller
                \emlpoyee-view.controller.ts
            \views
                \emlpoyee-view.html
            \employee.service.ts
        \components


Let’s see how our final working example looks like:

    namespace Portal.Employee {
        export interface IEmployeeViewScope extends Core.IScope {
            GridOptions: uiGrid.IGridOptionsOf<Employee.EmployeeViewModel>;
        }

        @Core.Controllers.Controller("Portal.Employee.EmployeeViewController")
        export class EmployeeViewController extends Core.Controllers.BaseController<IEmployeeViewScope> {
            Init(): void {
                this.Scope.GridOptions = {};
                this.Scope.GridOptions.data = [];
                this.Scope.GridOptions.columnDefs = [
                    { field: 'name' },
                    { field: 'gender' },
                    { field: 'company', enableSorting: false }
                ];
                this.Scope.GridOptions.onRegisterApi = (gridApi) => {
                    this.LoadData();
                };
            }
            LoadData(): void {
                this.Services()
                    .EmployeeService((service) => {
                           return service.GetAll()
                                .then((result) => {
                                      this.Scope.GridOptions.data = result;
                            });
                    });
            }
        }
    }

    namespace Portal.Employee {
        export class EmployeeViewModel {
            name: string;
            gender: string;
            company: string;
        }
        @Core.Services.Service("Portal.Employee.EmployeeService")
        export class EmployeeService extends Core.Services.BaseService {
            GetAll(): Core.IPromise<Array<EmployeeViewModel>> {
               return this.Http()
                    .get("https://cdn.rawgit.com/angular-ui/ui-grid.info/gh-pages/data/100.json")
                    .then((result) => {
                        return result.data;
                    });
            }
        }
    }


Download the source code solution with final implementation from my CodeProject post here.

Some of the topics that I’ll be covering in next article are already part of the code. Go ahead and explore those features included like:

  1. Better routing implementation
  2. Define page title from Route registration itself (Not included in code)
  3. Lazy loading controller’s files as required (Not included in code)
  4. Angular 2 support. No code change required in implementation, another benefit of this approach, developer don’t have to worry if its Angular 1.5 or 2 working behind.

Please share and comment if you find it useful. Comment if you have any other issues that you want to explore with this approach or any suggestions.

Advertisements