Download as pdf or txt
Download as pdf or txt
You are on page 1of 9

blog.worldmaker.

net

Angular is Rotten to the Core


Max Battcher
25–31 minutes

I find Angular an impressive front-end framework for just how badly it is


designed, top to bottom, and yet how large of a cult-ish following it has. There
exists a weird “everyone knows it” mentality that in practice seems to be
entirely an illusion. There is the strange “no one got fired for picking Google”
echo of ancient IBM mistakes exacerbated by Google barely dogfooding
Angular (and arguably never successfully). There is the awful internal politics
of Google that have produced many horror stories from former Angular
developers, former open source community contributors, and combinations in
between, and an impression that the rabbit hole goes only deeper if you could
dig beneath Google NDAs and secrecy.
Why does anyone use Angular in production?
I’ll start with the sociopolitical weirdness and save the real meaty technical stuff
for the end. Let’s think of it like one of those long, mostly useless food blog
narratives to air some grievances before the technical equivalent of a recipe.

“Everyone Knows It”

When we started a greenfield project at my employer (opinions here are mostly


mine and not that of my employer and other usual disclaimers apply, of course)
I suggested React, as I was comfortable and happy with React in other
projects, and even did some prototypes in early testing in React. I was brought
on to the project to be “the backend expert”, so I was overruled because “no
one knows React” and “everyone knew Angular”. I didn’t know Angular at the
time, other than gut instincts from skimming tutorials that it was “Enterprise”
and “Bloated” in all of the worst senses of both words, and some hesitation/
general “sense of doom” from reading the blogs of Rob Eisenberg (because I
had used Durandal successfully in previous lives and Aurelia semi-successfully
in more recent projects; I’ll come back to all of this later in this post).
As soon as we started digging into real world usage of the application it
became very apparent to me that everyone that claimed they “knew” Angular,
simply didn’t. Out of frustration with application performance and
modularization needs and so many little problems, I found myself increasingly
having to become an expert on Angular, and the more of an Angular expert I’ve
become the angrier I’ve become for using Angular at all.
I think there are two big lies that add up to an illusion that “everyone” knows
Angular: the Angular template language uses an .html file extension, and
Angular Dependency Injection at first glance looks “Enterprise” and familiar to
Java developers especially. (Sometimes C# developers too.)
The first I think is the biggest illusion and the one that causes so much trouble.
React’s JSX/TSX looks “weird” at first, and “no one knows it” without at least
some learning curve. Vue and Svelte aren’t liars either and people realize there
is a learning curve to learn their .vue and .svelte templates. Like the much
mocked Jurassic Park lines “it’s a Unix system, I know this”, despite the many
variations of Unix and the weird UI shown on the screens that wouldn’t have
been familiar to anyone, “it’s an HTML file, I know this” is an amazing
misdirection of Angular’s. Angular’s template language is no less a
complicated template system like .vue or .svelte or .jsx, but that first
impression from junior developers that they know enough HTML it will be
“easy” and they already know it is amazing (and wrong).
Also, I realize that Angular themselves refuse to call it a “template language”
and go out of their way to call it a “view engine”. They seem to insist that you
could ship the template language’s HTML to a browser (as Knockout used to
do, using only HTML compatible custom attributes, back in the day), but at the
point where you have an AOT compiler for it is the point where I think you have
to admit it is a custom language. At least in my opinion. Angular’s insistence
that it is “just using HTML” seems so much intentional propaganda at this point
to keep the “everybody knows Angular” reputation despite an incredibly
complex template language compiler and build process.

“No One Got Fired For Picking Google”

Google, for the most part, has never used Angular. The few projects that have
obviously used Angular are notoriously awful performing applications.
The largest and most commonly noticed is the GCP cloud console.
Performance is definitely something it is not known for. Google and its
proponents will argue that the GCP console cross-cuts a huge number of
teams that all have to deliver components and maybe don’t all individually
have the right performance experts and collectively don’t have the right
incentives aligned to better coordinate performance across teams and so a lot
of the performance is left at “out of the box” configured from base settings.
(Despite GCP being a huge revenue source for Google, a major competitive
battle front with AWS/Azure, and presumably time wasted spent configuring
things in GCP can be directly associated with time not running billable
operations.)
Angular proponents would point out how great it is that Google doesn’t get
“special privilege” and it’s almost a badge of honor that one of the largest
instances of Angular usage in the company performs so poorly. This kind of
“fairness” sounds great on screen photons, but seems immediately and
obviously flawed. Is it really that great that everyone is equal in having bad
performance out of the box? If engineers “down the hall” and paid out of some
of the same budgets can’t get it to perform, who can?
One of the big lies implicit in the “no one went wrong going with Google” thing
here is that it implies that Angular works well at Google scale, and yet here
clearly it falls down. Regardless of what Google thinks of itself, Google isn’t
special: other companies have multiple cross-cutting teams involved in building
websites/dashboards/consoles/portals all together. It isn’t some special
Google-only workflow, it’s a common problem, that Angular is advertised to
solve. (It’s one of the oldest reasons for component-based systems since the
invention of the computer.)
It’s possible that there is a use case that Angular was designed to fit. (That’s
somehow not using a component model built to be a component model usable
by multiple cross-cutting teams, despite that’s why they have a component
model.) But I’ve come to doubt that given it doesn’t really seem like Angular
was designed for specific workflows and instead it was designed to meet the
goals of specific egos.

A Toxic Workplace?

Here especially I can only make second and third hand speculations. Please
take all of this with a grain of salt, try to find your own primary sources, and
realize that even primary sources that are publicly posted to blogs such as
Medium are filtered a degree or two sometimes away due to Angular’s
relationship with Google NDAs and other secrecy tools.
The reports are that Angular is among the many projects at Google that
appears to be following Ego-Driven Development. (Google is also not special
here and certainly plenty of companies practice Ego-Driven Development, but
Google has picked up something of a particular reputation for it in the way that
it infects their promotion culture and odd incentives to create products that
duplicate existing ones at the company and disincentives to support and
maintain existing ones.) Angular tries to be an open source community-
supported project (in addition to getting the marketing weight from Google
behind it), so some of this dirty laundry has aired very publicly. (Compared to
what we can mostly only speculate about, say, Google’s revolving door of chat
apps.)
Most of what I followed second hand was the saga of Rob Eisenberg. I had
been following Rob because of Durandal. I loved Durandal for a few years. It
was a great minimalist SPA framework that left the hard template/dataflow
work to Knockout and then just filled in the SPA gaps (routing, component
lifecycle). On the back of Durandal Rob was invited to work on Angular (in the
awkward Angular.JS to Angular transition years). There was some sort of
falling out some time into that effort, presumably for creative differences, and at
the end of that Rob created Aurelia as an answer to Angular’s goals.
(I used Aurelia for a project before Angular. I didn’t like it anywhere like I liked
Durandal and kind of disliked it. Though in context of having now worked with
Angular I can see better where it came from and some of how it wound up
departing from Durandal’s principles. I don’t expect to want to use Aurelia
again on a project, but I’d definitely recommend it to any “Angular shop” that
wants a lighter weight alternative that “everyone knows” and possibly has more
of a pit of success than Angular. Faint praise, of course, but still praise.)
I offer this (possibly?) slander mostly as an appetizer to the technical
discussion. It’s not entirely unrelated, as I find it interesting background. It
answers to at least sate some of my curiosity how Angular got designed the
way it was and who it was maybe designed for. (Ego being an obvious and
clear answer to both.)

No One Knows Angular (But I Know More Now, Sorry)

My fast ramp up to team “expert” on Angular came from a path that was rare
and while I would never assert that it makes me a better “expert” than most on
Angular, it certainly makes me a peculiar one, especially in being able to
pinpoint to some key things were Angular has created “pits of failure” (where
it’s just too easy for developers following “best practices” and tutorials and
trying to do things “the Angular way” find themselves failing through no fault of
their own).
One of my odd paths is use of Aurelia before Angular. Aurelia has a better
Dependency Injection system than Angular. (In part by going all in on
Typescript’s experimental decorators support rather than half-in. Though I think
both are wrong to use an experimental flag in Typescript based on a TC39
proposal that has been rejected one and a half times.) In particular, Aurelia
tree-shakes better out of the box and without a lot of config work. Aurelia’s CLI
is better at finding circular dependency mistakes and things that may not tree-
shake well (and suggesting fixes when noticed). A lot of Angular’s easiest to fix
performance problems stem directly from the overly complicated and not very
good “Modules” system that gets in the way of all of the smarts and advances
packer tools have put into ES2015 module support and ES2015 module
treeshaking. The “Modules” provide a second parallel import system that is
harder to tree-shake, easily gets out of sync, and actively gets in the way to
getting bundle sizes down.
The other somewhat peculiar path was that I’ve been a Reactive Extensions
fan for a long time. I’ve done C# UI apps with Reactive Extensions from when
ReactiveX was still fresh out of Labs/Preview kindergarten. In C# I use
ReactiveX (and more recently IAsyncEnumerable InteractiveX) as extremely
useful tools in complicated (but easily described by ReactiveX) backend
dataflows and multi-threading and plumbing. I used Cycle.JS, a JS UI
framework built for first class ReactiveX, for years before running off to React
for the larger ecosystem. Even in React I tend to keep redux-observable as a
tool in my arsenal for complicated data flow. I’ve even built some interesting
Discord bot logic “backends” in redux-observable. I know ReactiveX and RxJS
pretty well at this point, their strengths and weaknesses, the differences
between hot and cold observables and when you want each to apply to which
problem, when and where to apply ReactiveX/RxJS as great tools (and how
best to do in places that don’t phase junior developers too much), and some
good ideas where ReactiveX/RxJS isn’t the best tool for the job.
If there’s a core decision in the core library of Angular that is most emblematic
a pit of failure it’s the incredibly half-assed adoption of RxJS. This one
decision, this one broken usage, affects everything else, contributes to so
much of the performance churn of people’s apps by default especially when
they believe they are following best practices. Angular intentionally ignores
RxJS best practices in setting its own “best practices”. Angular decided that
they wanted to mix all of the concerns of RxJS Observables,
BehaviorSubjects, and classic Node-era Event Emitters in one core
EventEmitter class that is the worst of all worlds. This adds an RxJS
dependency that bloats every Angular app everywhere no matter how well the
developers know RxJS. RxJS is a huge dependency to do that with. RxJS has
gotten better at tree-shaking itself, but it is still a huge chunky library.
RxJS is hard to learn. It does have a huge learning curve. It’s not just
something “everyone knows”. If there is a bigger bright neon sign that should
be blaring on top of all the assumptions and assertions that “everyone knows
Angular” it should be “No one knows RxJS”. You can understand why Angular
developers might want to provide “escape hatches” from RxJS because they
want make the framework more approachable. However, by ignoring RxJS
best practices and making the EventEmitter publicly a BehaviorSubject in API,
Angular gives developers an escape hatch deep inside in the core for ignoring
RxJS best practices (BehaviorSubjects are meant to be generally avoided, but
when used as internal details of an encapsulated API), an excuse to avoid
learning RxJS until it is far too late, and almost always immediately falling into
a pit of failure that results in bad performance and the many of the worst of
singleton “god objects” that are just nasty global variables with much more
complicated APIs. Right out of the starting gate, Angular “best practices” cause
so much of their own misery.
It’s a worst of both worlds situation: why take on a huge, complicated
dependency like RxJS if you are just going to immediately provide system
breaking escape hatches? These escape hatches then breed like rabbits,
having a ripple effect on the entire ecosystem. The few libraries that are more
likely to use RxJS for complex behaviors are more likely to get it wrong simply
by following the bad example directly from right inside Angular’s core and
leaking BehaviorSubjects everywhere. The ones that don’t want to use RxJS
just sit on complex webs of escape hatches and easily broken state mechanics
(especially if they fall into just using global singletons everywhere). So many
tutorials and examples of “good code” are littered with bad RxJS usage
(BehaviorSubjects leaking across encapsulation boundaries, Observable
subscriptions without matching unsubscriptions (classic malloc without free;
reference counting is hard and everyone is bad at it and will be for all time),
cold/hot problems, over-subscriptions, and more even more complicated RxJS
data flow problems (that at best are just too much work and at worst are
memory leaks and performance problems waiting to happen).
The ripple effect happens inside the Angular house too as other Angular
components can’t make up their minds to embrace RxJS or not, can’t make up
their mind how many of their own escape hatches they need to add, and just
constantly adding to the escape hatch proliferation problem.
Angular Routing tries to use RxJS deeply and while it doesn’t let
BehaviorSubjects directly escape into its public API it offers an almost equally
bad “snapshot” escape hatch.
Angular’s HttpClient embraces RxJS deeply in its outputs, but for what are
essentially over-weight Promises. It doesn’t take Observables for input, nor
does it use them in the middle of its processing pipeline, and in being used for
not much more than over-complicated Promises it still has ripples like the other
escape hatches. Some developers start to associate Observables as “bad
Promises that you can’t use async/await with” and take away from it that
subscribe is just like Promise then and use it like bad pre-async/await era
callback hell. It directly contributes to the subscribe without unsubscribe
problem in so many tutorials and sample code. There actually is an easy
solution that these tutorials/samples could use: RxJS Observables provide a
good toPromise() implementation that does the subscribe and immediately
unsubscribe after first value dance, and lets you use traditional async/await.
(I’ve made this suggestion to at least a couple of tutorial authors I’ve found. But
there’s no way I alone can post that suggestion to the expansive number of
bad tutorials in the Angular ecosystem.)
Of course, Angular’s HttpClient could just return Promises if that is 99% of
what people use them for and most uses of HttpClient are single value
expectations. HttpClient offers a somewhat plausible reason for this: with an
optional config parameter HttpClient will provide Observables with more values
that include a stream of progress values. In theory, this might be useful for
better progress reporting in the app, but in practice I’ve still yet to see a good
library take good advantage of it. HttpClient isn’t in a library that is setup (in
current Angular “organization” structures) to offer an out-of-the-box UI
component to make use of it. It’s a feature that might as well not exist given
very little good guidance on how to use it (and again that I’ve never seen
anyone really take advantage of it). Even in trying to add progress reporting to
projects I’m working on, it was much easier to build a dependency-injected
HttpInterceptor that kicks off and stops NProgress (and isn’t far removed from
things I’ve done all the way back to Durandal and its middleware).
HttpInterceptors are “middleware” and at least they aren’t intentionally built to
be RxJS escape hatches, but here it is the one escape hatch I found myself
particularly using from a provided Observable, because it was so much
simpler.
Angular’s Template Language doesn’t bother with first-class support for
Observables (though that would potentially make things a lot cleaner;
Knockout called its bindings Observables for a reason, way back in the day).
Instead the template language relegates it to AsyncPipe, which I was angry
when I found it in its almost hidden spot in Angular’s documentation which also
relegates it to something of an afterthought. The other obvious thing that would
clean up a lot of tutorials/samples (including and especially here in Angular’s
own documentation!) with regard to Observables is if far more tutorials/
samples used | async pipes instead of manual Observable subscribe/
unsubscribe in example components. (Or you know, if Angular had added
Observable support first class into the template language instead of as an
afterthought.) Again, RxJS best practices heavily suggest reducing the number
of subscribe/unsubscribe to a bare minimum, and yet almost every example in
Angular’s documentation (and from there so much of the rest of the
ecosystem) use manual subscribe/unsubscribe rather than something like
AsyncPipe that can handle it automatically. Though I realize that doing that
would mean more Angular documentation would need to teach RxJS sharing
(shareReplay(1) being one of the most common in my arsenal) and that
would risk needing to teach the hot/cold Observable problem and directly risk
that “everybody knows Angular” reputation for the actual learning curve (that’s
still there anyway, just hidden behind bad documentation and bad practices as
“best practices”, because apparently Angular is okay with bad performance out
of the box).
At this point I’ve contributed more than I should have had to to fix RxJS-related
documentation mistakes to the wider Angular ecosystem. I’ve left notes to
tutorial writers how they might better their tutorials (though there is no incentive
to do so, because “everybody knows Angular”). I’ve glanced at the bad
Observable code in entire libraries, shuddered in horror, and wrote my own in a
third of the code (and been thankful for the production performance problems I
avoided). Two of the worst offenders I’ve seen are Angular Material and
Angular CDK, and if Google can’t get Observables right in its own “second
party” libraries, I don’t blame anyone else in the ecosystem but the Angular
core team for these problems. Also, tossing Angular Material and Angular CDK
out the airlock was a huge “performance fixing” development effort on my part
(after what the frontend devs defaulted to), and keeping them out is a larger
effort because so many tutorials and other third party components “suggest”
them (“no one got fired for picking Google”).

Suggestions For a Better Angular (“Project Gawky”)

So I’m not a monster, and this is a “recipe” blog like I said, so I’m happy to
leave with a thought experiment of what something like Angular would look like
if it properly embraced Observables instead of being wishy-washy with them all
the way deep into the core. I think Observables are a great idea for a frontend
framework (again, I used Cycle.JS for some time previously). Let’s call this
thought experiment “Project Gawky” (as a fun synonym for “angular” when
referring to a person that also implies a double meaning of observing).
At one point I toyed with the idea of attempting a toy proof of concept for
“Project Gawky”, but so far as I know Angular’s “Ivy compiler” for its template
language is not documented at all for reuse and I have no interest in building
my own template language and especially not in trying to emulate the Angular
template language just for a toy proof of concept.
Taking for assumption that the resemblance to HTML of Angular’s template
language is a part of its success and something worth keeping, I’d start from
what you can do if the only bindings you can do are Observables. The idea is
basically RxJS-powered “modern” Knockout.
First, it would get rid of the incredible weirdness that is Angular’s two-way
binding syntax (the strange bag-in-box [(thing)] that a lot of people don’t like
or understand in Angular templates). Supporting which is an incredibly odd
“code behind” pattern involving a normal property and an EventEmitter for
when it changes, but only by the component itself because you want to avoid
infinite loops by it accidentally re-emitting values it got from other components/
Angular.
But that’s a nice to have “side effect”, the real meat is what you can do once
every output variable in the template is expressed as an Observable:
combination and scheduling. For years now, React has been building a bunch
of initiatives in somewhat parallel (Suspense, Concurrent, etc) to do a lot of
complicated update combination and scheduling: in a nutshell, they want low
priority DOM updates to happen during the browser’s
requestAnimationFrame timer, high priority updates (such as to inputs that
the user is directly interacting with) to happen as soon as possible, and they
want to be able to combine all of the updates to entire component sub-trees at
once rather than showing partial updates as data is loaded. These initiatives
have been fascinating to watch, especially as some of the information React is
doing for this combination and scheduling work is “reverse-engineered” from
the “pull” and “diff/patch” nature of the Virtual DOM. React has been doing
some interesting smart things to gather more information, and the
underpinnings of Hooks are fascinating in relationship to these efforts. (Though
Hooks are required for some of it to work, they mostly just light up more
“smarts” and even class-based components still sometimes benefit.)
An Observable based template engine potentially has a much easier time
doing such complicated combination and scheduling with the comparative
“push” nature of Observables. So easy and cheap it’s nearly free and out of the
box. Combinators like combineLatest are the bread and butter operators for
why you’d pick something like Observables in the first place. Observables have
a direct concept of Schedulers which provide timing mechanics and moving
some updates to happen at requestAnimationFrame may be as simple as
adding throttleTime(0, requestAnimationFrameScheduler) to the right
pipelines. Given an AOT compiler (like Ivy) you could sometimes bake entire,
complicated Observable pipelines for entire component trees at build time
(including smart uses of combinators and schedulers) with little to no runtime
code. In theory it is so much of what React has been trying to do with a lot of
(successful and interesting) hard work in potentially a simpler and “cheaper”
package deal.
Observables-first “Project Gawky” should reduce a lot of things that Angular
relies on Dependency Injection for, or the very least reduce a lot of singletons
acting as global state in the average project, so I could even see trying to use
Dependency Injection still for wiring up some of the more complicated
pipelines. DI in that case might be a good way to better encapsulate
BehaviorSubjects and remove their need from all “user” code.
BehaviorSubjects are mostly an escape hatch around essentially circular
dependencies and while DI sometimes hates circular dependencies, in the
case of Observables they make a certain sense. (Cycle.JS’ name is not an
accident.)
(To contrast with Cycle.JS for the very few developers curious: Cycle.JS’
Virtual DOM approach has a very “Observables first” definition. Inverting it to
be HTML Template “first” like an RxJS-based Knockout should feel very
different from the approach of Cycle.JS. “Smart templates” doing some pipeline
management such as requestAnimationFrameScheduler is something
mostly sort out of scope for Cycle.JS, leaving that essentially for the “drivers”/
Virtual DOM to reverse engineer similar to React, though you can do some
such things by hand to give it a push. I’m not a fan of Angular’s Dependency
Injection system, and while I’d simplify it, I can see a use for a Dependency
Injection system in an Observable-first “Project Gawky” to clean up or at least
simplify some of the harder bits of wiring/plumbing I recall from Cycle.JS.)
All it would take is taking Observables seriously and first class with fewer
escape hatches. It would be a lot harder to learn, but might have a much
greater pit of success (smarter update combination and scheduling, right there
out of the box with little to no developer config or wiring needed), and
presumably a smaller pit of failure. I ran out of interest in trying to build a toy
“Project Gawky” proof of concept when I didn’t see an easy way to hack the
Angular template language compilers for such an experiment, but I’ll happily
code review and maybe pitch in if someone else wants to toy with the idea.

You might also like