Go back

Why angular is a bad framework



Introduction

The title is probably provocative, but I think it's fair enough. To justify it, I'm going to list all the points that, in my opinion, make Angular a bad framework.

Note : I'll compare Angular to React and Svelte. React because of its popularity, and Svelte because of some similarities that some have seen with Angular. But unlike Angular, I consider Svelte to be a good framework because…

Angular doesn't allow you to see unused code

Buzz Lightyear telling Woody: “Dead code... dead code everywhere.”

As TypeScript and HTML files are separate in Angular, you can't tell whether a method is in use or not. In the example below, the decrement method is unused, but your IDE won't tell you. Imagine refactoring your code with dozens of methods!!

A code capture showing that Angular does not highlight unused methods.

Conversely, on React or Svelte, since the view and TypeScript are in the same file in the same file, you'll be informed immediately of any problems (whether it's an unused function or a call to a function that doesn't exist).

A code capture showing Svelte and React highlighting unused methods.
ICON
Objection

Isn't it possible to detect unused methods with a linter?

No, Angular's linter does not detect unused methods. Also the basic configuration of Angular is very insufficient, this will be my next point.

Angular does not give you any "framework"

I hear it repeated that Angular offers a framework, an architecture compared to other frameworks. Where do you see this?

Does Angular include in its linter a configuration to impose a coding style (like airbnb)? No.

Is the linter fully configured? Not even. You have to add a specific version of ESLint yourself.

Does Angular include a code formatter for consistent style? No. No. And since Angular uses an unconventional HTML syntax, you'll have to dig deep to find out how to implement Prettier properly.

Does Angular offer an architecture? No. It's a supposed quality that's repeated without thinking, but Angular doesn't actually impose any architecture on you (you may or may not use modules (I'll come back to this point), use whatever name you like for your files, place them wherever you like, and the proliferation of utilities means that you never know what the official way of doing a task is).

Conversely, a framework like NextJS really imposes an architecture: you're obliged to name your files in a certain way (for example, you can't name a page anything other than page.js) and the routing system imposes rigorously following a certain folder structure.

ICON
Objection

But Angular is more modular than other frameworks!

Naming files “Modules” is not enough to qualify a framework as modular. In the same way, having a logo in the shape of a shield doesn't guarantee you any additional security, I assure you.

A constant semantic and stylistic struggle

With Angular, you'll constantly have problems with the semantics of your HTML and others with the management of your CSS styles. Before we get to that, let's take a look at how Svelte and React components are displayed in the DOM. Let's start with Svelte:

<script>
import Component from "./Component.svelte";
</script>
<main>
<Component />
</main>

Let's move on to React:

import Component from "./Component";
export default function App() {
return (
<main>
<Component />
</main>
)
}

As you can see, the result is fairly predictable. What about Angular?

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<main>
<app-component></app-component>
</main>
`,
})
export class AppComponent {}

Surprise! Instead of displaying the component directly, Angular wraps it in an additional tag. This is a problematic point that can quickly lead to contortions as tedious as they are pointless. Indeed, if you want to create components for a custom table, for example, you'll end up with incorrect semantics:

<main>
<app-table _nghost-ng-c4098229708=""
><table _ngcontent-ng-c4098229708="">
<app-table-head
_ngcontent-ng-c4098229708=""
_nghost-ng-c167012404=""
><thead _ngcontent-ng-c167012404="">
<tr _ngcontent-ng-c167012404="">
<th _ngcontent-ng-c167012404="" scope="col">
Employee Name
</th>
<th _ngcontent-ng-c167012404="" scope="col">
Department
</th>
<th _ngcontent-ng-c167012404="" scope="col">Project</th>
<th _ngcontent-ng-c167012404="" scope="col">
Hours/Week
</th>
<!--bindings={
"ng-reflect-ng-for-of": "[object Object],[object Object"
}-->
</tr>
</thead></app-table-head
><app-table-body
_ngcontent-ng-c4098229708=""
_nghost-ng-c775862262=""
><tbody _ngcontent-ng-c775862262="">
<app-table-row
_ngcontent-ng-c775862262=""
app-table-row=""
ng-reflect-employee="[object Object]"
><td>Employee 1</td>
<td>Engineering</td>
<td>Website Redesign</td>
<td>21</td></app-table-row
><app-table-row
_ngcontent-ng-c775862262=""
app-table-row=""
ng-reflect-employee="[object Object]"
><td>Employee 2</td>
<td>Design</td>
<td>Q4 Sales</td>
<td>30</td></app-table-row
><app-table-row
_ngcontent-ng-c775862262=""
app-table-row=""
ng-reflect-employee="[object Object]"
><td>Employee 3</td>
<td>Engineering</td>
<td>Mobile App</td>
<td>37</td></app-table-row
><!--bindings={
"ng-reflect-ng-for-of": "[object Object],[object Object"
}-->
</tbody></app-table-body
><app-table-footer
_ngcontent-ng-c4098229708=""
_nghost-ng-c2143765403=""
><tfoot _ngcontent-ng-c2143765403="">
<tr _ngcontent-ng-c2143765403="">
<th
_ngcontent-ng-c2143765403=""
scope="row"
colspan="3"
>
Total Weekly Hours
</th>
<td _ngcontent-ng-c2143765403="">75</td>
</tr>
</tfoot></app-table-footer
>
</table></app-table
>
</main>

Tailwind is sometimes mocked for the overload it imposes on HTML classes, but Angular is far worse in this respect, as you can see! What's more, the table won't display like a classic HTML table, because all the semantics are broken. The same table, made with React, would be rendered in this way:

<main>
<table>
<thead>
<tr>
<th scope="col">Employee Name</th>
<th scope="col">Department</th>
<th scope="col">Project</th>
<th scope="col">Hours/Week</th>
</tr>
</thead>
<tbody>
<tr>
<td>Employee 1</td>
<td>Engineering</td>
<td>Website Redesign</td>
<td>21</td>
</tr>
<tr>
<td>Employee 2</td>
<td>Design</td>
<td>Q4 Sales</td>
<td>30</td>
</tr>
<tr>
<td>Employee 3</td>
<td>Engineering</td>
<td>Mobile App</td>
<td>37</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row" colspan="3">Total Weekly Hours</th>
<td>75</td>
</tr>
</tfoot>
</table>
</main>
ICON
Objection

There are at least three techniques to get around this problem!

First of all, this problem does not exist on other frameworks. Secondly, none of the proposed solutions are relevant. Using ng-container is impossible because you can't pass attributes to them. Using ViewContainerRef is absurdly complex and not the normal way to create components with Angular. As far as attribute-selectors is concerned, it only reinforces the second problem highlighted in the introduction, which I'm now going to develop.

Wrapping components in tags or using attribute-selectors like this creates problems in terms of component stylization, forcing us to break with the single responsibility principle. Let's take a minimalist version of our table (just one row and one cell):

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<tr>
<app-table-cell></app-table-cell>
</tr>
`,
})
export class AppComponent {}

If we now use attribute-selectors to restore correct semantics, we find ourselves in this situation:

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<tr>
<td class="cell" app-table-cell></td>
</tr>
`,
styles: ['.cell { background: red }'],
})
export class AppComponent {}

The problem is that we are forced to move all the styling, which should be encapsulated in the child component (Cell component) into the parent component! That's why I say that Angular breaks the single responsibility principle and is a bad framework.

Angular does not have any data caching manager.

There are probably two things you shouldn't code yourself in a web application: authentication and server data caching. These two points are absolutely essential, and I'm surprised to see that Angular doesn't offer any tools for managing data caching!

Fortunately, almost all other frameworks can rely on TanStack Query to handle this need.

ICON
Objection

— I'm from the future, and TanStack Query is finally usable with Angular! — Glad to hear it. The Angular development team probably had nothing to do with it.

The ecosystem is poor

Echoing the previous point, it's clear that Angular's ecosystem is pretty poor.

Take, for example, Angular's main component library component library: Angular Material. It includes 36 components with many shortcomings, such as the absence of masks) , while its React equivalent (MUI) includes more than 50 components, plus utilities and the possibility of using it in headless mode (the quality of the documentation is also significantly higher). In fact, there is currently no headless library on Angular.

Compared to Angular, you'll almost always find an answer to your question if you're with React, and you can use this framework even for exotic uses like creating emails, building command-line applications, creating 3D experiences, editing videos and even making virtual reality applications !

Finally, in terms of popularity, the results of the 2023 JavaScript Rising Stars show that Angular is on a downward trend compared with other front-end frameworks. Angular may have a very honorable past, but in my view it has no future.

Inability to use template literals in HTML

The lack of this syntax in HTML can lead to unnecessarily complex situations with Angular.

On most frameworks you can use them without any problems:

Example of using template literals in HTML with Svelte and React.

But not with Angular:

Example of using template literals in HTML with Angular. All HTML code is underlined in bright red.

Note : The Angular team is aware of this.

Unnecessary complexity

A good framework allows you to do a lot with little writing. Angular allows you to do little with a lot of writing, especially because of its completely unnecessary complexity. Here are some specific points.

Animations

To see just how complex Angular is when it comes to animations, we'll try to reproduce this effect with Svelte, React and then Angular:

  • Item 1
  • Item 2
  • Item 3

This is a pretty simple example of making a list item bigger on hover and bouncing it on click.

React version

To create animations with React, we can rely on Framer Motion, an easy-to-use animation library that lets you create complex animations.

Let's start by laying out the page structure:

import classes from './App.module.css';
export default function App() {
const items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
return (
<ul>
{items.map((item) => (
<li key={item.id} className={classes.box}>
{item.text}
</li>
))}
</ul>
);
}

To animate list items, we need to replace our li with motion.li order to extend the HTML element with the Framer Motion API:

import { motion } from 'framer-motion';
import classes from './App.module.css';
export default function App() {
const items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
return (
<ul>
{items.map((item) => (
<motion.li key={item.id} className={classes.box}>
{item.text}
</motion.li>
))}
</ul>
);
}

All that remains is to add three lines to indicate the effect of the transition (I've opted for a spring animation), as well as the events involved (hover and click):

import { motion } from 'framer-motion';
import classes from './App.module.css';
export default function App() {
const items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
return (
<ul>
{items.map((item) => (
<motion.li
key={item.id}
className={classes.box}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
transition={{ type: 'spring', stiffness: 400, damping: 10 }}
>
{item.text}
</motion.li>
))}
</ul>
);
}

So we only had to add three lines of code to achieve the desired effect! This is what I particularly like about this library: we can make complex effects with a minimal number of lines of code.

Svelte version

To be perfectly honest, I didn't manage to create the effect presented as simply as I would have liked because I ended up in the situation where all the list items were growing at the same time.

That said, Svelte has a number of useful utilities for creating animations, including FLIP animations, built-in transitions and the spring animation, which can be created as a hover effect:

<script>
import { spring } from "svelte/motion";
let scale = spring(1, {
stiffness: 0.05,
damping: 0.1,
});
</script>
<div
class="animated-box"
on:mouseenter={() => scale.set(1.2)}
on:mouseleave={() => scale.set(1)}
style="transform: scale({$scale})"
>
Hover me!
</div>
<style>
/* ... */
</style>

There's also an equivalent to Framer Motion for Svelte, and for those who are looking for animated components, Svelte Animation Components has a pretty impressive collection.

Angular version

Finally, let's see how things work on Angular. Here's the code that gives us the overall structure of the page:

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
}

First pitfall: you need to import the BrowserAnimationsModule in the right place, otherwise your animations won't work.

Once that's done... roll up your sleeves! We'll need to import animate, keyframes, state, style transition and trigger from @angular/animations.

Unlike Framer Motion or Svelte, Angular doesn't provide any utilities for making Spring or FLIP animations, so we'll have to code a rough effect by hand. The animation is defined in the TypeScript component as follows:

import {
animate,
keyframes,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
trigger('listItem', [
state(
'default',
style({
transform: 'scale(1)',
})
),
state(
'active',
style({
transform: 'scale(1.03)',
})
),
transition('default => active', [
animate(
'450ms cubic-bezier(0.4, 0, 0.2, 1)',
keyframes([
style({ transform: 'scale(1)', offset: 0 }),
style({ transform: 'scale(1.04)', offset: 0.4 }),
style({ transform: 'scale(1.01)', offset: 0.7 }),
style({ transform: 'scale(1.03)', offset: 1 }),
])
),
]),
transition('active => default', [
animate(
'500ms cubic-bezier(0.4, 0, 0.2, 1)',
keyframes([
style({ transform: 'scale(1.03)', offset: 0 }),
style({ transform: 'scale(0.98)', offset: 0.4 }),
style({ transform: 'scale(1.01)', offset: 0.7 }),
style({ transform: 'scale(1)', offset: 1 }),
])
),
]),
]),
],
})
export class AppComponent {
items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
}

Next, you'll need to create your own methods and variable to track the state of the animation:

import {
animate,
keyframes,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
// Couvrez ce code que je ne saurais voir !
],
})
export class AppComponent {
items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
listItemAnimationState: 'default' | 'active' = 'default';
onListItemMouseEnter() {
this.listItemAnimationState = 'active';
}
onListItemMouseLeave() {
this.listItemAnimationState = 'default';
}
}

So much for TypeScript (or not). Now we need to link it all to HTML:

<ul>
<li
class="box"
*ngFor="let item of items"
[@listItem]="listItemAnimationState"
(mouseenter)="onListItemMouseEnter()"
(mouseleave)="onListItemMouseLeave()"
>
{{ item.text }}
</li>
</ul>

Verdict: it still doesn't work. When you move the cursor over a list item, all the items grow at the same time!

  • Item 1
  • Item 2
  • Item 3

So you have to hand-code a system to assign the right state to each list item... It's positively ridiculous complexity. Here's the final code, a total of 86 lines to get what React does in 26 lines, and better (by the way, I didn't even add the click effect on Angular, as that would have made this already overweight component even more complex).

import {
animate,
keyframes,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
trigger('listItem', [
state(
'default',
style({
transform: 'scale(1)',
})
),
state(
'active',
style({
transform: 'scale(1.03)',
})
),
transition('default => active', [
animate(
'450ms cubic-bezier(0.4, 0, 0.2, 1)',
keyframes([
style({ transform: 'scale(1)', offset: 0 }),
style({ transform: 'scale(1.04)', offset: 0.4 }),
style({ transform: 'scale(1.01)', offset: 0.7 }),
style({ transform: 'scale(1.03)', offset: 1 }),
])
),
]),
transition('active => default', [
animate(
'500ms cubic-bezier(0.4, 0, 0.2, 1)',
keyframes([
style({ transform: 'scale(1.03)', offset: 0 }),
style({ transform: 'scale(0.98)', offset: 0.4 }),
style({ transform: 'scale(1.01)', offset: 0.7 }),
style({ transform: 'scale(1)', offset: 1 }),
])
),
]),
]),
],
})
export class AppComponent implements OnInit {
items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
];
animationStates: { [key: number]: 'default' | 'active' } = {};
ngOnInit(): void {
for (let index in this.items) {
this.animationStates[index] = 'default';
}
}
onListItemMouseEnter(index: number) {
this.animationStates[index] = 'active';
}
onListItemMouseLeave(index: number) {
this.animationStates[index] = 'default';
}
}

In short, Angular has no utility class to handle animations like Svelte, there are currently no animation libraries (because, as I said, the ecosystem is poor) and the code to achieve a potable result is unnecessarily complex.

RxJS

RxJs is a library that needs to be used for all asynchronous processing on Angular. This adds incredible complexity at every level. Let's take a look at two examples: calling an API and creating a Todo-list.

API call

Although Svelte and React can rely on Tanstack Query to make API calls easier, I'm going to work without a library for the sake of fairness. Here's an example of an API call with Svelte and React:

<script>
let promise = fetch("https://jsonplaceholder.typicode.com/todos").then(
(response) => response.json()
);
</script>
{#await promise}
<p>Loading...</p>
{:then data}
<pre>
{JSON.stringify(data, null, 2)}
</pre>
{:catch error}
<p>Could not fetch data.</p>
{/await}

Both examples are fairly easy to understand, for Svelte, the only specific element to know is the await...then blocks.
For React, you need to know two things: a lifecycle method (useEffect) and the concept of state which is central. Everything else is classic JavaScript. Now let's see what you need to understand on the Angular side.

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AppService {
constructor(private http: HttpClient) { }
private handleError(error: HttpErrorResponse) {
if (error.status === 0) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong.
console.error(
`Backend returned code ${error.status}, body was: `, error.error);
}
// Return an observable with a user-facing error message.
return throwError(() => new Error('Something bad happened; please try again later.'));
}
fetchData() {
return this.http.get('https://jsonplaceholder.typicode.com/todos').pipe(
catchError(this.handleError)
)
}
}

I won't go into all the steps, but for a simple API call, you need to understand the general concept of service injection, observables, subscription, error handling with catchError, the OnInit lifecycle method and the structural directive NgIf.

Added to this is the difficulty of using these elements correctly. Indeed, there are several ways of making an API call and subscribing to a response, including using the pipe async (yet another concept to overload our memory). This article quickly shows what an API call with this method would look like. Dare to say that Angular offers a framework after this!

Todo-list

For this example, I'll make a simplified version of a Todo-list by allowing only the addition of a Todo (this will be sufficient to illustrate my point). Here's what we'll create (without the clean button):

  • Rant about Angular.
  • Break a chair.

Let's start with an implementation using Svelte and React:

<script lang="ts">
import type { Todo } from "./types/Todo";
let newTodoText = "";
let todos: Todo[] = [
{ id: 1, text: "Rant about Angular." },
{ id: 2, text: "Break a chair." },
];
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
if (!newTodoText.trim()) return;
const newTodo = {
id: Date.now(),
text: newTodoText.trim(),
};
todos = [...todos, newTodo];
newTodoText = "";
}
</script>
<form on:submit={handleSubmit}>
<label for="add-todo">Your new todo</label>
<input id="add-todo" type="text" bind:value={newTodoText} />
<button type="submit">Add</button>
</form>
<ul>
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{/each}
</ul>

The two versions are quite similar, and the whole thing doesn't deviate much from traditional JavaScript. The only additional concept to assimilate would be that of props (that is common to both Svelte and React), because in a real case, we would have created a separate component for the form and another for the Todo list (and probably another component for the Todo).

Let's see what the same code looks like with Angular:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { Todo } from './models/Todo';
@Injectable({
providedIn: 'root',
})
export class TodoListService {
todosChanged = new Subject<Todo[]>();
private todos: Todo[] = [
{
id: 1,
text: 'Rant about Angular.',
},
{
id: 2,
text: 'Break a chair.',
},
];
getTodos() {
return this.todos.slice();
}
addTodo(todoItem: Todo) {
this.todos.push(todoItem);
this.todosChanged.next(this.todos.slice());
}
}

Once again, we're saddled with unnecessary complexity, particularly with Subjects (which break down into BehaviorSubject, ReplaySubject, AsyncSubject and Void subject. Don't ask me what they are for). Furthermore, simply interacting with a form requires wading through Angular's own complexity (in this example I kept it simple, but in a real example you will have to use reactive forms with all the complexity they bring).

Modules

It's hard to give a comparative example, because modules are an Angular-specific feature that don't add any functionality. I have the impression that they were only added so as to be able to claim the label of “modular framework”. If you've ever worked with them, you're probably left with a few ill effects.

Fun fact: the Angular team seems to be pushing developers to use standalone components to reduce the need to use modules. Perhaps this is an indirect admission of the uselessness of the thing.

Writing components is a pain

The main advantage of using a framework is that you can divide your code into reusable components. Generally speaking, this is quite simple: just create a new file (by right-clicking in the desired location or using a keyboard shortcut) and start coding.

With Angular, the complexity of a component makes it necessary to use the command line tool to generate your components. The problem is that this is a tedious task:

  • This requires opening a terminal.
  • You must handwrite the complete path to create your component.
  • If you want to create a component located, for example, in: shared/ui/tables and you make a mistake (by writing, for example, “table” instead of “tables”), you must not only delete the files created, but also delete its declaration in the app.module.ts file.
ICON
Objection

Isn't it possible to create the files by hand like on other frameworks in this case?

It's a possibility, but it requires a ridiculous amount of work:

  • Simply creating a class is very complex (you need to use the decorator, import it and remember the syntax to use). Moreover, the syntax changes if your class is a component, service, module or directive. There's no point wasting memory on this.
  • You have to create a folder and three files by hand. It's laborious.
  • You must then declare your file in the right module (this is not always easy).
Four files opened simultaneously in the IDE with a total of 33 lines to create an Angular component.

For comparison, with React or Svelte you don't have to think about any of this. A React component is just a function (which you can write in two keystrokes with a snippet) and a Svelte component can fit in one line.

Examples of React and Svelte components. The Svelte component has a single line, while the React component has three.

File multiplication and separation

This section echoes the above. One of the points that causes fatigue when coding with Angular is the multiplication and separation of files.

When I talk about file separation, I'm referring to the MVC architecture, which is reflected in Angular's file organization (HTML, TS and Services). In my view, this approach has three problems:

  1. The relationship between elements is no longer obvious. By separating the view and the controller, it's difficult to see exactly what effect user interaction has on the interface. This problem is exacerbated by the fact that data binding is bidirectional on Angular. On React, on the other hand, data flows in only one direction (which makes the architecture and data source search much easier).
  2. This is a consequence of the previous point: as elements are only loosely linked, it's very easy to find yourself in a situation where you no longer know the usefulness of a method or whether or not a CSS style is being used. You end up with a constant residue of dead code or code of uncertain utility (cf Angular doesn't allow you to see unused code).
  3. Juggling from file to file is exhausting. When I'm using Svelte or React, I can focus on one feature, usually contained in one component. With Angular, I'm constantly switching from one file to another to implement a feature.

Everything is slow

All operations on Angular seem to take a while. Just launching the development server can take several minutes on an advanced project, and hot-reload often responds with several seconds of delay.

As an example, here's a “hello world” project launched simultaneously with React and Angular:

Result: the Angular project takes about ten times longer to load. The installation time for dependencies is also longer.

List generation is flawed

Yet this is a fundamental element, and Angular doesn't seem to have the basic tools to generate a list efficiently. If you're using Svelte or React, you'll certainly know that it's recommended (even mandatory in the case of React) to add a key to each list element to avoid unnecessary renderings and to protect against some unexpected behaviors when manipulating lists:

Example of using the 'key' props with React and Svelte.

At first I thought Angular didn't need a key (since, unlike React, no error is displayed if you don't supply it), but digging through the documentation reveals that you need to write a trackBy function to avoid the problems associated with list management.

Now imagine you're developing an application that generates dozens of different lists. Writing a trackBy function for each component is more than laborious. So, just to have the basic functionality of any front-end framework, you'd have to create an entire manager dedicated to list management. I'll let you read this Medium article which shows just how complex this is.

Fun fact: you can write anything in your trackBy function, Angular won't generate any errors.

Use of a function displaying a console.log in Angular's 'trackBy' tag. No errors detected in the IDE.

Implementing i18n is laborious

To get a better idea, let's take a look at how to implement i18n on Angular.

Once the initial configuration is complete, you need to add i18n attributes to your template to indicate the elements to be translated:

<element i18n="{i18n_metadata}">{string_to_translate}</element>

Once this is done, you then need to run a command to generate the reference translation file (which will serve as a model for the other languages):

Terminal window
ng extract-i18n --output-path src/locale

Now, how to generate the translation files for other languages? With a copy and paste!

Terminal window
cp src/locales/messages.xlf src/locales.fr.xlf

An obvious problem then arises: what to do when you want to update the translation file? Laborious copy-and-paste acrobatics. Imagine the work involved when working with several languages.
That said, there is a library that solves this problem. Why go through a library for something like this?

Added to this are a number of other limitations:

  • Translation is router-dependent. In most other frameworks, it is possible to change the language dynamically without changing the URL of the page. With the basic implementation of i18n on Angular, this is not possible.
  • There's no TypeScript support to help with translation (the basic i18n implementation on Angular doesn't use translation keys, by the way). However, this is something that can be found in other frameworks such as Vue or React. As for Svelte, similar behavior can be achieved with typesafe-i18n but this library is not specific to Svelte and is no longer maintained.

Inputs handling

On most frameworks, you can add attributes to your components to make them generic and reusable. What's more, if you forget an attribute, your IDE will let you know, and you can also make certain attributes optional:

Example of React and Svelte components with attributes. If a mandatory attribute is missing, an error is displayed.

Now let's take the same example with Angular:

Example of Angular components with attributes. No error is displayed if a seemingly mandatory attribute is omitted.

As you can see, no errors are detected, and it's impossible to know which attributes are required and which are not, unless you go through the component with a fine-tooth comb.

ICON
Objection

— Since version 16 of Angular, it's possible to add required inputs.
— This means that developers have had to manage components in this way for seven years… But there's no need to be too enthusiastic: the Inputs API is still too complex, and all Inputs are optional by default when they should be mandatory.

DOM pollution

During development, you'll see your DOM polluted by HTML comments added by Angular. This makes reading and debugging a real pain:

A representation of the DOM with several parasitic HTML comments added by Angular.

Environmental pollution

Not using Angular even has a beneficial effect on the environment! In addition to these figures, which are to be taken cum grano salis, Angular's bundle size remains much larger than other frameworks, which can have an effect on performance and infrastructure costs.

…and other things I won't take the time to detail

Even in a whisper.