Isn't it possible to detect unused methods with a linter?
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

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!!

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).

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.
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>
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.
— 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:

But not with Angular:

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 theapp.module.ts
file.
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).

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.

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:
- 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).
- 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).
- 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:

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.

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):
ng extract-i18n --output-path src/locale
Now, how to generate the translation files for other languages?
With a copy and paste
!
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:

Now let's take the same example with Angular:

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.
— 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:

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.