ngUpgrade which is included in Angular 2+ (hereinafter just called Angular) allows for the creation of hybrid applications that contains both, AngularJS 1.x based and Angular 2+ based services and components. This helps to migrate an existing AngularJS 1.x application to Angular 2+ step by step. The downside of this approach is that an application needs to load both versions of Angular.
Fortunately, beginning with Angular 2.2.0 which came out in mid-November 2016, there is an implementation of ngUpgrade that allows for ahead of time compilation (AOT). That means that the size of the Angular part can be reduced to the constructs of the framework that are needed by using tree shaking.
In this post, Im showing how to use this implementation by an example Ive prepared for ngEurope. It contains several components and services written with AngularJS 1.x and Angular:

While a hybrid application is always bootstrapped as an AngularJS 1.x application, it can contain services and components written with both versions. To use an AngularJS 1.x building block (component or service) within an Angular building block, it needs to be upgraded. This means that it gets a wrapper that makes it look like an Angular component or service. For using an Angular building block within AngularJS 1.x it needs to get downgraded. This also means that a wrapper is generated. In this case, this wrapper makes it look like an AngularJX 1.x counterpart. The following picture demonstrates this. The arrows show whether the respective building block is up- or downgraded:

The full sample used here can be found at github.
Downgrading an Angular Component to AngularJS 1.x
To downgrade an Angular Component to AngularJS 1.x, the sample uses the new method downgradeComponent
that is located in the module @angular/upgrade/static
. The result of this method can be registered as a directive with an AngularJS 1.x module:
import { downgradeComponent } from @angular/upgrade/static;
var app = angular.module(flight-app, [...]);
[...]
app.directive(flightSearch, <any>downgradeComponent({ component: FlightSearchComponent }));
After this, the AngularJS 1.x part of the hybrid application can use this directive. The sample presented here uses it for instance within a template for a route:
$stateProvider
[...]
.state(flightBooking.flightSearch, {
url: /flight,
template: <flight-search></flight-search>
});
Downgrading an Angular Service to AngularJS 1.x
The procedure to downgrade an Angular service to AngularJS 1.x is similar to the process for downgrading a component: for this, the module @angular/upgrade/static
provides a method @angular/upgrade/static
`. Its return value can be registered as an AngularJS 1.x factory:
import { downgradeInjectable } from @angular/upgrade/static;
var app = angular.module(flight-app, [...]);
[...]
app.factory(passengerService, downgradeInjectable(PassengerService));
Then, the service can be injected in an AngularJS 1.x building block:
class PassengerSearchController {
constructor(private passengerService: PassengerService) {
}
[...]
}
Here it is important to remember that AngularJS 1.x uses names and not types for injection. Because of this, the presented Controller has to name the constructor argument passengerService
. The mentioned typed is irrelevant for dependency injection.
Upgrading an AngularJS 1.x Component to Angular
Upgrading an AngularJS 1.x component to Angular is a bit more complicated. In this case, the application has to provide a wrapper for the upgraded component by subclassing UpgradeComponent
:
import {UpgradeComponent} from "@angular/upgrade/static";
[...]
@Directive({selector: flight-card})
export class FlightCard extends UpgradeComponent implements OnInit, OnChanges {
@Input() item: Flight;
@Input() selectedItem: Flight;
@Output() selectedItemChange: EventEmitter<any>;
constructor(elementRef: ElementRef, injector: Injector) {
super(flightCard, elementRef, injector);
}
ngOnInit() { return super.ngOnInit(); }
ngOnChanges(c) { return super.ngOnChanges(c); }
}
Unfortunately, this cannot be moved to a convenience function because it would prevent the compiler from finding the required metadata. The wrapper needs to have an input for each ingoing property of the AngularJS 1.x component and an output for each event. ngUpgrade will connect them to the respective counterparts in the AngularJS 1.x component. To tell ngUprade which AngularJS 1.x component to wrap, the constructor has to delegate its canonical name to the base constructor using super
. In addition, super
also needs an instance of ElementRef
and Injector
that can be obtained via dependeny injection.
In addition to that, the wrapper needs to implement life cycle hooks which are relevant for the AngularJS 1.x component. In any case, it needs to implement ngOnInit
because ngUpgrade
is picking up this method via reflection and using it to instantiate the wrapped component. Its fully sufficient to just make this method to delegate to the base implementation. To support data binding, the wrapper needs to have an ngOnChanges
method for the same reason.
After this, the wrapper can be registered with an Angular 2 module.
import {UpgradeModule} from "@angular/upgrade/static";
@NgModule({
imports: [
[...],
UpgradeModule
],
[...],
declarations: [
FlightSearchComponent,
FlightCard // <-- Upgraded Component
],
[...]
})
export class AppModule {
ngDoBootstrap() {}
}
This module has to import the UpgradeModule
. As it provides Angular building blocks for an application that is bootstrapped with AngularJS 1.x, it does not contain own root components. However, it needs to be bootstrapped too and to make Angular to bootstrap a module without at least one top level component, the module class needs to have an ngDoBootstrap
method.
After registering the wrapper, it can be used in the template of other Angular components:
<flight-card
[item]="f"
[selectedItem]="selectedFlight"
(selectedItemChange)="selectedFlight = $event"></flight-card>
Upgrading an AngularJS 1.x Service to Angular
To upgrade an AngularJS 1.x service, the application has to to provide a function that takes an AngularJS 1.x injector and uses it to return the service in question:
export function createFlightService(injector) {
return injector.get(flightService);
}
Again, this cannot be moved to a generic convenience function because this would prevent the AOT from finding the necessary metadata. To use this function in the Angular 2 part of the hybrid application, it is registered as a factory function using a provider:
@NgModule({
[...]
providers: [
PassengerService,
{
provide: FlightService,
useFactory: createFlightService,
deps: [$injector]
},
[...]
]
})
export class AppModule {
ngDoBootstrap() {}
}
After this, Angular 2 can inject the service into consumers, e.g. components or services:
@Component({ [...] })
export class FlightSearchComponent {
constructor(
private flightService: FlightService, [...]) {
}
[...]
}
Bootstrapping
To bootstrap an hybrid application, the demo here presented uses a function bootstrap
that has been "borrowed" from the unit tests of ngUpgrade. It bootstraps both, the AngularJS 1.x part as well as the Angular 2 part of the application:
// bootstrap function "borrowed" from the angular test cases
export function bootstrap(
platform: PlatformRef, Ng2Module: NgModuleFactory<{}>, element: Element, ng1ModuleName: string) {
// We bootstrap the Angular 2 module first; then when it is ready (async)
// We bootstrap the Angular 1 module on the bootstrap element
return platform.bootstrapModuleFactory(Ng2Module).then(ref => {
let upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(element, [ng1ModuleName]);
return upgrade;
});
}
bootstrap(
platformBrowser(),
AppModuleNgFactory,
document.body,
flight-app)
.catch(err => console.error(err));
Here it is important to note, that the AngularJS 1.x module is bootstrapped with the UpgradeModules
bootstrap method. This is a replacement for ng-app
or angular.bootstrap
. The passed NgModuleFactory
is created by the AOT compiler when compiling the Angular 2 module.
AOT Compilation
To use the AOT compiler, the application should provide an (additional) tsconfig.json
. This file is called tsconfig.aot.json
in this sample. It contains an angularCompilerOptions
property which tells the compiler where to place the generated files.
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"typings/globals/"
]
},
"files": [
"app/app2.module.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"skipMetadataEmit" : true
}
}
To allow tree shaking for optimizing the size of the Angular 2 part, the module pattern es2015
is choosen. For the compilation and for starting the sample, the file package.json
defines some scripts. The most important one here is ngc
which is starting the AOT compiler and passing the file tsconfig.aot.json
:
[...]
"scripts": {
"webpack": "webpack",
"server": "live-server",
"start": "npm run server",
"ngc": "ngc -p tsconfig.aot.json",
"build": "npm run ngc && npm run webpack"
},
[...]
This sample also uses webpack for bundling, which is executed after the AOT compiler by the script build
. To make this work, one needs to download the package @angular/compiler-cli
. The start
script calls the live-server
and not the webpack-dev-server
because the latter has not fully supported the AOT compiler for recompiling when this text was written.
Build and Starting the Application
To build and start the application described here, the following commands can be used:
npm run build
npm start