Feedback

AngularJS + TypeScript at Serenytics

TypeScript meetup #2 - Paris
2015-06-10

Adrien Chauve @adrienchauve
CTO @Serenytics

disclaimer

  • I'm not a TypeScript expert, nor an AngularJS expert!
  • But it's better if you know some to follow this talk ;-)

Contents

  1. What we do: Dashboards and Widgets
  2. Why moving to TypeScript?
  3. Angular + TypeScript: HowTo?
  4. The Good, the Bad and the Ugly

1. What we do: Dashboards and Widgets

1. What we do: Dashboards and Widgets

1. What we do: Dashboards and Widgets

Initial question:

  • we use AngularJS with lots of different widgets
  • how to reuse as much code as possible?
  • while still being able to tune each widget appearance

Solutions:

  • Service: not enough (factorize logic but not UI interactions)
  • Single generic directive: single template problem
  • Directive composition: a generic base directive plus several small directives to adapt the template and behavior
  • TypeScript to the rescue, and much much more!

2. Why moving to TypeScript?

2. Why moving to TypeScript? (1/2)

  • Potential good solution to factorize our code (more on that later)
  • All the goodness of ES6 (classes, fat arrow, template strings, soon async/await, ...), plus:
  • statically typed
    • automatic feedback while developing (think gulp/grunt watch)
    • interfaces! description of complex types (e.g. widget data model) available in a single place and not spread around the code (Angular is really persmissive for models)

2. Why moving to TypeScript? (2/2)

  • It's just a Javascript superset, so the migration can be incremental and smooth, no need to rewrite the app from scratch
  • really easy integration with Angular (even if a little scary at first)
  • forces to use classes, and then better organize the code (again Angular is really permissive)
  • Angular2 is written in TypeScript: Google + Microsoft are now supporting it

3. Angular + TypeScript: HowTo?

3. Angular + TypeScript: The Basics

  • Controller
  • Service
  • Directive

3. Angular + TypeScript: The Basics - Controllers

Using ControllerAs syntax, a controller is just a Class


angular
    .module('my-lib')
    .controller('LoginController', LoginController);

$stateProvider
    .state('login', {
        url: '/login',
        templateUrl: 'mylib/auth/login.template.html',
        controller: 'LoginController',
        controllerAs: 'vm'
    })

3. Angular + TypeScript: The Basics - Controllers

Example in ES5:


var LoginController = (function () {
    function LoginController(loginService, $state) {
        this.loginService = loginService;
        this.$state = $state;
        this.invalidCredentials = false;

        if (loginService.isLogged) {
            $state.transitionTo('home');
        }
    }
    LoginController.prototype.login = function () {
        var _this = this;
        this.invalidCredentials = false;
        this.loginService.loginWithCrendentials(this.email, this.password)
            .catch(function () {
                _this.invalidCredentials = true;
            });
        };
    return LoginController;
})();

3. Angular + TypeScript: The Basics - Controllers

Example in TypeScript: lots of goodness in it


class LoginController {

    invalidCredentials = false;
    email: string;
    password: string;

    constructor(private loginService: ILoginService,
                private $state: angular.ui.IStateService) {
        if (loginMgr2.isLogged) {
            $state.transitionTo('home');
        }
    }

    login () {
        this.invalidCredentials = false;
        this.loginService.loginWithCrendentials(this.email, this.password)
            .catch(() => {
                this.invalidCredentials = true;
            });
    }
}

3. Angular + TypeScript: The Basics - Services

Just like Controllers:


    class LoginService {

    constructor(private Restangular: restangular.IService) {
    }

    loginWithCrendentials (email: string, password: string) {
        return this.Restangular.one('api/token')
            .then((apiData) => {
                // ... save token
                // ... transition to 'home' state
            });
        }
    }

angular
    .module('my-lib')
    .service(loginService, LoginService);

3. Angular + TypeScript: The Basics - Directives


interface IWidgetDirectiveScope extends ng.IScope {
    widgetModel: IWidgetModel;
}

class WidgetDirective {
    scope = {
        widgetModel: '=',
    };
    restrict = 'E';
    replace = true;
    controllerAs = 'vm'

    templateUrl = 'components/widgets/widget.directive.html';
    controller = WidgetController;

    link = (scope: IWidgetDirectiveScope,
            element: ng.IAugmentedJQuery,
            attrs: ng.IAttributes,
            controller: WidgetController) => {
            // ...
    }
}

angular.module('my-lib').directive('my-widget', () => {
    return new WidgetDirective();
});

3. Angular + TypeScript: Even more!

ok Angular + TypeScript is cool, but what about code reuse and our initial question?

Lots of common behavior between

  • table widget / value widget (= single cell table)
  • all chart widgets (pie chart, bar chart, curve chart, ...)

3. Angular + TypeScript: Reuse code!

Different ways:

  • keep the same controller, adapt the template, 2 directives for the same price!
  • inherit base controller to inherit basic behavior exposed to the view (think Mixins when available)
    • refresh state (reload data from API)
    • error handling
    • global data filtering
    • data export
  • implement models (e.g. Widgets) as classes completely outside of Angular's world

3. Angular + TypeScript: Our solution for code reuse

  • keep directives small and simple, and have several if needed
    • each customized with its own template
    • with possibly one base directive to factorize $scope features and simple properties (replace, ControllerAs, ...)
  • one base controller and several inherited controllers as needed
  • pure TypeScript Widget classes without any Angular dependency (model/business logic)

4. The Good, the Bad and the Ugly

4. Angular + TypeScript: The Good Parts

  • easy integration with Angular, especially with ControllerAs since 1.2
  • even encourage to use best practises for Angular 2 (ControllerAs => Components)
  • incremental migration (superset + gradual typing with any)
  • type infos, type inference and all the good that comes with it
  • Interfaces: all the model in one place!
  • Good debugging experience using source maps with Chrome

4. Angular + TypeScript: The Bad Parts (1/2)

  • using 3rd party libraries (missing or outdated typed definitions): but not such a great problem
  • dev environment a little more complex (gulp, tsc, tslint, tsd): actually not so much pain
  • a little more work sometimes (adding types, directives more verbose)

4. Angular + TypeScript: The Bad Parts (2/2)

Dealing with class hierarchies: compromise between testability and verbosity

class BaseWidgetController {
    private _data: IData;

    constructor(private globalFilterService: GlobalFilterService /* other dependencies */) { /* ... */}

    filterData () { return this.globalFilterService.applyFilters(this._data); }
}

class TableWidgetController extends BaseWidgetController {
    constructor(private globalFilterService: GlobalFilterService /* other dependencies */) {
        super(globalFilterService, ....);
    }
}

/* less verbose alternative - dangerous */
class GlobalFilterService {
    /* WARNING: bypass Angular DI and make testing more complex */
    static instance() {
        angular.element(document.body).injector().get('globalFilterService');
    }
    applyFilters(...) {...}
}

class BaseWidgetController {
    private _data: IData;
    constructor() {}
    filterData () { return GlobalFilterService.instance().applyFilters(this._data); }
}

4. Angular + TypeScript: Ugly Parts?

Not really... or maybe

when coding e2e tests with Protractor + TypeScript: incompatible Promises types


// selenimum-webdriver type declaration
interface IThenable<T> {
    then<R>(opt_callback?: (value: T) => Promise<R>, opt_errback?: (error: any) => any): Promise<R>;
    then<R>(opt_callback?: (value: T) => R, opt_errback?: (error: any) => any): Promise<R>;
}

// vs. ES6 type declaration
interface Thenable<R> {
    then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
    then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
}

Good luck if you use nodejs with
Q.denodeify or Bluebird.promisify

Towards Angular 2.0: Angular in TypeScript

If you:

  • have a growing project in Angular 1.X
  • want to invest on it for the next couple of years

Do you a favor, go for TypeScript!
Congrats! you'll be half way through the migration to Angular2!

Angular 2: everything becomes a TypeScript class with annotations (Component, Directive)

References

Questions?

Want to work with us at Serenytics?
  • Interested by Analytics, BI and Startups?
  • Passionate about Angular and TypeScript? Love Python?
  • Come and see me, we're looking for an amazing dev / startuper!
  • Or contact me at adrien.chauve@serenytics.com