Things that Excite Me about Angular 2

Posted on by Tero Parviainen (@teropa)

In the past six months or so I've spent a good amount of time playing with Angular 2. I've built some fun projects and given talks about them. I've written some documentation and had lots of discussions with other people who are doing that. And I've written a couple of articles too. I thought this might be a good time to sum up what I find exciting about this platform right now.

TypeScript

I wasn't interested in TypeScript before the Angular community started making noise about it. I'd heard of it and seen some examples, but I just didn't see why it would matter to me.

One reason is that I've been working almost exclusively in dynamically typed languages (JavaScript, Clojure, Ruby) for a long time and have grown used to not having type checks in my code or autocompletion in my editor. My brain has learned to work without these things. I could see why these have been selling points in TypeScript for many people, but they weren't for me. I just didn't see them as worth the added hassle that comes with the extra tooling and the need to find and learn type definitions for the various JavaScript libraries I'm using.

On the other hand, I've been intrigued by the purely functional approach taken by Elm and PureScript for a while now. The purist in me tends to think that if I'm going to reach for a statically typed language, why not choose one that goes all the way? The type systems in these languages can do much more than TypeScript can. Perhaps most importantly, they can tell you about the absence or presence of side effects in your code. This provides clarity and safety on an entirely other level from merely being able to tell you if foo has the attribute bar or not.

So TypeScript struck me as the kind of compromise I didn't want to make. But then I started using it in some of my projects and something happened. I was ostensibly programming in TypeScript but just kind of forgot it was there. There wasn't any big change from how I had been writing JavaScript for years.

I think this is a key realization about TypeScript. People keep saying "it's just a thin layer on top of JavaScript" and when I kept hearing it, it kind of made sense. But somehow I still had to experience it first hand before it clicked. It is just JavaScript - just with the seat belt fastened. It doesn't really make sense to compare it to other languages.

What has also happened though is I've started spotting issues in my JavaScript projects where I've thought "we could've caught this bug earlier if there had been a TypeScript interface here". This is especially the case when you have nested data structures that you're passing around.

function convertData(data) {
  // OK, wtf is "data" again?
  // Was it an object of arrays of objects?
  // Or an array of objects of arrays?
  // Let's console.log this...
}

vs.

interface Datum {
  name:string,
  items:{id: number, description: string}
}

function convertData(data:Datum[]) {

}

So you kind of start missing that thin layer of safety measures you get from TypeScript. To me that is a sign that the tool is actually useful for me. So I'm going to keep using it. I'm also going to play with Elm but I'm no longer comparing it to TypeScript per se.

There's definitely still a tooling overhead that comes with TypeScript and sometimes it trips you up with weird error messages and things. But this seems to be a worthy tradeoff, and it's actually quite painless in new Angular 2 projects that you kickstart with the Angular CLI and don't have to spend time configuring.

A Sane Data Flow

In Angular 1 there traditionally hasn't been a "data flow" to speak of. There are scopes, and on those scopes there's a web of bindings and watches that shuffle data and events around. In some apps that web is neat and tidy. In most of the real-world apps I've seen it's a hot mess.

With React came a more reasonable way of approaching UI data flow: You've got a hierarchy of components. Data passes down that hierarchy, and change events pass up the hierarchy. That's 90% of what happens, and everything else is an exceptional case.

Angular 2 does this too. Every component has clearly defined inputs and outputs, through which data flows in and actions flow out. There are no scopes or inheritance hierarchies that circumvent this.

There are also different change detection strategies that let me choose the most efficient way to check for changes: If I've got immutable data structures or Observable inputs, I can use OnPush. If I know things are never going to change as long as the component exists, I can use CheckOnce. It's kind of like React's shouldComponentUpdate but all nice and declarative.

This means that if I want to use Redux and persistent data structures, the framework won't fight me. There are actually bindings for Redux that make it real easy. Or if I want to go for a fully RxJS based data flow, I can do that too. There are also interesting things being done integrating Relay into Angular 2 components.

There's one piece currently missing from Angular 2 that I really want to have: Hot loading.

Redux is a very nice architectural pattern, but if you listen to what Dan Abramov has said about the origins of Redux, you hear that the motivation wasn't really to have "a nice architecture". It was driven by the practical goal of enabling hot loading. That is, the ability to get a feedback cycle where you can change code while the application is running without having to restart the app or lose the UI state you have in your components.

When you've gotten used to this kind of turbocharged feedback cycle, you don't want to go back. This is why we need to have it in Angular 2 as well.

Luckily there's no reason why this can't happen. Minko Gechev did some experimentation on this early on, and I believe he's also currently thinking about taking it forward.

Static Template Compilation

When you run an Angular 1 application, there's a lot of code involved that has nothing to do with your application logic: HTML templates get fetched, parsed, and attached to the DOM. Directive elements and attributes and interpolation expressions are searched by walking over the DOM. All of the expressions are parsed and compiled into JavaScript, and watchers are created for them.

None of these are things that have to be deferred until runtime. All the template and JavaScript code already exists in the application source tree, so we really have all the information we need to process the templates at build time. The platform architecture just needs to support it.

Angular 1 does not support this, but Angular 2 does. It has a static compilation mode, where all your templates are processed at build time. It takes the HTML and generates TypeScript code from it. For all the expressions in the templates it generates monomorphic change detector code, with the exact minimal DOM manipulation that's required to apply each change to the page.

The code generated here is the kind of code you would write if you were to do it all manually in order to get maximum performance gains. It leaves no runtime framework performance overhead whatsoever. It's screaming fast and it doesn't need a virtual DOM to get there. It's also code that JavaScript VMs can then go and inline if it becomes hot.

Listen to Miško talk about this at ng-conf:

Another optimization Angular 2 can now do has to do with code size. When you use a framework like Angular, it doesn't mean you actually need every single line of code that comes with it. There may be hundreds of functions that are part of the packages you install but that never actually get executed by your application. It makes no sense to ship them to users, but it's often hard to eliminate them because of the dynamic nature of JavaScript, and also because it's difficult to know what exactly gets called from HTML templates.

With Angular's static compilation mode this too is different. Angular's compiler knows everything about all the TypeScript and template code that is included, and thus can aggressively eliminate dead code paths. This is called tree-shaking minification and it results in a much smaller amount of code in the final application build.

This kind of whole program optimization is by no means a new idea. It has been around for quite some time in other technologies. It reminds me of what Google Web Toolkit was doing almost a decade ago with its optimizing compiler. It's also always been possible in ClojureScript, which piggybacks on the Google Closure Compiler whose advanced optimization mode does this kind of thing. But it hasn't been viable in any of the current crop of mainstream JavaScript frameworks as far as I know.

Web Worker Support

Most of the devices we use these days have multiple CPU cores in them. That means they can process more than one thing at the same time. If you're writing native apps, you can use threads or similar concurrency primitives to make use of that in your code.

On the web things aren't quite so simple. JavaScript doesn't have threads. But it does have Web Workers, which allow launching background processes in browser applications.

The thing is though, using Web Workers isn't always straightforward. Web Workers are independent processes that don't share any state with the UI, or each other for that matter, nor do they have any kind of access to the DOM. You can only communicate with Web Workers using asynchronous JSON message passing. This is a perfectly usable concurrency model, but it does mean that you can't just take your web application code and stick it in a Web Worker and expect it to work.

That is, unless you're using a platform that is built with Web Workers in mind. A platform such as Angular 2. You can configure Angular 2 so that most of your application code runs in a Web Worker. This includes things like data flow and change detection. The only thing left in the UI "thread" is the stuff that has to be there: DOM access. Angular takes care of the messaging between the worker and the UI.

This is something you can do as long as you're willing to stay within the abstractions Angular provides, which mainly means keeping the DOM itself at an arm's length and only accessing it through sanctioned APIs.

Not all applications will benefit from this. If all you're doing is data entry through forms, you don't need to do anything in the background. That's why the Web Worker support is not enabled by default. You need to opt in.

I'm probably not going to use this for most of the apps I write, but I'm glad it's there when the time comes that I need to do some more involved data crunching. I'll be able to move it to a background worker without sacrificing UI responsivity or complicating my code.

Server-side Rendering

Angular 2 ships with a full solution for universal rendering, which means that the initial view of the application can be rendered on a server and shipped as fully populated HTML. This means that

Now, I personally mostly work with B2B applications that a) Are behind a login barrier and b) Are typically used in a context where it's OK to wait for a second for the initial load. For these reasons, universal rendering isn't on the top of my priorities. I'm unlikely to use it in these kinds of apps because of the added architectural complexity. But just like with the Web Worker support, I do like the thought that it's there when the day comes that I need it.

What's also pretty cool is, as Brad Green mentioned in one of the ng-conf keynotes, the Universal renderer will be able to run on platforms other than Node.js as well. This will be especially interesting for large enterprises based on pure Java or .Net stacks, whose operations people may not be that keen on deploying Node servers. If Angular Universal can run right in the Nashorn engine built into the JVM, it fits more naturally to the server environments that these organizations already have. It can also be more easily integrated to existing Java serverside apps because you'll be able to run it all in the same process.

Lazy Loading

When an application gets large enough, it's likely to accumulate a hefty amount of JavaScript code, CSS, and assets. This in turn will make it slower to load. All that code and those assets need to be transferred over the network and processed by the browser.

For this reason, it is useful to be able to load parts of the application lazily - to only load some part of the UI when the user actually needs it. For example, an application I'm working on right now has a separate "admin UI" where privileged users can do administrative tasks. There's a good amount of code behind that admin UI. And the thing is, all that code is loaded for all users, most of whom will never actually see that UI. It would be nice to only load the code if and when it's actually needed.

In Angular 1 this was never easy. Though there have been community-maintained packages such as Olivier Combe's ocLazyLoad, these packages have had to go to extraordinary lengths to make it all work. It just isn't part of the Angular 1 architecture to be able to, say, add new services to the injector after the application has started.

This all changes in Angular 2. It comes with a router that supports lazily loading the code for routes only when they're first accessed. This is supported by the hierarchical dependency injection system, where dependencies that are only used in a certain route are also only loaded together with that route's UI components.

Lazy loading in Angular 2 is not a hack, but something that the platform is designed to support, which will make it much more easily accessible.

Component CSS

This is kind of a small feature but it has a surprisingly big impact: In Angular 2 we put all UI code into components, and this includes not only the TypeScript classes and an HTML templates that define the components, but also the CSS that is used to style those components.

A big chunk of the time that we spend styling a UI component is spent authoring CSS that is specific to that particular component. For this reason, it makes perfect sense to bundle the CSS up with the rest of the component code. That's exactly what we do in Angular 2: We can define component styles for each component.

This support goes beyond just co-locating the files though: Using a feature called view encapsulation, Angular actually processes component CSS code so that it is scoped to the component template and cannot bleed outside it.

This means that if I want to use a class like .selected for styling, I can use it without worrying about where else in the application such a class may be used, and whether it's styled in a different way there. I don't have to come up with globally unique naming schemes for things, making some parts of methodologies like BEM unnecessary. For someone like me who has chronic issues with getting their CSS organization practices in order, this is great news.

What happens here is somewhat similar to CSS Modules. Both Angular Component CSS and CSS Modules provide similar local scoping guarantees. Angular's CSS support doesn't have the dependency system that CSS modules have, though it is certainly possible to share stylesheets between components. On the other hand, what Angular is doing is based on the Shadow DOM standard, so instead of proprietary syntax you're only using things that will be natively supported by browsers sooner or later.

Animations

There are two main alternatives that browsers give us for doing animations natively. Both of them have their tradeoffs:

  1. With CSS Transitions we can easily declare how style changes in elements are animated over time. But we have very limited programmatic access to this. Everything needs to be predefined in CSS and there's no way to influence a running transition. If you only know the values of your animated properties at runtime, you're out of luck.
  2. With the Web Animations API we get full control over animations, because we trigger them from JavaScript code and can control them after the fact. But in this case we can't do anything with CSS stylesheets. We need to define animation styles in JavaScript, which is rarely where you want to be putting styles.

The Angular 2 animation engine gives us the best of both worlds. It builds on top of the Web Animations API, but integrates tightly with Angular's component stylesheet support so that we can define our animation styles there.

What's really great is the way these animations integrate with the rest of our application code. Even though we define animations declaratively using CSS and Component metadata, we can get the kind of dynamic runtime hooks that are usually missing:

Native App Support

I'm a web developer. I've done my share of native app development on iOS, Android, and Windows Phone, but I choose the web whenever I can. I'm happy to see the web being a good option in a widening category of situations, most recently boosted by the whole progressive web apps thing, which Angular 2 embraces.

That being said, there are some situations where I find native a more suitable option. I've got an application in the pipeline right now that will use the device camera a lot and incorporate a bunch of image manipulation code. That's the kind of stuff I can't really do that well on the web platform right now.

That's why it's nice to have tools for doing native apps that are more geared toward a web developer's sensibilities, such as React Native and NativeScript. In multi-platform apps you might even be able to share chunks of code between platforms.

What's even nicer is that these platforms are fully supported by Angular 2. It's been designed to be a multi-platform tool that runs equally well in browsers and native platforms. This means I can write actual native apps with full native capabilities while using Angular's change detection, dependency injection, directives, components, pipes, services, animations, and testability support. That's pretty cool.

Conclusion

A lot of the focus on Angular 2 so far has been about how its APIs are different from Angular 1, or from React, or from whatever else people want to compare it to. People spend a lot of time talking about the relative merits of HTML templates vs. JSX, and DI vs. no DI, and TypeScript vs. Babel.

These are all interesting debates, but there's a lot more to these application platforms than that. What's also important is what kinds of features the platforms choose to include and exclude, and how they combine those features. Angular 2 is cooking a pretty compelling mixture and I'm planning to try it out in at least a couple of projects this year.

comments powered by Disqus