Why you should not use AngularJS

mnemon1ck
11 min readJan 2, 2015

--

Much time has passed since AngularJS birth (given the fact how fast front-technologies evolve). Now on the internet, there are a huge amount of posts praising this framework, and the critics are not so many as it deserves. But such posts are slowly beginning to appear, and it pleases me. I hope industry will give up AngularJS, as it gave up MooTools, Prototype, %some new JVM language%, %another-super-modern-technology%. I don’t know why, but in the IT field such revolutionary technologies that raise the noise and then disappear appear quite often. A good developer should be able to distinguish another fancy technology from the running tool. In order to do this it is very important to look at things critically. My post is a compilation of the most important conclusions from other posts and my personal thoughts.

Angular creates a good “wow-effect”, when you see it for the first time: “wow, I wrote ng-repeat, and implemented this logic only with tags, and it updates by itself!”, but as soon as you have to implement a real application, but not another TODO-list, then it all becomes very frustrating. I just want to say that I know the framework well, even better than I would want to know it. I’ve been using it for two years already. So what is wrong with AngularJS? There is no definite answer, as there are too many flaws that create the appearance of the framework. One would say it is ill-conceived architecture.

Two-way data-binding

There is a fundamental rule in programming, it says that explicit is always better that implicit. All these $watch’s is an implicit invocation of event handlers. In reality, events occur when a user clicks the item, not when the model changes, so when you write on AngularJS you need to think the following way: “the user clicked the button and the model has changed, now we need to listen to changes on this model and call the handler” and not “the user clicked the button and call the handler”. It’s not how how events are handled in principle. This is the same as when you fetch data from the database and write these data into the model, and only by the change of the model callbacks are called. It’s nonsense! Even in Java, a language which has no anonymous functions (when I was writing in it) callbacks were implemented with anonymous classes. Because it is explicit and because this concept describes the real situation. Even the next version of EmberJs will not use two way data binding.

Also two way data-binding means that changing anything in your application will trigger hundreds of functions that monitor changes. It’s slow, and it is especially all turns bad on mobile platforms and when you write something something complex. And it is a fundamental part of the framework. Angular even imposes restrictions on how rich UI you can write. And what is most interesting is that this is not some ephemeral or distant limit you will never experience. It’s only 2000 watchers, and if you develop more or less large application, you will undoubtedly run into this limitation. And then you will have to fight with the framework. Angular introduces the possibility of one way data-binding, to avoid performance problems. Create a problem and solve it with a hack (ill-conceived architecture#1). Take a look at how many people who are struggling with the performance of angular. Even if you google “angular performance” you will be astonished with the number of articles describing how to accelerate AngularJS. Isn’t it a sufficient argument in favor that angular is slow?

There is another point, sometimes you need two way data-binding, but UI is already slow. For example, when the user loads the page he may see {{ expressions in brackets }}. Hmm… it’s not a bug it’s a feature! We need to introduce a directive, which will hide the UI so that user won’t see flickering, and won’t distract the framework from solving its fictional problems. And for these purposes AngularJS introduces new directives: ngCloack, ngBind. These problems wouldn’t happen in the first place if a more nimble framework was used which immediately shows data as soon as they appeared. In return it takes time for angular to display these data (in additional to rendering time). AngularJS again creates problems and solves them with hacks (ill-conceived architecture#2). And if the framework has such problems with scalability, doesn’t it mean that something is wrong at the fundamental level? Concise and well written technologies scale well as a rule.

Dependency Injection

The next example of how angular makes you suffer is Dependency Injection. In Angular dependencies are injected by the name of an argument.

function MyController($scope, $window) { // … }

Here Angular calls .toString(), gets the names of the arguments and then looking for them in the list with all existing dependencies. Searching for a variable name is ok, given the typing of JavaScript, but the problem is that it stops to work after minification of code. When you minify your code, it stops working, since variables are injected by name. You either have to use this syntax:

someModule.controller(‘MyController’, [‘$scope’, function($scope) { }]);

or this

MyController.$inject = [‘$scope’, ‘$window’];

but it’s unclear why it was necessary to introduce several ways to do the same thing, but even more unclear why it was necessary to introduce a deliberately broken way (ill-conceived architecture#3). The next important thing is how dependencies are declared. Before you inject a dependency it needs to be declared. How would you do this:

injector.register(name, factoryFn);

Where name is the name of the dependency and factoryFn is the function that will initialize the dependency. That’s it. Literally the single sentence described a very concise idea. Now look at what angular offers. AngularJS introduces 5 new entities: provider, service, factory, value, constant (ill-conceived architecture#4). And each of them is different from each other, but essentially they are all the same. Most importantly, they can all be easily replaced with a single method that I described above. But it is too easy and not interesting, let’s make people suffer better!

Debugging

Debugging is complicated in itself, but it’s not enough, it needs to be more complicated! Angular feeds your pain and suffering. Errors in bindings don’t fire at all, for example in this code

<div ng-repeat=”phone in user.phones”> {{ phone }} </div>

if the user is undefined, no error will be fired (ill-conceiving architecture#5). Moreover, you can’t put a breakpoint inside {{ this expression }}, because it is not JavaScript. Let’s go further, errors that occurred in JavaScript are caught by the internal angular interceptor, and interpreted by browser as caught errors (everything that happens in AngularJS, stays in AngularJS). Because of that you have to switch on the flag Pause On Caught Exceptions (so that debugger stops on all exceptions, but not only on uncaught (usually uncaught exceptions are worth attention)), that’s why you need to go through all internal angular exceptions while it is initializing (ill-conceived architecture#6) and only then you get to your real exception. And then you will see the following stacktrace:

The error fired from the digest cycle and it means, that it could be caused by the change of any variable in the whole application. You will never find out where errors come from using debugger (ill-conceived architecture#7).

Scope inheritance

It is without a doubt the most common error that absolutely every AngularJS developer faces. For example, if one writes this code:

<div ng-init="phone = 02"> {{ phone }} </div>
<div ng-if="true">
<button ng-click=”phone = 911">change phone</button>
</div>

then by clicking on the button the “phone” variable won’t change, while in this code:

<div ng-init="phone = {value: 02}"> {{ phone.value }} </div> 
<div ng-if="true">
<button ng-click=”phone.value = 911">change phone</button>
</div>

everything works as expected. Or for example in this code AngularJS behavior is extremely not intuitive: jsfiddle.net/1op3L9yo/. There is even a new syntax for declaring variables in a controller using this notation, which fixes this problem. (ill-conceived architecture#8). Obviously, this behavior is absolutely useless and very dangerous:

  • If you base your logic on scope inheritance, then it becomes very hard to test it (you have to initialize the state of the parent controller).
  • Logic becomes more complicated and implicit (you use a variable that isn’t declared in the current module).
  • Inherited variables are similar to global variables (from child scope you can access absolutely any variable of any parent scope). And everybody knows that global variables are evil.
  • It increases the amount of theory you need to know: prototypal inheritance, when transclude creates a new scope and when it doesn’t create a new scope, you need to know how scope inheritance work with ng-repeat, ng-if, ng-switch, you need to know why a directive has 3 different types of scopes and so on.

Just by the amount of questions on stackoverflow.com and articles on the internet it is clear how confusing this topic is. That is it, by the way, what are you doing tonight? Even in advanced style guides people advise to avoid using $scope. During 2 years of work with AngularJS, I haven’t found any places where referring a variable from a parent scope would be a good idea (except for standard directive like ng-repeat, about the implementation of which I, as a user of the framework, do not care). Most interestingly, this behavior could be easily avoided by removing the possibility of scope inheritance or by creating some wrapper over JavaScript objects (for example ExtJs creates a wrapper over objects for imitating OOP). As a rule, frameworks should hide shortcomings of the platform and provide in its API more logic and transparent behavior. In return angular makes you fight with this aspect.

Directives

We come to the most interesting part, directives is the Holy of holies of angular. Everybody is crazy about them, people just worship them for some reason! But I’m pragmatic, here is complete syntax of the directive declaration. To understand this syntax and why you need it, you really have to spend a lot of time. Instead of writing WHAT to do, you think about HOW to do it. The sad thing is this complexity is useless. There is no logical reason to separate logic for 3 methods (compile, link, controller), all this can be easily implemented in a single method. Again, look how many problems it causes. Also take into account that some functions get scope, some not, some execute only once, some every time with $digest cycle, and every directive also have the priority. Even in order to integrate some code in the angular world, for example some jQuery plugin, you need to wrap it in a directive. Because it would be too easy if developers could use ready solutions right away! Also it’s unclear why the controller function in the directive has the same name as a usual controller, they are used in completely different places and for different purposes, they have to have different names! (ill-conceived architecture#9)

Problems with people

Firstly, since the framework is overcomplicated, not many developers really know it well and it is hard to find such developers is very hard. Secondly, server developers won’t understand what is going on front-end and won’t be able to read the code at all. And this is a very huge disadvantage, as it creates a black box, which can handle only one person in the team, and if that person leaves, then no one can replace him. It would be ok if development of front-end really required such complicated tools (for example writing GUI in ExtJs is much harder, than in AngularJS, but in ExtJs this complexity is reasonable), but no, AngularJS is hard because difficulties are created on purpose. If you use ReactJs or jQuery then any server developer will be able to read the code.

Inability of server side rendering

If you try to use server side rendering for example to speed up page rendering, or for SEO (or both), then you will be disappointed. Since server side rendering adds logic into your HTML and AngularJS writes logic in HTML, there is no clear separation of concerns and as a result you get spaghetti code. Angular simply doesn’t suppose that developers will want to speed up their pages, or want to be indexed by search engines, it isn’t created for these purposes (ill-conceived architecture#11). Yes, you can go around using prerender.io (the service, that parses your SPA and gives you HTML files that should give to crawlers). But again — it’s a hack, and not a solution of the problem.

AngularJS 2.0

Soon the next major version of the framework will come out, which breaks backward compatibility. Apparently, even developers of the framework realized that there is something wrong and decided to rewrite the framework almost from scratch. They don’t add new functionality, breaking backward compatibility a little, they rewrite almost everything. It means that if you start a new project with the current version of AngularJS, then in the future you definitely will be overboard. Just think to yourself, would they rewrite the framework if it already was good?

Other points

I covered only the most noticeable problems, when the developer writes bad code, he writes it everywhere, so if you are going to use this framework, get ready to face not only these problems, but also many other problems, less noticeable, but still problems.

  • Terrible documentation, you have to google a lot of information.
  • Still no directives for Drag and Drop events (by the moment of writing). It’s not a big problem by itself, but it indicates the quality of the product and the attention to details.
  • When you write in AngularJS you put your logic into your HTML (ng-repeat, ng-show, ng-class, ng-model, ng-init, ng-click, ng-switch, ng-if). Existence of such logic is not as bad as the fact that it is impossible to test this logic with unit tests, this logic can’t be debugged and errors don’t fire from markup (but this code contains very important logic).

Conclusion

Most of the problems that I have described can be solved if desired (and I had to, I couldn’t give up the project, that I wrote myself). But it is a dead end, it is like driving some very cheap car, instead of something expensive and reliable. — Is it cold inside? — Dress warmly! — Details come off easily? — You should only drive on the straight road! — Is it noisy at high speed? — Make the music louder and you won’t hear the noise! And if something has broken, come to our workshop and we will fix it. Angular is the same cheap car. You can drive it, but you will definitely get huge problems. The fact that problems can be solved doesn’t mean, that there are no problems. The only good thing that AngularJS has is that it forces developers to break their logic into modules, and code becomes more granulated, I don’t see any other advantages of AngularJS. Instead of AngularJS it is better to use React by facebook (I haven’t found in it any of listed problems) or Polymer (it is even not a framework, just a polyfill for web components), or something else, but on your own risk because there is plenty of negative feedback about Backbone, Knockout and others. I don’t know why AngularJS is so popular, but it is definitely not because it is a good framework. That is why I strongly advise you not to use the current Angular version. I hope my experience will help someone to make the right decision about technology stack for the next project.

Resources

What I would recommend instead of Angular.js?
React vs AngularJS — How the two Compare
From AngularJS to React: The Isomorphic Way
The reason Angular JS will fail
Pros and Cons of Facebook’s React vs. Web Components (Polymer)
AngularJS: The Bad Parts
What’s wrong with Angular.js
Things I Wish I Were Told About Angular.js
Why AngularJS is worse than a new ASP.NET WebForms
2 years with Angular
The problem with Angular

--

--