Angular 2 is terrible

We used Angular 2 for our frontend. I was not involved in this decision, and I came to work on the frontend relatively late in the project. This post is not meant to be a comprehensive review of the framework, but rather a collection of observations after using it for a little more than two weeks. I don’t claim that using it for two weeks makes me an expert, and welcome any corrections, but for what it’s worth, I consider using Angular 2 one of the biggest mistakes for our project.

Shaky Foundation

Angular 2 stable is built on experimental language features (TypeScript decorators, based on a Stage 1 TC39 draft proposal) and beta libraries (Rx.js 5.0). Let me repeat that: the framework claims to be more stable than its dependency and the language it is built on. That is absolutely bonkers. Angular 2 should not call itself stable when its core dependencies have not landed. Building applications on it is like building on a deck of cards. What would happen if the semantics of the decorators proposal changed, or worse, is withdrawn completely from the language?

Not Invented Here

Angular 2 is a framework in the truest sense of the term. It uses its own module loading system (because JavaScript doesn’t have enough of those already, right?), attempts to abstract away the entire browser platform, and even comes loaded with a complete HTML parser and sanitizer. It even speaks its own language - structural directives, pipes, declarations, modules, injectors, services, view encapsulation, decorators.

This adds a large learning curve to the framework. Even to someone familiar with existing browser API and reactive frameworks need to relearn how Angular does it. Pipes for instance, are almost but not entirely unlike the concept of Unix pipes, or filters seen in traditional templating languages like Twig. Another example: Angular’s HTTP client returns Observables instead of Promises, forcing you not only to learn another async library, but a completely different async paradigm.

This is especially aggravating because despite the documentation’s claims, Observables offers little tangible improvements over Promises when used for Ajax requests.

An Ajax request is singular, and running methods like Observable.prototype.map when there will only ever be one value in the pipe makes no semantic sense. Promises on the other hand represent a value that has yet to be fulfilled, which is exactly what a HTTP request gives you. I spent hours forcing Observables to behave before giving up using Observable.prototype.toPromise to transform the Observable back to a Promise and simply using Promise.all, which works much better than anything Rx.js offers.

Premature Abstraction

Angular likes to pretend it is a platform agnostic platform on which apps can be built.

Learn one way to build applications with Angular and reuse your code and abilities to build apps for any deployment target. For web, mobile web, native mobile and native desktop.

It does so by abstracting away bits of the browser API - Angular replaces the DOM with its own grotesque mutation of HTML, the browser history and location API with its own routing and location service, and XHR and websocket with its HTTP client. Unfortunately this is a ridiculously small subset of the APIs a modern application may need. Any time you need something as simple as LocalStorage, geolocation, notifications, or even just to polyfill difference in browser support for input type="date", you immediately break the promise that your application is cross platform compatible.

More importantly, in many cases this abstraction is completely unnecessary. Some of the Angular platform APIs are merely thin wrappers around browser API, affording no useful abstraction except making it harder to use. The location and HTTP client service are two good examples of this.

The quote above alludes to learning one framework to build apps on all platforms, but the flip side of that is that by learning the framework, your knowledge can no longer be reused anywhere else. Learn to work with the Fetch API, and you can use this knowledge any time you build web applications. Learn Angular’s HTTP client, and that knowledge is useless outside of the (much smaller) Angular universe.

HTML Minus

Angular claims to be HTML Plus - that is, HTML, but better. It is wrong.

First, Angular HTML is not HTML. HTML attributes are not case-sensitive, while Angular’s are. This may seem like a small inconsistency, but it means any tooling built to support HTML cannot support Angular’s version of it. WebStorm’s code autocomplete, for instance, suggests ngif instead of ngIf - the two are equivalent in HTML, but not Angular HTML.

Second, Angular HTML’s syntax is poorly designed, far more complex than it needs to be, and is filled with little gotchas - just look at the number of red warning boxes on the template syntax reference. Here are some things the guide doesn’t mention.

<div *ngIf="condition">...</div>
<div template="ngIf condition">...</div>
<template [ngIf]="condition"><div>...</div></template>

The above are equivalent, but god forbid you mix up the syntax - the following will simply silently fail with no warnings:

<template *ngIf="condition">...</template>

The reason is because the * you see is a syntactic sugar that essentially wraps the element in a <template>. The problem is when used on <template> tag this gets translated into:

<template [ngIf]="condition"><template>...</template></template>

which doesn’t render anything at all. Vue gets this right with a single v-if directive for both template and normal tags that works as you’d expect it to.

Angular’s ngFor uses a microsyntax. I’ve never seen another templating language that needs a DSL for for loops.

<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>

The let is misleading - you can’t actually do arbitrary assignments in these expressions, which makes you wonder why they bother with requiring it at all. Vue’s syntax, for comparison, is much simpler and saner:

<li v-for="(item, index) in items" :key="item.id">...</li>

Angular pipes (which everyone else calls filters) can take in parameters, but for some multiple parameters are separated by colons, not comma, unlike just about every template language I’ve used. Why? I have no idea.

<p>{{ name | slice:1:5 }}</p>

All of the above are syntax problems - annoying, but fixable. This final problem though is a design problem that goes to the heart of Angular’s philosophy. Angular attempts to graft much of its application logic - logical flow, event and property binding, directives, references onto the HTML markup language. I’ll let the following piece of code from our codebase speak for why this is a horrible idea.

<ul class="session-container-list">
  <li class="session-container"
    *ngFor="let container of containers"
    [class.empty-container]="container.sessions.length === 0" (click)="container.sessions.length === 0&&!isPublic&&!isAnalytics&&addNewSession(container)" [style.cursor]="(!isPublic&&!isAnalytics)?'pointer':'auto'">
    <ul *ngIf="!isPublic" class="session-list"
        [dragula]='"column"' [dragulaModel]='container.sessions'
        data-column-type="absolute"
        [attr.data-container]="getContainerData(container) | json">
      <li class="session-wrapper"
        *ngFor="let session of container.sessions"
        [attr.data-session-id]="session.id"
        [attr.data-session-placeholder]="session.placeholder"
        [attr.data-session-start]="session.start?session.start.toISOString():''"
        [attr.data-session-duration]="session.duration"
        [class.placeholder]="session.placeholder">
        <my-session [session]="session" [offsetDate]="offsetDate" [agenda]="agenda" [isPublic]="isPublic" [isAnalytics]="isAnalytics"
          (onSessionEdited)="onSessionEdited($event)"
          (onSpeakerEdited)="onSpeakerEdited($event)"
          (onSpeakerAdded2)="onSpeakerAdded($event)"
          (onVenueEdited)="onVenueEdited($event)"
          (onVenueAdded2)="onVenueAdded($event)"
          (onSessionDeleted)="onSessionDeleted($event)"
          ></my-session>
      </li>
    </ul>
    <ul *ngIf="isPublic && container.sessions" class="session-list">
      <li class="session-wrapper"
        *ngFor="let session of container.sessions"
        [class.placeholder]="session.placeholder">
        <my-session [session]="session" 
                    [offsetDate]="offsetDate" 
                    [agenda]="agenda" 
                    [isPublic]="isPublic" 
                    [isAnalytics]="isAnalytics" 
                    *ngIf="session.toggle"
                    [token]="token"
                    [interested]="isInterestedInSession(session)"
                    (onSessionInterestEdited)="onSessionInterestEdited($event)"
                    [analyticsData]="getAnalyticsDataForSession(session)">
        </my-session>
      </li>
    </ul>
  </li>
</ul>

To be fair, having logic in template is not necessarily a bad thing - I’m not advocating for a purist approach like Mustache, and I find the amount of logic in most templating language acceptable.

The problem is with the way Angular template allows for, and even to some extend encourages the binding of everything onto the components. This makes it easy to slowly build up layers of event emitters and inputs until you get the result seen above, instead of using a properly planned out data model for the application. Now most of our components are a mess of event handlers, emitters and properties with extremely poor separation of concerns, and whenever we need to reuse a component, we usually copy and paste the code

Unnecessary Verbosity

Consider the component, the basic building block in most modern JavaScript applications. One of the best thing to have happened to frontend development is the idea that the web applications should be organized into independent, reusable components. Ideally this means that components should be easy to create. Angular’s are the exact opposite of that.

To create a component in Angular, you have to create separate files for JS, CSS and HTML. Depending on the setup, the JavaScript may need to be compiled from TypeScript and the CSS from SCSS or LESS, so that’s five files just for a single component. With that many files, you need a separate folder for each component. The component itself is a class decorated with a @Component decorator, declaring component metadata such as the styles, template, and a selector so that the component can be reused elsewhere. You then inject the component dependencies through the constructor, and remember to declare the component lifecycle interfaces. Finally you need to declare the component in the app’s module file. That is a lot of work just to set up a single component.

Compare this with Vue’s single file components. For Vue, the markup, style and logic gets declared in a single file. The component is expressed as a simple object. There is very little ceremony, making it easy to create new components.

Single file components are also possible with Angular, but you need to declare the markup and styling in TypeScript itself, and it is (as far as I know) incompatible with preprocessors such as SCSS which we need to manage our CSS variables.

Is the difference significant? I’d argue yes. Our Angular app has 21 components, while our Vue app had over 30, even though the latter is significantly less complex. The Vue components were small and lean, while the Angular components grew uncontrollably because it was easier to pile on more things into a single component than to extract them into separate components.

Poor Performance and Bloat

This may be due to our particular setup, but Angular 2 feels exceptionally heavy. The Vue based application I built for assignment three rebuilds itself in a blink of an eye during development - by the time I switch workspace to my browser often the page has already reloaded and rendered. Our Angular 2 app on the other hand struggles with this most basic of tasks, taking several seconds to rebuild and render. This may sound small, but remember that any change to the source code result in a reload, so this is something that has to be done hundreds of times per day.

Our Vue app also comes with hot module and style reloading in development mode, while the Angular app comes with neither, further slowing things down. Although these may be possible to configure for Angular 2 for hot module reloading, the Vue boilerplate is already configured for this by default out of the box.

Similarly, while our Vue app does template rendering ahead-of-time (AoT) out of the box, Angular 2 has to be configured to do so, and requires all dependencies to support the same. The latter is actually more significant - while it is (relatively) simple to configure your own app to support AoT compilation, requiring the same for all dependencies is painful. This also contributes to size bloat. The initial size of our app is 1MB.

$ ls -sh *.js 
8.0K app.4fda0a42cad7fc187df1.js  
100K polyfills.4fda0a42cad7fc187df1.js  
908K vendor.4fda0a42cad7fc187df1.js

One. Megabyte. That’s the size of app with only vendor and polyfill files. It is also the size of all JS used in our entire assignment 3 app. That includes more than 600kb used by Three.js and the OSM Building library. 0.js contains Three.js while 1.js contains OSMB.js. Both are very large JS libraries designed to do 3D rendering in browser, and even with both included our app is still smaller than what we started with when using Angular.

$ ls -sh *.js 
496K 0.932d135e27de51cdc9e7.js  116K app.f5d63e42fa964e3f9b04.js
128K 1.09b1205dc52803a6ab90.js  8.0K manifest.e0de65d72b1053e2c121.js
8.0K 2.71a0679fdbcabd1595e7.js  236K vendor.ab341f6d4eec7db5a1a3.js

Vue’s own frameworks comparison page notes that a ‘Hello World’ Angular 2 app can be as slim as 50kb after tree shaking. However, remember that number only comes after removing every component that’s not needed from the build, while Vue 2.0 comes in at 23kb with everything bundled. In other words, 23kb is the maximum for Vue, while 50kb is only the minimum for Angular.

Putting the Java back in JavaScript

I was trying to understand why the profiler output for our app looked less than a flame graph and more like a piece of abstract postmodern art…

…when I found this article discussing Angular 2’s change detection strategy.

By default, even if we have to check every single component every single time an event happens, Angular is very fast. It can perform hundreds of thousands of checks within a couple of milliseconds. This is mainly due to the fact that Angular generates VM friendly code.

I’m not sure about you, but that bold part sounds crazy. Angular treats the browser’s JavaScript engine as a VM, not entirely unlike what Java does. What this means, in addition to making stack traces three pages long and profilers useless, is that the framework’s performance is entirely at the mercy of the engine it is running on. While there is only one JVM, there are half a dozen JavaScript engines, each with their own performance profiles. I can only assume the code generated by Angular will be “VM friendly” on all of them. Hopefully.

This is not where the similarity to Java ends. Angular is a not a JavaScript framework, but a TypeScript framework. TypeScript likes to pretend it is safe because it is strongly typed. This is somewhat true, but TypeScript suffers from the exact same problem Java does - it is not null safe. Worse, because TypeScript has no runtime component and emits no warnings for many unsafe casts, the language only offers the illusion of safety. For instance, this is perfectly valid TypeScript code that raises no warnings.

private static extractAgenda(agenda: any): Agenda {
  return agenda;
}

Terrible Documentation

Angular 2 has the worst documentation for a major framework I have ever seen. It is sloppy, incomplete, and badly written. For starters, Angular is a JavaScript framework that has no documentation in JavaScript. The quickstart page says:

Although you’re getting started in TypeScript, you can also write Angular applications in JavaScript and Dart. Use the language selector in the left nav to switch development languages for this guide.

This is a lie. The JavaScript documentation page has said it is coming soon since the framework was in alpha, beta, release candidate, release, and even the 2.1 release. As of writing it is still empty.

Most of the documentation has been written for TypeScript developers and has not yet been translated to JavaScript. Please bear with us. Meanwhile, we’ve provide links to the TypeScript chapters where JavaScript versions are unavailable.

To add insult to injury, the utterly useless language picker on the navigation menu actually hinders navigation by hiding the last menu item when the menu gets too long. This is the sort of bugs you’d expect on the homepage of small libraries built by lone maintainers, not a framework built by the largest tech company in the world that thousands of developers around the world depend upon for their livelihood.

The documentation is poorly organized. The writers split it into Guide, Advanced and Cookbook sections, but the distinction is extremely arbitrary. For instance, the guide on forms and the template syntax reference is under Guide, while the routing and navigation guide is under Advanced. Ahead-of-Time compilation is somehow a topic under Cookbook, as is form validation. None of these make any sense, and even worse, opening up the second level menu takes a full page reload, making finding information in the documentation a painfully slow process.

Guide pages are numbered, but these numbers have no meaning - ‘Forms’ is before ‘Template Syntax’, even though the two-way data binding syntax used in the former is only introduced in the latter.

The individual guides are themselves poorly written. Here’s a paragraph from the guide on Pipes:

Let’s write one more impure pipe, a pipe that makes an HTTP request to the server. Normally, that’s a horrible idea. It’s probably a horrible idea no matter what we do. We’re forging ahead anyway to make a point. Remember that impure pipes are called every few microseconds. If we’re not careful, this pipe will punish the server with requests.

The writing style is completely insufferable. Only the first and last sentence say anything useful, and it contains a major technical error - the writer likely meant milliseconds instead of microseconds, a three order of magnitude difference. There are typos throughout the page:

The component doesn’t have to subscribe to the async data source, it doesn’t extract the resolved values and expose them for binding, and the component doesn’t have to unsubscribe when it is destroyed (a potent source of memory leaks).

Despite the two bindings and what we know to be frequent pipe calls, the nework tab in the browser developer tools confirms that there is only one request for the file.

The entire documentation reads as if it was written and coded by an intern who wasn’t paid enough to care.

The Future of Frontend Development!?

When I was introduced to Angular 2 I hoped it would be an elegant framework that is on par with projects like Laravel and Django that makes frontend development easy and enjoyable. What I found is an unstable, bloated, over-engineered framework that promises much but delivers little.

Please for goodness sake don’t use Angular. For less than one-tenth of its size, Vue.js delivers a much better development experience.