Feedback
TypeScript meetup #2 - Paris 2015-06-10
Adrien Chauve @adrienchauve CTO @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 ;-)
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 with Q.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)
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?