De Voorhoede

front-end developers

Hacking with Angular 2.0

At De Voorhoede, we've been using AngularJS for almost a year now. Meanwhile, Angular 2.0 is being designed in the open. I've been following its progress very closely. I get asked a lot to explain Angular to clients, and to advise them on architecture of apps and components. I often tell them about Angular 2 and the philosophy behind it, as we discuss different design practices. We use v1 eventually for its stability, but inspired by v2.

Last Friday, we organized an internal workshop to give everybody at De Voorhoede some first hands-on experience with Angular 2. In this post I’ll explain some of the things that I've learned personally, and share some experiences that we had during our workshop.

Before the Workshop: Following Angular

Angular 2.0 was first announced on March 18 2014, on the Angular blog. The blog post linked to a public Google Drive folder containing design docs and a progress tracker spreadsheet.

Now, I'm one of those developers that just always has to look at all the Git commits that were involved in a release. For example, I just had to check how they implemented the asynchronous ng-model validators in v1.3.0-rc.0, since I had once written my own, custom implementation for a project and was very curious to see how the official implementation ended up.

With Angular 2.0, it wasn't different. This Google Drive folder was a great chance to follow along with all the discussions and growing philosophy behind the framework. Besides keeping myself up-to-speed with the progress of the framework, the way they've been designing these components, continuously processing feedback from the community and doing all sorts of crazy code experiments has really been inspiring me.

Here are some of my favorite things that landed in v2:

Single-purpose Libraries

Before I started with Angular, I had done a lot with Backbone. It's often said that because Backbone is very minimal and unopinionated, one of the benefits of Backbone is that it encourages you to really think about your technical requirements. Designing an architecture specifically for those requirements, using your own selection of libraries to help you be more productive. When you use Angular, a big toolbox filled with everything you need to develop a web app, it's just very tempting stop thinking about architecture and let Angular lead the way. It seems like many developers just want someone to tell them how to build web apps, as Brian Ford wrote it in Stop Whining About New JS Frameworks.

Angular 2 will not be an all or nothing framework, but a collection of single-purpose libraries. A first step in encouraging you to form your own opinions on application architecture. It's funny to see that once Angular's new dependency injection library had been released, it got picked up in the Backbone community immediately. I love this kind of cross-pollination in open source.

Standards & Modern Browsers Only

Angular 1 is filled with non-standard APIs. Because of this, it can be very hard to integrate components in an Angular architecture when those components aren't written specifically for Angular. For example, when using a plain-JavaScript component that uses event handlers (the event handlers would need to be patched with digests). Similar problems arise when you want to use an asynchronous module loader or do anything with web components.

Angular was first released in 2010. The requirements of a web app architecture were very different from what they are now. Also, legacy browser support was much more of an issue than it is now. Angular 2 will focus on modern standards and browsers, allowing it to really push the envelope of possible and provide developers with a next generation experience.

These are a few of the technologies used in Angular 2:

Development on v1 will be continued (and some features from v2 backported) as an alternative for projects that need the broader browser support.

DI & Annotations

Annotations are one of the few things in Angular 2 that aren't currently part of any standard. They were first explained at ng-conf 2014: DI by Vojta Jina.

Before Angular 2, I had personally never used annotations. Once I started reading about them, I immediately liked how they separate meta data from the code. During recent projects, I’ve spoken a lot with Java developers, who have been familiar with using annotations for years. Simon Brown mentions annotations as a way to express software architecture as code, and to identify architectural elements when navigating through a system.

Annotations appear all over the place in Angular 2, so for examples just keep on reading.

New Directive APIs

The directive API of v1 is pretty overwhelming. It makes learning Angular more difficult, and makes using a directive for a simple problem feel like overkill to some.

In Angular 2, Directive is an abstract class that you don't use directly. Instead, you use one of these three directive types, optimized for different purposes:

  • DecoratorDirective: decorate element, e.g. show/hide or add an event handler
  • TemplateDirective: turn element into a template (example implementations: NgIf, NgRepeat)
  • ComponentDirective: encapsulated JavaScript, HTML and CSS

I like how these different APIs make you think: what kind of directive is it that you need? Do you need transclusion or an isolated execution context? You can’t have both. If you need both, make multiple directives and combine them in an umbrella component. It encourages smaller directives that work together. Smaller directives are easier to (unit) test, maintain and reuse.

Queryables

Basically the first thing we all had to unlearn when starting with Angular, is doing DOM queries. DOM queries are dangerous in an SPA, as the DOM is constantly changing and should not be talked to directly.

This is the role of controllers in Angular: they are the APIs of directives. The best-known examples are ng-model and ng-form. Models talk to forms, and forms talk to parent forms. They don't talk to the form element, though. They talk to the form controller. If you've ever written a custom ng-model validator, then you've registered your validator into the ng-model controller, instead of messing with the input element directly. It looks like this:

angular.module('foo', [])
  .directive('fooValidator', function() {
    return {
      require: 'ngModel', // Find ngModel directive on the same element
      link: function(scope, element, attrs, ngModelController) {
        // Talk to ngModelController here...
      }
    };
  })

It's also possible to talk to parent controllers by using ^:

angular.module('foo', [])
  .directive('foo', function() {
    return {
      require: '^form', // Find parent (ng)form directive
      link: function(scope, element, attrs, ngFormController) {
        // Talk to the parent ngFormController here...
      }
    };
  })

You can't do it downwards, though. If the parent directive would query child directives, its result could get outdated pretty quickly because of the dynamic nature of the DOM in an SPA. This is why ng-models register themselves in parent forms, and deregister themselves when their scope is destroyed. This way, the form will always have an up-to-date representation of child models, which it will use to determine whether the form is valid or invalid. So you can safely use ng-if, ng-repeat etc around ng-models without the form getting an out-of-date representation.

Subtle detail: the order in which the ng-models are registered in their parent form, is the order in which they are instantiated. Which is not necessarily the current DOM order. Luckily this doesn't matter for ng-model, but what if your directive really needs them in the current DOM order (e.g., when binding arrow keys from above to cycle through menu items)?

Angular 2 introduces queryables for this. It provides a live representation of directives in the current DOM order. Querying directives is based on roles that directives can specify to make themselves queryable (so the name or selector of the directive is irrelevant).

This is how you would use it to add a custom validator to ng-model:

@DecoratorDirective({selector: '[my-validator]'})
@Queryable('validator')
class MyValidator { ... }

NgModel would then inject all directives that are used on the same element and are queryable as a validator:

@DecoratorDirective({selector: '[ng-model]'})
@Queryable('model')
class NgModel {
  // QueryScope.THIS will find directives on the same element
  constructor(@InjectQuery('validator', QueryScope.THIS) validators) {
     this.validators = validators;
  }
}

NgForm would inject both nested models and forms this way:

@DecoratorDirective({selector: 'form'})
@Queryable('form')
class NgForm {
  // QueryScope.DEEP will go deeper down the DOM to find directives
  constructor(@InjectQuery('model', QueryScope.DEEP) models,
              @InjectQuery('form', QueryScope.DEEP) forms) {
    this.models = models;
    this.forms = forms;
  }
}

If you're curious like me and want to see the code behind this, take a look at Angular's Node Injector.

The Workshop: Hands-on Angular 2

So, eversince that announcement on the Angular blog, I've been reading about Angular 2, hacking with it and talking to people about it. Up to the point where other Voorhoeders must have been annoyed by it. Still though, most of them attended the workshop I had prepared. Some of them had experience with Angular 1, others had no experience with Angular at all. It was intended as a one-hour session (starting at 10:00), but we continued until we just had to go for lunch, if you catch my drift.

What We Built

Those of us who had experience with Angular 1, were kind of confronted by how spoiled we've been by some of our favorite v1 features, of which most don't exist (yet) in v2. During our workshop we focused on rebuilding some of these favorite features, rather than trying to build an app. I envisioned myself building ng-model that morning, but I haven't really gotten around to it just yet ;).

We based our experiments on a Hands-On Angular 2 boilerplate which already has all the required NPM modules, Bower components and Gulp tasks to instantly get your hands dirty.

The first exercise for everybody was building ng-cloak and ng-hide/ng-show. They're the simplest Angular features, so perfect to get familiar with the DecoratorDirective API and some basic routing to demonstrate the effect.

Result:

Another thing that I personally wanted to try, is the TemplateDirective API. I created two directives that work together, to change the content of the app header based on the current route.

Result:

Where Did We Look

There is absolutely no API reference for any of the libraries from Angular 2, at the moment. So we looked at design docs, examples and source code. We even looked at compiler unit tests at some point to discover what kinds of selectors are supported for a directive.

It took us a while to discover on-* attributes this way, which aren't directives but nonetheless are the replacement of ng-click, ng-focus, etc.

bind-* is another magical attribute that can be seen in the examples (used like this: <ANY ng-if bind-ng-if="expression">) and it felt weird at first. Most of us kept feeling the instinct to write <ANY ng-if="expression"> like we do in Angular 1. The difference is now that the original attribute always contains a string (you can interpolate), and the bind-* variant is evaluated as an expression (using Expressionist and can return other types as well (boolean, object, etc). The reasons for this separation are explained in Databinding with Web Components by Misko Hevery & Rob Eisenberg.

How Did We Feel

There were mixed feelings ;).

People definitely struggled a bit with all the fancy new ES6, annotations and Traceur, but at the same time loved that everything is based on standards. It may seem like a tiny thing to those who haven't worked with Angular 1, but to those who have, it was very satisfying to write a piece of asynchronous code (e.g., an event handler) and notice that it just works without having to wrap it with a$scope.$apply().

I’ve seen that the lack of scope inheritance pissed some people off (also outside our workshop), personally I think it's a good thinking exercise. It will slow you down at first but it will also make you think carefully about the way you connect directives. When you inject controllers into each other (rather than just cross-referencing scopes), the directives end up so much easier to test and maintain.

The lack of an API reference was annoying on some level, but understandable as everything is still under heavy development. Personally I also enjoy digging into the source code and unit tests for hints on how to use something, but obviously I wouldn't want to use Angular 2 on anything with a deadline just yet ;).

Now What?

Angular 2 has already changed the way I work, even when I'm using Angular 1. I think in decorator/template/component directives. I design smaller, single-purpose directives. I inject controllers into each other instead of cross-referencing scopes. I design component APIs, using the router to wire them together into workflows. All of these steps are helpful in making components more testable and maintainable.

At De Voorhoede, we'll still use Angular 1 for most of our projects on the short term. Angular 2 is a huge inspiration when we design our components; this will also help in upgrading apps when the time comes.

However, as we are starting to get more and more opinionated on how we think Angular components should look, we are currently in the design stage of some components that we will release into the open. We design for Angular 2 first (mobile first is so passe, ha ha), then backport the design to Angular 1. Check out our Voorhoede Angular Components folder on Google Drive if you would like to take a peek, or better, participate in the discussion.

How about you? Have you tried Angular 2 yet? If you have, I would love to hear how you've experienced it. If you haven't tried it yet but are attending ngEurope, there will be lots of talks about Angular 2. And if you want to get your hands dirty, do try the boilerplate from our workshop. It may be a nice quick start for you too. Exciting times for Angular developers.