Ship internal tools in hours, not weeks. Real auth, users, jobs, audit logs, and cohesive UI included. Early access $249 $499 → [Get it now]

How to migrate AngularJS app to Angular

Migrating from AngularJS to modern Angular is a significant architectural shift that requires moving from a scope-based model to a component-based architecture.
With over 25 years of experience in software development and as the creator of CoreUI, I have overseen dozens of enterprise-level migrations where stability was the top priority.
The most efficient and modern solution is to use a hybrid application strategy via the @angular/upgrade library, allowing both frameworks to run concurrently during the transition.
By using an incremental approach, you can deliver new features in Angular while gradually refactoring legacy code without a “big bang” rewrite risk.

Use the @angular/upgrade module to bootstrap a hybrid application where AngularJS and Angular components coexist and interact seamlessly.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { UpgradeModule } from '@angular/upgrade/static'
import { AppModule } from './app.module'

platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
  const upgrade = platformRef.injector.get(UpgradeModule)
  upgrade.bootstrap(document.body, ['myLegacyApp'])
})

This code uses the Angular bootstrap process to initialize the modern framework first, then retrieves the UpgradeModule from the injector to manually bootstrap the legacy AngularJS module. This establishes a bridge that allows data flow and dependency injection to work across both frameworks.

1. Preparing the Legacy Environment

Before you can run a hybrid app, you must ensure your AngularJS application is prepared for the transition. This involves moving to a component-based structure in AngularJS 1.5+ and integrating a module bundler like Webpack. Since modern Angular relies on TypeScript, your first step should be renaming your .js files to .ts and fixing basic type errors.

// AngularJS Component preparation
export const LegacyComponent = {
  template: '<div>{{ $ctrl.message }}</div>',
  controller: class LegacyController {
    message = 'Hello from AngularJS'
    static $inject = ['$scope']
    constructor($scope) {
      // Transitioning away from $scope where possible
    }
  }
}

angular.module('myLegacyApp')
  .component('legacyComp', LegacyComponent)

In this phase, you should also create a new Angular project using the Angular CLI to serve as the host for your hybrid environment. The CLI provides the build tools and development server necessary for the dual-framework setup.

2. Configuring the Hybrid Module

The AppModule in your new Angular project acts as the orchestrator. You must import UpgradeModule and define how the legacy services and components will be handled. The ngDoBootstrap method is overridden to prevent the automatic bootstrapping of an Angular component, allowing the UpgradeModule to take control of the DOM.

import { NgModule, DoBootstrap } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { UpgradeModule } from '@angular/upgrade/static'

@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule
  ],
  providers: [
    // Providers go here
  ]
})
export class AppModule implements DoBootstrap {
  constructor(private upgrade: UpgradeModule) {}
  ngDoBootstrap() {
    this.upgrade.bootstrap(document.body, ['myLegacyApp'], { strictDi: true })
  }
}

By setting strictDi: true, you ensure that your legacy code follows best practices for dependency injection, which simplifies the process of replacing old AngularJS components with new Angular ones.

3. Downgrading Angular Components

To use a new Angular component inside an existing AngularJS template, you must “downgrade” it. This creates a wrapper that makes the Angular component look like a standard AngularJS directive. This is the core mechanism for building new features in modern Angular while the shell remains in AngularJS.

import { Component } from '@angular/core'
import { downgradeComponent } from '@angular/upgrade/static'

@Component({
  selector: 'app-modern-button',
  standalone: true,
  template: '<button (click)="onClick()">Modern Button</button>'
})
export class ModernButtonComponent {
  onClick() {
    console.log('Clicked modern button')
  }
}

// Register the downgraded component with AngularJS
angular.module('myLegacyApp')
  .directive(
    'appModernButton',
    downgradeComponent({ component: ModernButtonComponent })
  )

This allows you to start using high-quality libraries like the CoreUI Angular components immediately. For example, you can use a CoreUI Button inside your legacy templates to ensure a consistent, modern UI during the migration period.

4. Upgrading AngularJS Services

Communication between frameworks often requires sharing data services. If you have a legacy authentication or data service in AngularJS that hasn’t been migrated yet, you can “upgrade” it so it can be injected into new Angular components.

// Define a factory for the upgraded service
export function legacyServiceFactory(i: any) {
  return i.get('LegacyDataService')
}

// In your Angular AppModule providers:
{
  provide: 'LEGACY_DATA_SERVICE',
  useFactory: legacyServiceFactory,
  deps: ['$injector']
}

// In your modern Angular component:
import { Inject, Component } from '@angular/core'

@Component({
  selector: 'app-data-view',
  template: '<div>{{ data }}</div>'
})
export class DataViewComponent {
  constructor(@Inject('LEGACY_DATA_SERVICE') private legacyService: any) {
    this.data = this.legacyService.getData()
  }
}

This ensures that your application maintains a “single source of truth” for data, preventing state synchronization bugs while you transition business logic piece by piece.

5. Synchronizing the Routers

Handling routing in a hybrid app is often the most complex task. You can use the UrlHandlingStrategy to tell the Angular router which URLs it should handle and which ones it should ignore (letting the AngularJS router handle them). This allows for a page-by-page migration of the application flow.

import { UrlHandlingStrategy } from '@angular/router'

export class HybridUrlHandlingStrategy implements UrlHandlingStrategy {
  shouldProcessUrl(url: any) {
    // Only handle routes starting with /modern
    return url.toString().startsWith('/modern')
  }
  extract(url: any) { return url }
  merge(url: any, whole: any) { return url }
}

// Register in AppModule providers:
{ provide: UrlHandlingStrategy, useClass: HybridUrlHandlingStrategy }

Using this strategy, you can keep your legacy navigation intact while creating a new, optimized user experience for your migrated sections using an Angular Dashboard Template.

6. UI Refactoring with CoreUI

One of the biggest challenges in migration is visual inconsistency. As you move components to Angular, the styling often breaks or looks dated. This is where a robust UI library becomes essential. By integrating CoreUI, you provide a unified design system that works across both legacy and modern parts of the app.

// Example of a modern CoreUI-based component in your hybrid app
import { Component } from '@angular/core'
import { CardModule, ButtonModule } from '@coreui/angular'

@Component({
  selector: 'app-migration-card',
  standalone: true,
  imports: [CardModule, ButtonModule],
  template: `
    <c-card>
      <c-card-body>
        <h5 cCardTitle>System Status</h5>
        <p cCardText>This section is now running on Angular.</p>
        <button cButton color="primary">Action</button>
      </c-card-body>
    </c-card>
  `
})
export class MigrationCardComponent {}

Using the Free Angular Admin Template as a baseline for your new Angular shell can drastically reduce the time spent on layout and navigation issues, allowing your team to focus strictly on migrating business logic.

Best Practice Note:

Always migrate services before components whenever possible. Services are the “glue” of your application, and having them in Angular first makes it much easier to build new, pure Angular components that don’t rely on downgradeModule. In CoreUI development, we prioritize clean data flow to ensure that components remain decoupled and easy to test, which is a principle that will serve you well during a complex migration.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team