Feedback
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 ;-)
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)
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