angular-migration

Par wshobson · agents

Migrez d'AngularJS vers Angular en utilisant le mode hybride, la réécriture incrémentielle des composants et la mise à jour de l'injection de dépendances. À utiliser lors de la mise à niveau d'applications AngularJS, de la planification de migrations de framework ou de la modernisation de code Angular legacy.

npx skills add https://github.com/wshobson/agents --skill angular-migration

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

Skills similaires