Table of Contents Preface typescript angular

Modules

Back in 2009 when An­gu­lar was cre­ated there was no con­ven­tion for im­port­ing files in JavaScript. There­fore An­gu­lar 1.x uses its own syn­tax to reg­is­ter mod­ules (angular.module), which does not con­form the ES­2015 mod­ule syn­tax (import, export).[1]

For­tu­nately both mod­ule load­ers are com­pat­i­ble and we can use ES­2015 mod­ules to fur­ther im­prove our code base. In ad­di­tion, us­ing the ES­2015 mod­ules syn­tax will make it eas­ier for us to mi­grate to An­gu­lar 2, once its ready.

It is best to put the ap­pli­ca­tion logic and mod­ule reg­is­tra­tion into seper­ate files. This way it will be eas­ier to rea­son about the struc­ture of your ap­pli­ca­tion and makes test­ing in­di­vid­ual mod­ules even bet­ter. At a later point you even might re­move the files that reg­is­ter as a An­gu­lar mod­ule and only use ES­2015 mod­ule syn­tax.

In or­der to il­lus­trate this re­call the MarvelApiService from the Type­Script chap­ter. If you have the ser­vice in a file named marve-api.service.ts and a HTTP in­ter­cep­tor in an­other files called marvel-key.interceptor.ts im­port­ing these files into an An­gu­lar mod­ule, lo­cated in marvel.module.ts, looks like this:

import angular from 'angular';
import MarvelApiService from './marvel-api.service';
import MarvelKeyInterceptor from './marvel-key.interceptor';

export default angular
    .module('marvel', [])
    .service('MarvelApiService', MarvelApiService)
    .config(['$httpProvider', ( provider:ng.IHttpProvider ) =>  {
        provider.interceptors.push(() => new MarvelKeyInterceptor());
    }]);

Load­ing other An­gu­lar mod­ules as de­pen­den­cies is also much sim­pler, be­cause you do not have to re­mem­ber their names. If you need the marvel mod­ule as a de­pen­dency you can do the fol­low­ing in or­der to re­quire it:

import angular from 'angular';
import MarvelModule from './services/marvel/marvel.module';

export default angular
    .module('myAwesomeApp', [
        MarvelModule.name
    ]);

On using class

The in­tro­duc­tion of class al­lows us to write a lot of our An­gu­lar mod­ules as such. Be­low are ex­am­ples how you can im­ple­ment ser­vices, con­trollers and di­rec­tives as classes, fol­lowed by some re­marks about what other ob­jects can be im­ple­mented as classes or re­main reg­u­lar func­tions.

Service

Since ser­vices in An­gu­lar are fol­low­ing the con­struc­tor pat­tern[2] im­ple­ment­ing them as a class is straight for­ward:

// Path: services/marvel/marvel-api.service.ts
import angular from 'angular';
import MARVEL_CONFIG from './marvel.config';

class MarvelApi {
    public $http:ng.IHttpService;

    constructor( $http:ng.IHttpService ) {
        this.$http = $http;
    }

    getSuggestions ( query:string ) {
        return this.$http.get(
           `${MARVEL_CONFIG.base}${MARVEL_CONFIG.characters}?nameStartsWith=${query}`
        );
    }
}
// Path: services/marvel/marvel.module.ts
import * as angular from 'angular';
import MarvelApi from './marvel-api.service';

export default angular
    .module('whiz.marvel', [])
    .service('MarvelApi', MarvelApi);

Controller

Im­ple­ment­ing con­trollers is as sim­ple as ser­vices, be­cause they are also con­struc­tors:

// Path: app/app.controller.ts
import MarvelApi from '../services/marvel/marvel-api.service';

export default class AppController {
    public api:MarvelApi;
    public sugesstions:string[] = [];

    constructor( MarvelApi:MarvelApi ) {
        this.api = MarvelApi;
    }

    fetchSuggestions( query:string ) {
        this.api.getSuggestions( query )
            .then( (suggestions) => {
                this.sugesstions = sugesstions;
            });
    }
}
// Path: app/app.module.ts
import * as angular from 'angular';
import AppController from './app.controller';

export default angular
    .module('app', [])
    .service('AppController', AppController);

Direcitve

Im­ple­ment­ing di­rec­tives with the class syn­tax is a lit­tle bit dif­fer­ent be­cause An­gu­lar’s .directive API ex­pect a fac­tory rather than a con­struc­tor. The dif­fer­ence be­tween those two is very sut­tle. A con­struc­tor is an ob­ject that in­sta­ni­ates with the new key­word, while a fac­tory will return a new in­stance of the ob­ject.

There is no clear win­ner (yet?) among the op­tions. So choose the style that suits you best. It is more im­por­tant that every­one in your team will use the same style in or­der to be con­sis­tent.

(1) As class with the factory in the .directive callback

Your di­rec­tive de­f­i­n­i­tion ob­ject is im­ple­mented as a class with sta­tic prop­er­ties only. When reg­is­ter­ing the di­rec­tive with the An­gu­lar mod­ule sys­tem an an­nony­mous func­tion is called, which cre­ates a new in­stance.

// Path: components/say-hello/say-hello.component.ts
import controller from './say-hello.controller';

export default class SayHelloComponent implements ng.IDirective {
    restrict = 'E';
    scope = {};
    template = '<strong>Good Day, {{ vm.name }}!!!</strong>';
    controller = controller;
    controllerAs = 'vm';
    bindToController = {
        name: '='
    };
}
// Path: components/say-hello/say-hello.module.ts
import angular from 'angular';
import SayHelloComponent from 'say-hello.component';

export default angular
    .module('hello', [])
    .directive('sayHello', () => new SayHelloComponent());

(2) As a factory with a static create method

You de­fine your di­rec­tive as de­scribed in (1) and cre­ate a fac­tory class with a create method in the same file. Now you can reg­is­ter the fac­to­ry’s create method with An­gu­lar.

// Path: components/say-hello/say-hello.component.ts
import controller from './say-hello.controller';

export class SayHelloComponent implements ng.IDirective {
    restrict = 'E';
    scope = {};
    template = '<strong>Good Day, {{ vm.name }}!!!</strong>';
    controller = controller;
    controllerAs = 'vm';
    bindToController = {
        name: '='
    };
}

export default class SayHelloFactory {
    public static create:ng.IDirectiveFactory = () => {
        return new SayHelloComponent();
    }
}
// Path: components/say-hello/say-hello.module.ts
import angular from 'angular';
import SayHelloFactory from 'say-hello.component';

export default angular
    .module('hello', [])
    .directive('sayHello', SayHelloFactory.create);

(3) As a factory function

You de­fine your di­rec­tive as de­scribed in (1) and cre­ate a ded­i­cated fac­tory method in the same file. Af­ter that you reg­is­ter the ex­ported func­tion as an An­gu­lar di­rec­tive.

// Path: components/say-hello/say-hello.component.ts
import controller from './say-hello.controller';

export class SayHelloComponent implements ng.IDirective {
    restrict = 'E';
    scope = {};
    template = '<strong>Good Day, {{ vm.name }}!!!</strong>';
    controller = controller;
    controllerAs = 'vm';
    bindToController = {
        name: '='
    };
}

let SayHelloFactory:ng.IDirectiveFactory = () => {
    return new SayHelloComponent();
}
export default SayHelloComponent;
// Path: components/say-hello/say-hello.module.ts
import angular from 'angular';
import SayHelloFactory from 'say-hello.component';

export default angular
    .module('hello', [])
    .directive('sayHello', SayHelloFactory);

Note that in all the above cases you could cre­ate an ob­ject lit­eral in­stead. How­ever, cre­at­ing a class and ex­port­ing it will give you bet­ter ca­pa­bil­ity to thor­oughly test your di­rec­tive.

Remaining Modules

Most of the time you will cre­ate con­trollers, ser­vices and di­rec­tive in An­gu­lar. But the re­main­der mod­ules have also their ap­pli­ca­tions. For in­stance, the .provider mod­ule is ex­tremly use­ful if you want to con­fig­ure your ser­vices and fac­to­ries.

If you reg­is­ter a ser­vice or a fac­tory with An­gu­lar a provider is cre­ated. This provider will re­turn your ser­vice/​fac­tory when­ever its $get method is called. So ba­si­cally, be­hind the scences An­gu­lar will al­ways use the provider pat­tern to in­stan­ci­ate ser­vices and fac­to­ries.[3]

The Type­Script de­c­la­ra­tion file of An­gu­lar[4] knowns three types of providers even though they are not all di­rectly im­ple­mented in An­gu­lar:

    interface IServiceProvider {
        $get: any;
    }

    interface IServiceProviderClass {
        new (...args: any[]): IServiceProvider;
    }

    interface IServiceProviderFactory {
        (...args: any[]): IServiceProvider;
    }

IServiceProvider is the base in­ter­face and what you will use when cre­at­ing a provider with the class syn­tax. The other two de­c­la­ra­tions are re­quired for the .provider API, e.g. IServiceProviderClass en­ables you to reg­is­ter a class with module.provider('SayHello', new SayHelloProvider()).

Thus, writ­ing a provider as a class is pretty straight for­ward. But be care­ful. Cur­rently the de­c­la­ra­tion file al­lows any as re­turn type of $get, which makes void a valid re­turn value. So be care­ful and make sure $get re­turns a con­struc­tor or a fac­tory.

Dec­o­rat­ing mod­ules via .decorator should be done with a func­tion, since the dec­o­ra­tor has to re­turn the $delegate (orig­i­nal ser­vice) or or a new ser­vice ob­ject which re­places or wraps and del­e­gates to the orig­i­nal ser­vice.[5]

The .value and .constant should used with their cor­re­spond­ing JavaScript key­word let/var and const.

Using Classes elsewhere

There are even more op­pur­tu­ni­ties to use classes in your An­gu­lar ap­pli­ca­tion. If you are us­ing ngAnimate and reg­is­ter an­i­ma­tions you can use the pre­vi­ously in­tro­duced pattern (1) in or­der to cre­ate de­clar­a­tive an­i­ma­tion ob­jects. An­other com­mon use case are HTTP in­ter­cep­tors. Their pur­pose is to per­form some ad­di­tional tasks on cer­tain XHR events (request, requestError, response, responseError).

The Mar­vel de­vel­oper API will only suc­cess­fully re­solve your re­quest if you API key was send as a get pa­ra­me­ter. The re­quest in­ter­cep­tor, which al­ways adds the key to the re­quest pa­ra­me­ters, could look like this:

// Path: service/marvel/marvel-key.interceptor.ts
import MARVEL_API_KEY from 'marvel.key';
import MARVEL_CONFIG from './marvel.config';

export default class MarvelKeyInterceptor {
    request ( config:ng.IRequestConfig ) {
        // Request pings Marvel API?
        if( config.url.startsWith(MARVEL_CONFIG.base) ) {
            if( !config.params ) {
                config.params = {};
            }
            config.params.apikey = MARVEL_API_KEY;
        }
        return config;
    }
}

Reg­is­tra­tion of the MarvelKeyInterceptor with An­gu­lar’s $http ser­vice can be done like shown be­low.

// Path: service/marvel/marvel.module.ts
import * as angular from 'angular';
import MarvelKeyInterceptor from './marvel-key.interceptor';

export default angular
    .module('marvel', [])
    .config(['$httpProvider', ( provider:ng.IHttpProvider ) =>  {
        provider.interceptors.push(() => new MarvelKeyInterceptor());
    }]);

  1. Dr. Axel Rauschmayer - ECMAScript 6 modules: the final syntax

  2. Addy Osmani - Learning JavaScript Design Patterns

  3. Pascal Precht - Service vs Factory - Once and for all

  4. Definitely Typed - AngularJS

  5. Angular Documentation - $provide