Feedback – AngularJS + TypeScript at Serenytics



Feedback – AngularJS + TypeScript at Serenytics

0 1


Prez-Angular-TypeScript

Feedback using AngularJS + TypeScript at Serenytics (slides)

On Github Serenytics / Prez-Angular-TypeScript

Feedback

AngularJS + TypeScript at Serenytics

TypeScript meetup #2 - Paris2015-06-10

Adrien Chauve @adrienchauveCTO @Serenytics

  • I'm also a fullstack dev at the moment ;-)
  • Serenytics: early stage startup that buils a SASS in Analytics and BI; go see the website for more
  • Use AngularJS for our SPA and decided a short time ago to switch to TypeScript
  • This prez is about our feedback after a few months using Angular+TypeScript

disclaimer

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

Contents

What we do: Dashboards and Widgets Why moving to TypeScript? Angular + TypeScript: HowTo? The Good, the Bad and the Ugly
  • First: what we do because it's a key point why we chose to begin the migration to TypeScript

1. What we do: Dashboards and Widgets

1. What we do: Dashboards and Widgets

  • Actually we do more than that and there is a big backend part in python but it's out of topic here

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!
  • code reuse as opposed to duplicating the code for by instance our ten widgets (=> ten directives, it was the case, at first!)
  • single directive, we need full customization available for each widget
  • Problem in directive composition: transclusion, shared isolated scope between several directives, reuse of Controller of other directive is *NOT* straightforward and not worth the pain in my opinion
  • TypeScript to the rescue: could actually be done in pure ES5 but lot more verbose, and loose all other goodies of TypeSCript

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
  • migration is an iterating process, really crucial as an early-stage startup
  • classes are also a good way to organize code when you're used to them in your backend programming language like python

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;
            });
    }
}
  • really easy to use TypeScript for controller
  • force us to use ControllerAs and decouple Controller Class from $scope
  • ES6 classes -> cleaner
  • type checked! (+completion +jump to source code: super nice!)

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();
});
  • not so much work
  • need to type the scope to take full advantage of TypeScript
  • open the way to inheritance to factorize simple attributes like restrict, replace, controllerAs, and utils to setup the link function
  • Thanks to Felix Billon for mentioning the interesting 'bindToController' attribute that is clearly a good choice too :)

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
  • I'm not really comfortable with implementing models as services, as I think service first role is to be singletons, not object constructors

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)
  • example of type definitions with use: angularjs of course, angular-ui, jquery, lodash, momentjs, numeraljs, highcharts, and more with the e2e tests in protractor

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); }
}
  • Compromise between easy testability with mocking and Angular native DI, and simple access to services outside of Angular but more difficult mocking in tests

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 withQ.denodeify or Bluebird.promisify

  • you get this use case if you want to use Q with protractor for instance
  • The kind of errors you get can remind you of old times with C++ template metaprogramming ;-)

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
  • Thanks, I hope you enjoyed the presentation
  • Any question?
Feedback AngularJS + TypeScript at Serenytics TypeScript meetup #2 - Paris2015-06-10 Adrien Chauve @adrienchauveCTO @Serenytics I'm also a fullstack dev at the moment ;-) Serenytics: early stage startup that buils a SASS in Analytics and BI; go see the website for more Use AngularJS for our SPA and decided a short time ago to switch to TypeScript This prez is about our feedback after a few months using Angular+TypeScript