AngularJS 1.x vs Angular 21 : architecture, performance, DI, routing, RxJS, Signals — comparaison technique pour choisir ou migrer.
Histoire et contexte
AngularJS (version 1.x) a été créé par Google en 2010 et a révolutionné le développement web en popularisant le two-way data binding et le MVC côté client. Pendant des années, il a dominé l'écosystème frontend.
En 2016, Google publie Angular 2 — une réécriture complète en TypeScript, incompatible avec AngularJS. La confusion des noms persiste encore aujourd'hui : "AngularJS" désigne la version 1.x, "Angular" (sans JS) désigne les versions 2 et supérieures (v21 en 2026).
AngularJS a atteint sa fin de vie officielle le 31 décembre 2021. Les applications AngularJS encore en production ne reçoivent plus de correctifs de sécurité.
Architecture : MVC vs Composants
La différence architecturale la plus profonde est le paradigme de construction de l'UI : AngularJS suit le pattern MVC avec des controllers et des scopes, Angular adopte entièrement l'architecture par composants.
AngularJS : Model-View-Controller
// Controller AngularJS : logique dans un objet $scope
angular.module('app')
.controller('ProductCtrl', function($scope, ProductService) {
$scope.products = []; // modèle
$scope.filter = ''; // état UI
// Charge les produits au démarrage
ProductService.getAll().then(function(data) {
$scope.products = data;
});
// Filtre les produits par nom
$scope.filtered = function() {
return $scope.products.filter(function(p) {
return p.name.includes($scope.filter);
});
};
});
Angular 21 : Architecture composants
// Composant Angular : logique encapsulée avec Signals
@Component({
selector: 'app-products',
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="filter" />
@for (p of filtered(); track p.id) {
<div>{{ p.name }}</div>
}
`,
})
export class ProductsComponent {
private svc = inject(ProductService);
products = signal<Product[]>([]);
filter = signal('');
// computed() recalcule uniquement quand products() ou filter() change
filtered = computed(() =>
this.products().filter(p => p.name.includes(this.filter()))
);
ngOnInit() {
this.svc.getAll().subscribe(data => this.products.set(data));
}
}
| Aspect | AngularJS | Angular 21 |
|---|---|---|
| Paradigme | MVC + $scope | Composants + Signals |
| Unité de base | Controller + Template | Composant autonome |
| État | $scope (mutable) | signal() (réactif) |
| Langage | JavaScript ES5 | TypeScript strict |
| Modules | angular.module() | NgModules / standalone |
Change detection : $digest vs Signals
Le mécanisme de détection des changements est la différence de performance la plus significative entre les deux frameworks.
AngularJS : le cycle $digest (dirty checking)
// AngularJS parcourt TOUS les watchers à chaque cycle
// Un simple clic peut déclencher des centaines de vérifications
angular.module('app')
.controller('PerfCtrl', function($scope) {
// Chaque $watch enregistre une expression à surveiller
$scope.$watch('user.name', function(newVal, oldVal) {
// Déclenché quand user.name change
console.log('Changement détecté:', newVal);
});
// $apply force un nouveau cycle $digest manuellement
// (nécessaire quand on modifie le scope hors d'Angular)
setTimeout(function() {
$scope.$apply(function() {
$scope.user.name = 'Alice'; // sans $apply, l'UI ne se met pas à jour
});
}, 1000);
});
// Problème de performance : avec 2000 watchers,
// chaque interaction déclenche 2000 vérifications
// C'est le "2000 watchers problem" bien documenté
Angular 21 : Signals (réactif, précis)
// Angular Signals : seuls les composants dépendants de ce signal
// sont re-rendus — zéro dirty checking
@Component({ standalone: true, template: `{{ userName() }}` })
export class UserComponent {
// signal() crée une valeur réactive
userName = signal('Bob');
// computed() dépend automatiquement de userName
userLabel = computed(() => 'Utilisateur : ' + this.userName());
// effect() réagit aux changements sans $watch manuel
logEffect = effect(() => {
console.log('Nom mis à jour :', this.userName());
// Déclenché uniquement quand userName() change
});
updateName(name: string) {
this.userName.set(name); // Angular sait exactement quoi re-rendre
}
}
Injection de dépendances
AngularJS : tokens sous forme de chaînes
// AngularJS : DI basé sur les noms de paramètres (fragile en minification)
angular.module('app')
.controller('OrderCtrl', function($scope, OrderService, UserService) {
// Les noms '$scope', 'OrderService', 'UserService' sont des tokens
// PROBLÈME : la minification renomme les paramètres → DI cassée !
});
// Solution : annotation explicite $inject
angular.module('app')
.controller('OrderCtrl', ['$scope', 'OrderService', 'UserService',
function($scope, OrderService, UserService) {
// Maintenant la minification ne casse plus rien
}
]);
Angular 21 : DI basée sur les types TypeScript
// Angular : DI via types — robuste, compile-time safe
@Component({ standalone: true })
export class OrderComponent {
// inject() — pattern moderne (Angular 14+)
private orderService = inject(OrderService);
private userService = inject(UserService);
// Ou via constructeur (pattern classique toujours valide)
constructor(
private http: HttpClient,
private router: Router,
) {}
}
Templates et routing
Templates : directives vs syntaxe native
| Fonctionnalité | AngularJS | Angular 21 |
|---|---|---|
| Condition | ng-if="expr" | @if (expr) { } |
| Boucle | ng-repeat="x in list" | @for (x of list; track x.id) { } |
| Switch | ng-switch | @switch (val) { @case (x) { } } |
| Liaison valeur | ng-model="x" | [(ngModel)]="x" |
| Événement clic | ng-click="fn()" | (click)="fn()" |
| Interpolation | {{ expr }} | {{ expr }} |
| Classe conditionnelle | ng-class="{active: x}" | [class.active]="x" |
| Chargement différé | ❌ (manuel) | @defer { } @loading { } |
Routing comparé
// AngularJS : ngRoute
angular.module('app', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/users', { templateUrl: 'users.html', controller: 'UserCtrl' })
.when('/users/:id', { templateUrl: 'user.html', controller: 'UserDetailCtrl' })
.otherwise({ redirectTo: '/' });
});
// Angular 21 : Router standalone avec lazy loading
export const routes: Routes = [
{ path: 'users', component: UserListComponent },
{ path: 'users/:id', component: UserDetailComponent },
{
path: 'admin',
// Lazy loading : le module admin est chargé à la demande
loadChildren: () => import('./admin/routes').then(m => m.adminRoutes),
canActivate: [authGuard],
},
{ path: '', redirectTo: 'users', pathMatch: 'full' },
];
Performance et bundle size
| Métrique | AngularJS 1.x | Angular 21 (standalone) |
|---|---|---|
| Bundle minimal (gzip) | ~55KB | ~45KB |
| Re-rendu 1000 lignes | ~80ms (dirty check) | ~8ms (Signals) |
| SSR / Hydration | ❌ | ✅ (hydration incrémentale) |
| Tree-shaking | ❌ (tout ou rien) | ✅ (standalone complet) |
| Lazy loading | Partiel (ui-router) | ✅ natif (Router) |
| Web Workers | Difficile | ✅ (Angular CDK) |
| Lighthouse Score | ~60-70 | ~95-100 (optimisé) |
Écosystème et tooling
| Outil | AngularJS | Angular 21 |
|---|---|---|
| CLI | Grunt/Gulp (manuel) | Angular CLI (ng) |
| Bundler | Webpack (manuel) | esbuild + Vite (natif) |
| Tests unitaires | Karma + Jasmine | Vitest / Jest |
| Tests e2e | Protractor (EOL) | Playwright / Cypress |
| State management | $scope, $rootScope | Signals, NgRx Signal Store |
| UI Components | UI Bootstrap (archivé) | Angular Material 3 (MDC) |
| DevTools | Batarang (obsolète) | Angular DevTools v2 (Chrome) |
| Mises à jour sécurité | ❌ (EOL 2021) | ✅ (active) |
Quand choisir quoi ?
Utiliser AngularJS si…
- Vous maintenez une application legacy qui ne peut pas être migrée immédiatement
- Le budget ou le temps de migration n'est pas disponible dans l'immédiat
- L'application est en fin de vie et sera remplacée par un nouveau projet
Utiliser Angular 21 si…
- Vous démarrez un nouveau projet frontend en 2026
- Vous avez besoin de SSR, d'hydration ou de performances Lighthouse élevées
- Votre équipe utilise TypeScript et veut un framework opinionné et maintenu
- Vous avez besoin de support à long terme et de mises à jour de sécurité
- Vous construisez une application enterprise scalable