Angular is probably still the undisputed heavy weight champion of JavaScript frameworks, but other frameworks are emerging as solid alternatives. In this post I will share some of my experiences from working with Angular on large enterprise projects.
I consider myself a fan of Angular, but think it's fair to shine a critical light on some of the design decisions in Angular that seem strange for an enterprise level framework. It's been said that Angular started as a prototyping framework and evolved beyond its original purpose. I think we see a few examples of this in some of the architectural decisions in Angular.
Binding model
Angular has always pushed for plain JavaScript objects without annotations to indicate that a property is “bindable”. This is convenient for developers, but one might argue that the current binding system favors simplicity over performance. One example of this is two way binding. I have always seen it as counterintuitive that two way bindings are opt out instead of opt in. Reversing this would have given most applications an automatic performance boost by avoiding unnecessary two way bindings. Angular 1.3 gave us one time bindings, but adding it retroactively is far from ideal. Developers now have to go through their applications and determine if they can opt out of two way bindings or not. Other frameworks like DurandalJS did a better job here by requiring very explicit binding syntax via Knockout observables.
Two way binding may be confusing terminology since there is no convention for specifying one way or two way bindings. Instead the practical application of the binding dictates if the binding will be bidirectional or not. A binding between an input element and a model will be two way since the UI element can be updated from the model and vice versa. In the case of a passive element though, there is no way to specify that the binding is just one way.
I like working with plain JavaScript objects – don't get me wrong, but I wish Angular had made one time binding the default – with optional two way bindings. It's too late now, but one way would have been to reverse the meaning of :: to mean two way binding instead of one time binding.
Speaking of observables - Angular's digest cycle is another hot topic. I guess there are pros and cons to both dirty checking and observables. However the dirty checking process of interrogating all bindings during every single pass, whether they changed or not, seems a bit heavy handed to me. The digest cycles are not 100% predictable either since a given cycle can automatically trigger another digest cycle. Another danger is that you have to be careful not to trigger a new cycle when one is already running. Granted, most of this is usually handled well by Angular, but there are times when you as a programmer have to be familiar with the inner workings of the digest cycle. One such example is integration with third party libraries like jQuery.
Dependency Injection
I would argue that Angular DI is not scalable in very large applications since there is no built in support for lazy loading of JavaScript resources. Instead users are forced to download the entire JavaScript load upfront when the application is being bootstrapped. There are workarounds, but they all require integration with third party libraries. I have a blog post detailing an example of this here. Basically it would be nice to have native support for deferring loading of JavaScript files until the user navigates to a section of the application that requires them. The good news is that support for lazy loading will be added via the new Angular 2.0 router. In fact there are plans to back port the new router to the 1.5 version line as well.
API Surface
It's no secret that Angular offers a very large API surface with several proprietary concepts like provider, factory, service, value and constant just to mention a few. These are all “dependency injectable” objects with stylistic differences when it comes to how they are defined and instantiated. While it's nice to accommodate people's preferences for object styles, it does add to the learning curve to introduce so many different types. I do appreciate that there are benefits to each one, but in the end it roughly comes down to different variations of the same thing.
A lot to cover
One thing I often hear from Angular beginners is that it's hard to get into the mindset of the framework. I think this might be because there is a lot to take in since Angular is a comprehensive framework that addresses most aspect of a single page application. My transition to Angular was pretty smooth, but to be fair, I had learned about most of the concepts through other frameworks like DurandalJS and KnockoutJS.
It probably benefited me that I learned Knockout first and then moved on to Durandal. This allowed me to learn data binding and templating as a separate topic rather than learning it all at once as part of a large comprehensive framework. Understanding decoupled object models and how they relate to templates is one of the key benefits of Angular and similar frameworks. I have an example of this here.
A lot of people don't fully embrace the idea of proper separation of concerns in their applications. I often see applications where Angular is used on the surface, but all the heavy lifting is done using jQuery in directives with link functions. Instead of using data binding, the code is full of raw jQuery code with tight coupling to the DOM elements. In my opinion this really defeats the purpose of using Angular since you miss out on a lot of the benefits – especially when it comes to unit testing.
Angular 2.0
The announcement of Angular 2.0 sparked outcry in the developer community. The initial message was delivered poorly since it felt as if Google had decided to create a new framework and just keep the name for brand recognition. Many are still upset, but I tend to support the new direction, and think the criticism is exaggerated. Properly decoupled Angular 1.x code should not be that bad to migrate over – especially if you are using controllerAs and keep your logic in services. So far I am very happy about what I've seen of Angular 2.0. I have created a few demo components that you may check out here.
Clarification based on Angular 2.0 bindings
I am simply making the case that Angular 1.x would be more optimized if bindings by default were configured as 'one-time' instead of two way bindings. I say this because I find that a lot of bindings only need to fire once in order to compose the initial UI – without the need for change tracing since the backing property will never change. This might be seen as an inconvenience to have to opt in to change tracking, but I make the case that it would remove a lot of unnecessary watches.
In Angular 2.0 they have replaced 2-way bindings with 1-way bindings. I like that since 1-way bindings are less expensive - not free though. Therefore it may still make sense to make one-time the default and then opt-in to one-way bindings where it makes sense. This is is somewhat similar to Observables in KnockoutJS where you have to explicitly define an observable to get change tracking. By default Knockout model POJO properties are similar to the behavior of one-time bindings in Angular. Not saying this has to be handled using observables, but I am just trying to sell the idea of trying to avoid unnecessary watches.
Conclusion
In the end I consider myself a fan of Angular and I am particularly excited for Angular 2.0. That said I have spent a lot of time evaluating other frameworks like Aurelia, Durandal, React/Flux and it's my opinion that they all perform at the same level with the same capabilities. It just comes down to which one you prefer.