Angular Migration
Master AngularJS to Angular migration, including hybrid apps, component conversion, dependency injection changes, and routing migration.
When to Use This Skill
-
Migrating AngularJS (1.x) applications to Angular (2+)
-
Running hybrid AngularJS/Angular applications
-
Converting directives to components
-
Modernizing dependency injection
-
Migrating routing systems
-
Updating to latest Angular versions
-
Implementing Angular best practices
Migration Strategies
- Big Bang (Complete Rewrite)
-
Rewrite entire app in Angular
-
Parallel development
-
Switch over at once
-
Best for: Small apps, green field projects
- Incremental (Hybrid Approach)
-
Run AngularJS and Angular side-by-side
-
Migrate feature by feature
-
ngUpgrade for interop
-
Best for: Large apps, continuous delivery
- Vertical Slice
-
Migrate one feature completely
-
New features in Angular, maintain old in AngularJS
-
Gradually replace
-
Best for: Medium apps, distinct features
Hybrid App Setup
// main.ts - Bootstrap hybrid app 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() { // Bootstrapped manually in main.ts } }
Component Migration
AngularJS Controller → Angular Component
// Before: AngularJS controller 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);
};
});
// After: Angular component 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); } }
AngularJS Directive → Angular Component
// Before: AngularJS directive
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> ,
};
});
// After: Angular component 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>();
}
// Usage: <app-user-card [user]="user" (delete)="handleDelete()"></app-user-card>
Service Migration
// Before: AngularJS service 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); }, }; });
// After: Angular service 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); } }
Dependency Injection Changes
Downgrading Angular → AngularJS
// Angular service import { Injectable } from "@angular/core";
@Injectable({ providedIn: "root" }) export class NewService { getData() { return "data from Angular"; } }
// Make available to AngularJS import { downgradeInjectable } from "@angular/upgrade/static";
angular.module("myApp").factory("newService", downgradeInjectable(NewService));
// Use in AngularJS angular.module("myApp").controller("OldController", function (newService) { console.log(newService.getData()); });
Upgrading AngularJS → Angular
// AngularJS service angular.module('myApp').factory('oldService', function() { return { getData: function() { return 'data from AngularJS'; } }; });
// Make available to 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'] } ] })
// Use in Angular @Component({...}) export class NewComponent { constructor(@Inject(OLD_SERVICE) private oldService: any) { console.log(this.oldService.getData()); } }
Routing Migration
// Before: AngularJS routing angular.module("myApp").config(function ($routeProvider) { $routeProvider .when("/users", { template: "<user-list></user-list>", }) .when("/users/:id", { template: "<user-detail></user-detail>", }); });
// After: Angular routing 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 {}
Forms Migration
<!-- Before: 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>
// After: 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>
})
// Or Reactive Forms (preferred) 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); } }
Migration Timeline
Phase 1: Setup (1-2 weeks)
- Install Angular CLI
- Set up hybrid app
- Configure build tools
- Set up testing
Phase 2: Infrastructure (2-4 weeks)
- Migrate services
- Migrate utilities
- Set up routing
- Migrate shared components
Phase 3: Feature Migration (varies)
- Migrate feature by feature
- Test thoroughly
- Deploy incrementally
Phase 4: Cleanup (1-2 weeks)
- Remove AngularJS code
- Remove ngUpgrade
- Optimize bundle
- Final testing