cyberangles guide

Migrating from AngularJS to Angular: A Complete Walkthrough

AngularJS (often called "Angular 1") revolutionized web development when it was released in 2010, introducing concepts like two-way data binding, dependency injection, and MVC architecture. However, as web development evolved, AngularJS began to show limitations in performance, scalability, and support for modern JavaScript standards. In 2016, Google released Angular (initially "Angular 2"), a complete rewrite built on TypeScript, with a component-based architecture, improved performance, and robust tooling. Today, AngularJS is in long-term support (LTS) until December 31, 2023, with no new features or security updates planned beyond that. For businesses relying on AngularJS apps, migrating to Angular is not just a choice but a necessity to ensure security, maintainability, and access to modern web capabilities. This blog provides a step-by-step guide to migrating from AngularJS to Angular, covering everything from understanding key differences to post-migration optimization. Whether you’re a developer leading a migration or a team member contributing to the effort, this walkthrough will help you navigate the process smoothly.

Table of Contents

  1. Understanding the Differences Between AngularJS and Angular
  2. Pre-Migration Checklist: Preparing for Success
  3. Choosing a Migration Strategy
  4. Step-by-Step Migration Process
  5. Handling Common Challenges
  6. Testing the Migrated Application
  7. Post-Migration Optimization
  8. Conclusion
  9. References

Understanding the Differences Between AngularJS and Angular

Before diving into migration, it’s critical to understand the core differences between AngularJS and Angular. This knowledge will help you anticipate challenges and plan effectively.

1.1 Architecture: MVC vs. Component-Based

  • AngularJS: Uses an MVC (Model-View-Controller) or MVVM (Model-View-ViewModel) architecture with controllers and $scope. Views are defined in HTML with directives (e.g., ng-repeat, ng-click), and business logic lives in controllers.
    Example AngularJS Controller:

    // AngularJS Controller
    angular.module('myApp')
      .controller('UserController', function($scope, UserService) {
        $scope.users = [];
        $scope.loadUsers = function() {
          UserService.getUsers().then(function(data) {
            $scope.users = data;
          });
        };
        $scope.loadUsers();
      });
  • Angular: Embraces a component-based architecture. Everything is a component (reusable, self-contained units with their own template, class, and styles). Controllers are replaced by component classes, and $scope is obsolete—data is managed via component properties and services.
    Example Angular Component:

    // Angular Component
    import { Component, OnInit } from '@angular/core';
    import { UserService } from './user.service';
    
    @Component({
      selector: 'app-user',
      template: `
        <ul>
          <li *ngFor="let user of users">{{ user.name }}</li>
        </ul>
      `
    })
    export class UserComponent implements OnInit {
      users: any[] = [];
    
      constructor(private userService: UserService) {}
    
      ngOnInit(): void {
        this.loadUsers();
      }
    
      loadUsers(): void {
        this.userService.getUsers().subscribe(data => {
          this.users = data;
        });
      }
    }

1.2 Language: JavaScript vs. TypeScript

  • AngularJS: Written in plain JavaScript (ES5/ES6).
  • Angular: Built with TypeScript (a superset of JavaScript with static typing). TypeScript enforces type safety, improves tooling (autocompletion, refactoring), and reduces runtime errors.

1.3 Dependency Injection (DI)

  • AngularJS: DI is based on string identifiers (e.g., function($scope, $http)). Minification can break DI if not annotated (requires $inject or inline array notation).

    // AngularJS Service with DI annotation
    angular.module('myApp')
      .service('UserService', ['$http', function($http) {
        this.getUsers = function() {
          return $http.get('/api/users').then(res => res.data);
        };
      }]);
  • Angular: DI is more robust and type-based. Services are injected via constructor parameters, and TypeScript types eliminate the need for string annotations.

    // Angular Service with DI
    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    
    @Injectable({ providedIn: 'root' })
    export class UserService {
      constructor(private http: HttpClient) {} // HttpClient injected via DI
    
      getUsers() {
        return this.http.get('/api/users');
      }
    }

1.4 Routing

  • AngularJS: Uses ngRoute (basic) or third-party libraries like ui-router for routing. Routes are defined with $routeProvider.
  • Angular: Includes a built-in RouterModule with advanced features (lazy loading, route guards, nested routes). Routes are defined with RouterModule.forRoot().

1.5 Template Syntax

  • AngularJS: Uses ng-* directives (e.g., ng-repeat, ng-bind). Two-way binding is ng-model.
  • Angular: Uses *ngFor (instead of ng-repeat), {{ }} for interpolation, and [(ngModel)] for two-way binding (with stricter typing). Structural directives (e.g., *ngIf) and attribute directives (e.g., [ngClass]) are more powerful.

Pre-Migration Checklist: Preparing for Success

Migrating without preparation is risky. Use this checklist to lay the groundwork:

2.1 Assess Your AngularJS App

  • Size and Complexity: Small apps (e.g., single-page tools) may be candidates for a full rewrite. Large enterprise apps with many components/services require incremental migration.
  • Technical Debt: Identify outdated patterns (e.g., global $scope usage, callback hell) and refactor them in AngularJS first to simplify migration.
  • Third-Party Libraries: Audit dependencies (e.g., lodash, moment.js). Check if they have Angular-compatible versions (e.g., @angular/material instead of AngularJS Material).

2.2 Upskill Your Team

  • TypeScript: Train the team on TypeScript basics (types, interfaces, classes) using TypeScript Handbook.
  • Angular Fundamentals: Use Angular’s official tutorial to learn components, services, routing, and RxJS (Angular’s reactive programming library).

2.3 Set Clear Goals and Timeline

  • Define success metrics (e.g., “Migrate user authentication module by Q1”).
  • Break the project into phases (e.g., migrate shared services first, then UI components).

2.4 Set Up Tooling

  • Install Angular CLI: npm install -g @angular/cli (scaffolds projects, runs builds/tests).
  • Use a code editor with TypeScript support (VS Code with Angular Language Service extension).

Choosing a Migration Strategy

Three common strategies exist; choose based on your app’s size and team constraints:

3.1 Hybrid Migration (ngUpgrade)

What: Run AngularJS and Angular side-by-side in a “hybrid” app using @angular/upgrade. Gradually replace AngularJS components with Angular ones.
Best For: Large, complex apps where a full rewrite is too risky.
Pros: Minimizes downtime; incremental testing.
Cons: Requires maintaining both frameworks temporarily.

3.2 Incremental Rewrite (Feature-by-Feature)

What: Build new features in Angular while maintaining old ones in AngularJS. Over time, migrate existing features to Angular.
Best For: Teams with ongoing development needs.

3.3 Full Rewrite

What: Discard AngularJS and rebuild the app from scratch in Angular.
Best For: Small apps, or apps with severe technical debt that make incremental migration impractical.
Pros: Clean slate, modern architecture.
Cons: High risk of delays; requires freezing new features during rewrite.

Recommendation: For most enterprise apps, start with the hybrid approach (Section 3.1) using ngUpgrade.

Step-by-Step Migration Process

Let’s walk through migrating an app using the hybrid strategy with ngUpgrade.

4.1 Set Up the Hybrid App

Goal: Bootstrap both AngularJS and Angular in the same app.

  1. Create a New Angular App with Angular CLI:

    ng new my-hybrid-app --routing --style=css
    cd my-hybrid-app
  2. Install AngularJS and ngUpgrade:

    npm install angular @angular/upgrade
  3. Bootstrap the Hybrid App:

    • AngularJS apps are bootstrapped with angular.bootstrap(). Angular apps use platformBrowserDynamic().bootstrapModule().
    • Use UpgradeModule to bridge them.

    Angular Main Module (app.module.ts):

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { UpgradeModule } from '@angular/upgrade/static';
    import { AppComponent } from './app.component';
    
    @NgModule({
      declarations: [AppComponent],
      imports: [BrowserModule, UpgradeModule], // Add UpgradeModule
      bootstrap: [AppComponent]
    })
    export class AppModule {
      constructor(private upgrade: UpgradeModule) {}
    
      // Bootstrap AngularJS after Angular
      ngDoBootstrap() {
        this.upgrade.bootstrap(document.body, ['myAngularJSApp']);
      }
    }

    AngularJS App (angularjs-app.js):

    // Define AngularJS module
    angular.module('myAngularJSApp', []);

4.2 Migrate Components Incrementally

Start with simple, low-risk components (e.g., a header or footer) and move to complex ones (e.g., forms, data tables).

4.2.1 Downgrade Angular Components to AngularJS

Use downgradeComponent to use Angular components in AngularJS templates.

Example: Angular Component → AngularJS Template

  1. Create an Angular Component:

    // Angular component to downgrade
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-greeting',
      template: `<h1>Hello, {{ name }}!</h1>`
    })
    export class GreetingComponent {
      name = 'Angular';
    }
  2. Downgrade It in AngularJS:

    import { downgradeComponent } from '@angular/upgrade/static';
    import { GreetingComponent } from './greeting.component';
    
    // Register downgraded component in AngularJS module
    angular.module('myAngularJSApp')
      .directive('appGreeting', downgradeComponent({ component: GreetingComponent }) as angular.IDirectiveFactory);
  3. Use in AngularJS Template:

    <!-- AngularJS template -->
    <div ng-app="myAngularJSApp">
      <app-greeting></app-greeting> <!-- Renders Angular component -->
    </div>

4.2.2 Upgrade AngularJS Components to Angular

Use upgradeComponent to use AngularJS components in Angular templates.

Example: AngularJS Component → Angular Template

  1. AngularJS Component:

    angular.module('myAngularJSApp')
      .component('userProfile', {
        template: '<p>{{ $ctrl.user.name }}</p>',
        bindings: { user: '<' }, // One-way binding
        controller: function() {}
      });
  2. Upgrade It in Angular:

    import { upgradeComponent } from '@angular/upgrade/static';
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-angular-host',
      template: `
        <div>
          <!-- Use upgraded AngularJS component -->
          <user-profile [user]="currentUser"></user-profile>
        </div>
      `
    })
    export class AngularHostComponent {
      currentUser = { name: 'John Doe' };
    }
    
    // Register upgraded component in Angular module
    @NgModule({
      declarations: [AngularHostComponent, upgradeComponent('userProfile')] // Upgrade AngularJS component
    })
    export class AppModule {}

4.3 Migrate Services

Services often contain business logic and are critical to migrate early. Use upgradeInjectable to upgrade AngularJS services to Angular.

Example: Upgrade AngularJS Service

  1. AngularJS Service:

    angular.module('myAngularJSApp')
      .service('userService', function($http) {
        this.getUsers = function() {
          return $http.get('/api/users').then(res => res.data);
        };
      });
  2. Upgrade to Angular:

    import { upgradeInjectable } from '@angular/upgrade/static';
    
    // Upgrade AngularJS service for use in Angular
    angular.module('myAngularJSApp')
      .service('userService', upgradeInjectable('userService') as angular.InjectableProvider);
    
    // Use in Angular component
    import { Component } from '@angular/core';
    import { userService } from './angularjs-app'; // Import upgraded service
    
    @Component({ selector: 'app-user', template: `...` })
    export class UserComponent {
      constructor(private userService: userService) { // Injected via Angular DI
        this.userService.getUsers().then(users => console.log(users));
      }
    }

4.4 Migrate Routing

Replace AngularJS ngRoute or ui-router with Angular’s RouterModule.

Example: Angular Routing Setup

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserComponent } from './user.component';
import { HomeComponent } from './home.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', component: UserComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Update templates to use Angular’s router-outlet and routerLink:

<!-- Angular template -->
<nav>
  <a routerLink="/">Home</a> | <a routerLink="/users">Users</a>
</nav>
<router-outlet></router-outlet> <!-- Renders routed components -->

4.5 Remove AngularJS

Once all components, services, and routes are migrated:

  1. Remove UpgradeModule from AppModule.
  2. Delete AngularJS code and dependencies.
  3. Bootstrap Angular directly:
    // app.module.ts (after AngularJS removal)
    @NgModule({
      declarations: [AppComponent],
      imports: [BrowserModule, AppRoutingModule],
      bootstrap: [AppComponent] // No more ngDoBootstrap
    })
    export class AppModule {}

Handling Common Challenges

5.1 Two-Way Data Binding

AngularJS uses ng-model for two-way binding. Angular uses [(ngModel)], but it requires importing FormsModule:

// app.module.ts
import { FormsModule } from '@angular/forms';

@NgModule({ imports: [BrowserModule, FormsModule] }) // Add FormsModule

Template:

<!-- Angular two-way binding -->
<input [(ngModel)]="username" />
<p>Hello, {{ username }}!</p>

5.2 Third-Party Libraries

  • AngularJS Libraries: Replace with Angular equivalents (e.g., angular-ui-bootstrapngx-bootstrap or @angular/material).
  • Unmaintained Libraries: Rewrite custom logic or use modern alternatives (e.g., rxjs instead of callback-based APIs).

5.3 State Management

If your AngularJS app used ui-router or custom state management, migrate to Angular’s RouterModule or add a state management library like NgRx (Angular’s Redux equivalent).

Testing the Migrated Application

Testing ensures no regressions. Use these tools:

  • Unit Testing: Jasmine/Karma (Angular’s default) to test components/services. Example:

    import { TestBed } from '@angular/core/testing';
    import { UserComponent } from './user.component';
    
    describe('UserComponent', () => {
      beforeEach(async () => {
        await TestBed.configureTestingModule({
          declarations: [UserComponent]
        }).compileComponents();
      });
    
      it('should load users on init', () => {
        const fixture = TestBed.createComponent(UserComponent);
        const component = fixture.componentInstance;
        fixture.detectChanges(); // Triggers ngOnInit
        expect(component.users.length).toBeGreaterThan(0);
      });
    });
  • End-to-End Testing: Cypress or Protractor to test user flows (e.g., “login → view dashboard”).

Post-Migration Optimization

After migration, optimize performance and maintainability:

  • Tree Shaking: Angular CLI removes unused code by default with --prod builds.

  • Lazy Loading: Split routes into feature modules and load them on demand:

    // app-routing.module.ts
    const routes: Routes = [
      { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
    ];
  • Bundle Size: Use ng build --prod --stats-json and Webpack Bundle Analyzer to identify large dependencies.

Conclusion

Migrating from AngularJS to Angular is a significant undertaking, but with careful planning, incremental migration, and the right tools, it’s manageable. The hybrid approach with ngUpgrade minimizes risk, while TypeScript and Angular’s component model improve maintainability and performance.

By following this walkthrough, your team can transition smoothly, unlocking Angular’s modern features (lazy loading, RxJS, static typing) and ensuring long-term support for your application.

References