BazaarJS: our criticisms of Angular

Angular is now, without doubt, the Javascript Framework par excellence®, with popularity far exceeding that of any of its competitors. But is all that shines really gold? In this latest post in our #BazarJS series, which looks at the development of Single Page Applications, we will describe our personal experience with the framework.

Estimate reading 12 minutes

Hey guys, we are talking about Angular 1.1!

This is a translation of a post written more than a year ago on our experience with Angular 1.1. We understand that some of these issues have been solved on Angular 1.4, and we are defenitly happy for this!

We are truly sorry if this was not clearly explained.

Angular describes itself as a toolkit for improving HTML, one which makes it possible to extend HTML itself using new vocabulary known as directives. Directives transform a static document into a dynamic template, thereby minimizing — and sometimes eliminating entirely — the need to write Javascript code.

Angular is without any doubt the most popular front-end framework amongst those currently available on the market and is supported by a internal Google team, giving it instant credibility. It is so popular that it deserves its own acronym… and in fact forms part of the MEAN stack, composed of MongoDB, Express, AngularJS and Node. It is no accident that knowledge of Angular is a skill currently particularly sought after on the job market.

Here at LeanPanda, we’ve been working with Angular for almost nine months now (the original article in italian is dated February 2015): enough time for us to have a gotten a reasonably complete picture of its advantages and disadvantages. Rather than produce yet another Angular tutorial, we have decided to get straight to the point and set out what we see as the major criticisms of the framework.

Problem #1: Scope inheritance and dynamic scoping

This is without doubt the most frequent and unnerving problem for any developer who finds themself using Angular. Let’s take these lines of code as a reference point:

<input type="text" ng-model="obj.prop" />
<div ng-if="true">
  <input type="text" ng-model="obj.prop" />
</div>

Question: in the second input tag, does obj.prop refer to the same variable as the first input? The answer, unfortunately, is that it is impossible to say with certainty just by reading the code. It depends on the state of the program at runtime. You don’t believe us? Try it for yourself: if you start writing inside the first input, the variable will be shared by both of them. If you start writing inside the second input, the two will be independent.

How is that possible? It’s caused by the way in which Angular manages variable scoping. ng-if is a directive that introduces a new scope, which prototypically inherits the most external scope.

Writing for the first time in the first input, the obj.prop variable is initialized within the external scope, meaning that — thanks to prototypical inheritance — it is also propagated to the inner scope. And vice-versa by writing for the first time in the second input, the variable is initialized within internal scope without being propagated in the external scope.

Simplifying and abstracting the concepts in play from the details of the Angular implementation, we get something like this:

Every time I find myself with the (unsought after) task of having to explain this situation to new developers I can’t help asking myself: “Is there any reason for this?” Fortunately, I don’t have to come up with the answer all by myself: the topic has been formalized and discussed for decades.

Lexical scoping is defined as scoping that can be determined by reading the source code alone. When, on the contrary, scoping depends on the state of the program, it is defined as dynamic scoping. Returning to our question, the response that dozens of people have given is “No: dynamic scoping doesn’t make any sense at all”. Almost all modern languages implement lexical scoping, because its far more predictable and easier to manage.

Problem #2: Dirty checking

Angular supports so-called data-bindings. That is, the ability to connect fragments of the DOM to Javascript variables. Once a binding has been imposed, it is possible for a modification to a variable to have an impact on the DOM without any intervention on the part of the developer.

Angular certainly isn’t the only framework to support this concept: the particularity lies in the way in which Angular achieves this result. Let’s suppose we want to generate a simple timer:

The modification of the value of a scope variable is propagated on the view only after Angular has been notified of the desire to apply it with a call to $scope.$apply(). To avoid filling the application code with these kinds of calls, Angular makes a comprehensive library of objects the only scope of which is to call $scope.$apply() for us at the correct time. In our case, we can make use of the $interval service:

Imposing a service as $interval from above in this way is questionable in and of itself, but the fundamental problem is the following: Angular, not knowing precisely which and how many variables have been changed when the final phase of rendering carried out, is forced to perform a dirty-check of every binding activity in the page, to identify any values that differ from those previously used in the last rendering. If such changes are present, the associated change-listeners are triggered, which may themselves cause changes to the scope! In this fashion, the process repeats itself until an absence of changes has been detected.

You don’t need a masters in computer science to realize that this results in an extremely costly and non-optimized operation. A single minor modification to the scope can trigger many hundred or thousands of checks within the application. It is for this reason that the Angular community has always stated that it is good practice to limit the total number of bindings present in an application to 2000. If this threshold is breached, good performance can no longer be guaranteed.

To avoid any misunderstanding, it’s important to bear in mind that this set-up was a conscious choice made by the Angular team, who were trying to find an “acceptable” trade-off between performance and ease of writing for developers. The problem is that it’s very easy to go over this theoretical limit in applications of average complexity, and up until version 1.3 of Angular, there weren’t any official alternatives in place to work around the problem.

ECMAScript 7 introduces the Object.observe() method, which is able to hook change-listeners onto the value changes in simple Javascript objects, offering a better-performing alternative to the current dirty-checking approach… but we still don’t even have a possible publication date for the new standard, so it’s anyone’s guess as to when the functionality will actually be implemented in-browser.

Problem #3: Dependency injection

Amongst Angular’s most striking features is the fact that it brings with it its own system of dependency management, based on the concept of dependency injection:

var myApp = angular.module('MyApp', []);

myApp.factory('sum', function() {
  return function(a, b) { 
        return a + b;
    };
});

myApp.controller('MyCtrl', function ($scope, sum) {
  $scope.foo = sum(1, 3);
});

The Angular injector is able to carry out introspection on the names of the parameters passed to the functions that define a module, and to make their related dependencies available.

So, what’s the problem? Well, in a previous article we have already explained the details of Javascript’s two standard module loading mechanisms: CommonJS/AMD. Angular forces you to use a third, custom designed alternative mechanism: one that is inferior to the already existing mechanisms available.

Angular’s system of dependency injection starts to get tiring as soon as you begin minimizing your code, and find yourself forced to invent an uncomfortable alternative syntax to manage the problem. And, more importantly, it makes it cumbersome for tools like Browserify or Webpack to do what they were designed to do:

  • manage the dependencies of Angular and other third party applications (such as Bower and npm) inside our application;
  • subdivide our application’s code into bundles that can be downloaded by the client asynchronously.

Problem #4: Pointless complexity

I remember very well the feeling of depression and suffering that I experienced during the first (of countless) visits I have made to the Angular service objects page. Oh dear God… providers, values, factories, services, constants…? What’re they for? Why do you need 2000 lines and 5 different modes to define an ordinary Javascript logic module?

After days of research and experimentation I came to the tragic conclusion that there aren’t any major differences between them: they’re all pretty much the same thing. All five concepts could easily be assigned a single identity. I’m not joking: we were perfectly able to get by in one of the Angular projects developed internally — consisting of around 200 Javascript files and a total of around 10,000 lines of code — using only factories.

This is not the only way in which Angular seems to be wilfully trying to be as incomprehensible as possible. Other examples?

  • What does it mean to create an E directive or an A directive? Never mind an EA directive?
  • Surely there is a more intelligible and semantically coherent way of describing the modality of a variable’s bindings than using symbols like =, &, =* and @?

Problem #5: Server-side rendering

Angular’s choice to use a page’s HTML content as a templating language — using “directives” embedded in the form of classes, attributes or tags — in addition to presenting an ample number of “minor” problems, also brings with it one insurmountable obstacle: the inability to produce isomorphic applications.

A large part of the logic of an Angular application resides in the HTML of the webpage itself, in the form of ng-ifs, ng-repeats and suchlike. This means that it is simply impossible to make an Angular application work server-side, by design.

Services like prerender.io make it possible to get around the problem, but the result is most certainly a workaround; one which can itself give rise to problems related to cache-expiration.

Problem #6: Angular 2

Not convinced yet? Well, what if we were to tell you that the next major version of Angular will take a scorched earth approach to the existing structure that means it will have zero retro-compatibility with what exists today?

Five years after its first public release, the developers of Angular have learnt a lot of lessons and realized that the abstractions that the framework is based on today are insufficient and confused. They have therefore made the decision, which they announced at ng-conf, to write the next major version of the framework from scratch.

This is a courageous choice, and all considered an entirely laudable one, given that Google have in any case guaranteed a long period of maintenance for Angular 1.x. We faithfully await the publication of this new project (which will probably share only its name with Angular’s current instantiation)… in the meantime, however, it would be simply foolish to use Angular 1.x for new applications.

Conclusions

Despite the unflattering words we have so far used to discuss Angular, it is important to underline that the framework nonetheless provides an acceptable option for the production of client-side applications.

Its huge popularity has led to the creation of an enormous number of third party modules, which can be extremely useful for quickly putting together working prototypes.

Once you’ve got past the initial learning curve, have found you own “formula” for structuring code and have learnt how to manage the idiosyncrasies of directives, scopes and ng-models, Angular offers you everything you need to be able to build applications within acceptable time-frames.

Let’s now return to fundamental question: is it worth it? The answer is “no”, at least for us. That said, we don’t just want to be negative, so we feel we ought to offer you an explanation of what we think is a valid alternative.

Next episode? React!

With the next article we’re going to introduce you to React JS, which takes an approach to the problem of SPA construction that is entirely different to the one taken by Angular — or any other library or framework developed in the past — and is in many ways a creature entirely alien to the world of Javascript. Alien enough to have won us over entirely.

Follow us on Twitter or subscribe to our RSS feed to keep up to date!

Did you find this interesting?Know us better

Made with Middleman and DatoCMS, our CMS for static websites