Goodbye, Angular
It’s been 14 months since I first started working with Angular and in recent times, I find myself moving away from this framework.
Now, I don’t necessarily agree that one framework and / or library is inherently better than the next. Every product has its own set of challenges, every team has its own unique dynamics and these, I believe, should form the basis for choosing a right set of tools to help craft an ideal solution for the task at hand.
Therefore, this is not intended to be another one of those articles that tell you not to use a particular technology. Instead, I’m just sharing some of the painful problems I’ve faced which encouraged me to seek out alternatives to Angular.
So, read on with an open mind.
Two-way data-binding
Two-way data-binding is indeed a nifty feature but it’s only made possible with the introduction of digest cycles within Angular.
For the uninitiated, a digest cycle in Angular is a process that runs when certain events, such as user interactions, are triggered.
During a digest cycle, Angular checks if the state of your application has changed since the last cycle and if it has, it automatically updates everything.
Sounds good, no?
It’s great when they are intended and predictable, but the moment you have user interactions, server responses and websocket events triggering digest cycles that update the state of your application unintentionally, it quickly becomes a painful mess.
This constant need to write conditional checks to preserve application state or to prevent digest cycles altogether was just too painful for me to maintain.
Scope Inheritance
On hindsight, inheritance of values and methods declared on a scope is a sensible design decision as it allows composing parent-child relationships throughout an application.
But the problem with scope inheritance in Angular is that values, and hence, state, can be changed by any component within the entire parent-child hierarchy.
This lack of knowledge of where the variable originated from, coupled with unintended digest cycles as mentioned previously, makes it really hard to debug and figure out which part of the system is causing the unintended behaviours.
Also, it does not help that parent and child scopes can both declare similarly named variables that have no relation to each other.
To protect the state of components within a parent-child relationship, we could enforce isolate scopes but I feel this just adds another unnecessary layer of complexity to an application’s architecture.
On the topic of isolate scopes, there are several Angular directives, such as `ng-if`, that creates a new scope and this is often easily overlooked, which leads to unexpected behaviour in the application.
Directives
Directives are often cited as one of the most powerful features of Angular as it allows the creation of reusable components / logic for our application.
They do promote reusability, but I feel that the amount of theory one needs to understand about Angular Directives somewhat hinders productivity.
Instead of being focused on building the functionality of a directive, I find my train of thoughts often disrupted by the need to refer to the documentation to refresh my mind about what’s `compile`, `link`, `controller`, `=`, `@` or `&`.
To further complicate matters, directives can also inherit from the scope of the parent controller and / or have other controllers as a dependency.
Now that we have access to the scope on `compile`, `link` and `controller`. Where do we attach application logic onto the scope?
Debugging Tools
In Angular, there are 2 general areas where errors may occur i.e. within templates and in Javascript.
With vanilla Javascript, whenever the browser encounters an undefined variable being called, an error is typically fired. But in the case of Angular templates, no error will be fired when a variable is undefined.
To add to the problem, because the error occurs in HTML, we can’t place a breakpoint to help us debug.
We could use Angular Batarang since it provides an interface that allows us to inspect the state of individual scopes but whenever an application grows in complexity, the debugger just slows to a crawl and its a pain to use.
For Angular errors that occur in Javascript, they do get thrown. But the problem is, they are fired from the digest cycle which means that any part of your application could have triggered the error since a cycle fires watchers on every scope.
This leaves me with the option of throwing out `console.log` everywhere which isn’t that great in my opinion.
Teamwork
At this point of writing, I believe there isn’t a widely accepted style guide for authoring Angular applications and I see this as a problem.
Everyone that joins the team will need to spend substantial amounts of time to familiarise themselves with the project structure, the way controllers are authored, when should directives be used, how are we doing dependency injection… and the list goes on.
Even then, we will constantly be questioning the team’s decisions — “Is this the right way to do things?”
Conclusion
I don’t hate Angular. I believe it has it’s place in a product’s lifecycle.
For us at Pie, it has served us well in getting our product to market but with evolving needs and the change in team dynamics, we found it beneficial to move on to explore other frameworks.
Angular.js is like your high school sweetheart. Dates were fun, wild and downright awkward at times. But as we mature, our values change and perhaps, it is wiser to part ways. ☺