Migration Angular
Maîtrisez la migration d'AngularJS vers Angular, incluant les applications hybrides, la conversion de composants, les changements d'injection de dépendances et la migration du routage.
Quand utiliser cette compétence
- Migrer des applications AngularJS (1.x) vers Angular (2+)
- Exécuter des applications hybrides AngularJS/Angular
- Convertir des directives en composants
- Moderniser l'injection de dépendances
- Migrer les systèmes de routage
- Mettre à jour vers les dernières versions d'Angular
- Implémenter les bonnes pratiques Angular
Stratégies de migration
1. Big Bang (Réécriture complète)
- Réécrire l'ensemble de l'app en Angular
- Développement en parallèle
- Basculer d'un seul coup
- Idéal pour : Petites apps, projets greenfield
2. Incrémentale (Approche hybride)
- Exécuter AngularJS et Angular côte à côte
- Migrer fonctionnalité par fonctionnalité
- ngUpgrade pour l'interopérabilité
- Idéal pour : Grandes apps, livraison continue
3. Tranche verticale
- Migrer une fonctionnalité complètement
- Nouvelles fonctionnalités en Angular, maintenir les anciennes en AngularJS
- Remplacer progressivement
- Idéal pour : Apps de taille moyenne, fonctionnalités distinctes
Configuration d'une app hybride
// main.ts - Bootstrap app hybride
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { UpgradeModule } from "@angular/upgrade/static";
import { AppModule } from "./app/app.module";
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then((platformRef) => {
const upgrade = platformRef.injector.get(UpgradeModule);
// Bootstrap AngularJS
upgrade.bootstrap(document.body, ["myAngularJSApp"], { strictDi: true });
});
// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { UpgradeModule } from "@angular/upgrade/static";
@NgModule({
imports: [BrowserModule, UpgradeModule],
})
export class AppModule {
constructor(private upgrade: UpgradeModule) {}
ngDoBootstrap() {
// Bootstrappé manuellement dans main.ts
}
}
Migration de composants
Contrôleur AngularJS → Composant Angular
// Avant : contrôleur AngularJS
angular
.module("myApp")
.controller("UserController", function ($scope, UserService) {
$scope.user = {};
$scope.loadUser = function (id) {
UserService.getUser(id).then(function (user) {
$scope.user = user;
});
};
$scope.saveUser = function () {
UserService.saveUser($scope.user);
};
});
// Après : composant Angular
import { Component, OnInit } from "@angular/core";
import { UserService } from "./user.service";
@Component({
selector: "app-user",
template: `
<div>
<h2>{{ user.name }}</h2>
<button (click)="saveUser()">Save</button>
</div>
`,
})
export class UserComponent implements OnInit {
user: any = {};
constructor(private userService: UserService) {}
ngOnInit() {
this.loadUser(1);
}
loadUser(id: number) {
this.userService.getUser(id).subscribe((user) => {
this.user = user;
});
}
saveUser() {
this.userService.saveUser(this.user);
}
}
Directive AngularJS → Composant Angular
// Avant : directive AngularJS
angular.module("myApp").directive("userCard", function () {
return {
restrict: "E",
scope: {
user: "=",
onDelete: "&",
},
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<button ng-click="onDelete()">Delete</button>
</div>
`,
};
});
// Après : composant Angular
import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-user-card",
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<button (click)="delete.emit()">Delete</button>
</div>
`,
})
export class UserCardComponent {
@Input() user: any;
@Output() delete = new EventEmitter<void>();
}
// Utilisation : <app-user-card [user]="user" (delete)="handleDelete()"></app-user-card>
Migration de services
// Avant : service AngularJS
angular.module("myApp").factory("UserService", function ($http) {
return {
getUser: function (id) {
return $http.get("/api/users/" + id);
},
saveUser: function (user) {
return $http.post("/api/users", user);
},
};
});
// Après : service Angular
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
@Injectable({
providedIn: "root",
})
export class UserService {
constructor(private http: HttpClient) {}
getUser(id: number): Observable<any> {
return this.http.get(`/api/users/${id}`);
}
saveUser(user: any): Observable<any> {
return this.http.post("/api/users", user);
}
}
Changements d'injection de dépendances
Rétrograder Angular → AngularJS
// Service Angular
import { Injectable } from "@angular/core";
@Injectable({ providedIn: "root" })
export class NewService {
getData() {
return "data from Angular";
}
}
// Rendre disponible pour AngularJS
import { downgradeInjectable } from "@angular/upgrade/static";
angular.module("myApp").factory("newService", downgradeInjectable(NewService));
// Utiliser dans AngularJS
angular.module("myApp").controller("OldController", function (newService) {
console.log(newService.getData());
});
Promouvoir AngularJS → Angular
// Service AngularJS
angular.module('myApp').factory('oldService', function() {
return {
getData: function() {
return 'data from AngularJS';
}
};
});
// Rendre disponible pour Angular
import { InjectionToken } from '@angular/core';
export const OLD_SERVICE = new InjectionToken<any>('oldService');
@NgModule({
providers: [
{
provide: OLD_SERVICE,
useFactory: (i: any) => i.get('oldService'),
deps: ['$injector']
}
]
})
// Utiliser dans Angular
@Component({...})
export class NewComponent {
constructor(@Inject(OLD_SERVICE) private oldService: any) {
console.log(this.oldService.getData());
}
}
Migration du routage
// Avant : routage AngularJS
angular.module("myApp").config(function ($routeProvider) {
$routeProvider
.when("/users", {
template: "<user-list></user-list>",
})
.when("/users/:id", {
template: "<user-detail></user-detail>",
});
});
// Après : routage Angular
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
const routes: Routes = [
{ path: "users", component: UserListComponent },
{ path: "users/:id", component: UserDetailComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Migration de formulaires
<!-- Avant : AngularJS -->
<form name="userForm" ng-submit="saveUser()">
<input type="text" ng-model="user.name" required />
<input type="email" ng-model="user.email" required />
<button ng-disabled="userForm.$invalid">Save</button>
</form>
// Après : Angular (Template-driven)
@Component({
template: `
<form #userForm="ngForm" (ngSubmit)="saveUser()">
<input type="text" [(ngModel)]="user.name" name="name" required>
<input type="email" [(ngModel)]="user.email" name="email" required>
<button [disabled]="userForm.invalid">Save</button>
</form>
`
})
// Ou Reactive Forms (recommandé)
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
template: `
<form [formGroup]="userForm" (ngSubmit)="saveUser()">
<input formControlName="name">
<input formControlName="email">
<button [disabled]="userForm.invalid">Save</button>
</form>
`
})
export class UserFormComponent {
userForm: FormGroup;
constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
}
saveUser() {
console.log(this.userForm.value);
}
}
Calendrier de migration
Phase 1 : Configuration (1-2 semaines)
- Installer Angular CLI
- Configurer l'app hybride
- Configurer les outils de build
- Configurer les tests
Phase 2 : Infrastructure (2-4 semaines)
- Migrer les services
- Migrer les utilitaires
- Configurer le routage
- Migrer les composants partagés
Phase 3 : Migration de fonctionnalités (variable)
- Migrer fonctionnalité par fonctionnalité
- Tester en profondeur
- Déployer progressivement
Phase 4 : Nettoyage (1-2 semaines)
- Supprimer le code AngularJS
- Supprimer ngUpgrade
- Optimiser le bundle
- Tests finaux