Update:
This post was written when Angular was in beta. The upgrade process is still very similar in spirit but the API's have changed. Thorough and up to date upgrade examples can be found in the angular.io documentation: https://angular.io/guide/upgrade
Now that Angular 2 is in beta, one of the more interesting questions at hand is how can we migrate existing Angular 1.x applications into Angular 2? Fortuntely, the Angular development team have thought about this same question and have introduced the upgrade module as a way to help seamlessly upgrade your application. In this blog post, we'll explore how to downgrade and upgrade components and services from Angular 1.x to Angular 2 and vice versa!
Migration Plan
An Angular codebase is usually built around services, directives, and in Angular 1.5 - component directives. Using the upgrade module allows an Angular 1.x and Angular 2 application to co-exist by upgrading and downgrading services and components to work with one another harmoniously. This is fantasic as we don't have to convert an entire codebase into Angular 2, we can incrementally pick and choose what parts of our application we want to upgrade!
Another interesting note, is that since TypeScript is a superset of plain JavaScript, you don't have to throw away old Angular 1.x based JavaScript code - it will run fine along any new Angular 2 TypeScript code. This makes upgrading to Angular 2 even more seamless and easy to do!
Components
Downgrading Components
We need to downgrade an Angular 2 component if we want to use it with the rest of our Angular 1.x application. Take a look at the following Angular 2 component:
@Component({
selector: 'angular2-component',
template: `<p>{{message}}</p>`
})
class Angular2Component {
...
}
In order to downgrade this as a regular Angular 1.x component directive we take advantage of the downgradeNg2Component method in the upgrade adapter class from the angular/upgrade module. Lets see how this is done:
import {UpgradeAdapter} from 'angular2/upgrade';
let upgradeAdapter = new UpgradeAdapter();
app.directive('Angular2Component',
upgradeAdapter.downgradeNg2Component(Angular2Component))
Cool, we've converted our Angular 2 Component into an Angular 1 directive by using the downgradeNg2Component method, then we register this Angular 1 directive as we would any other directive - with a call to .directive.
Even though we have registered this Angular 2 component using Angular 1.x, the view of this component is still controlled through the Angular 2 definition. If we want to change the message property to something different, we would do it though the Angular2Component class, and this change would propagate to our Angular 1 component directive. This is really handy! Now we build Angular 1 components that take advantage of Angular 2 features.
Upgrading Components
Components in Angular 1.x applications must be factored in a certain way in order for the upgrade module to make them work with Angular 2. Using directives, ideally component directives from Angular 1.5, must have the following properties:
- restrict: 'E'
- scope: {}
- bindToController: {}
- controllerAs
- template or templateUrl
- transclude (optional)
- require (optional)
Component directives should not use the following attributes:
- compile
- replace: true
- priority/terminal
Given these property constraints, lets take a look at an Angular 1.x component directive that we'll use for upgrading:
export function a1UpgradableDirective() {
return {
restrict: 'E',
scope: {},
bindToController: {},
controller: Upgradable,
controllerAs: 'a1Upgradable',
template: `<span>{{ a1Upgradable.message }}</span>`
};
}
class Upgradable {
message = 'I am an Angular 1 Directive';
}
This is a basic component directive - all it does is display a message. Now, how can we use this component directive in an Angular 2 component? Lets take a look:
import {Component} from 'angular2/core';
import {UpgradeAdapter} from 'angular2/upgrade';
let upgradeAdapter = new UpgradeAdapter()
@Component({
selector: 'a2-using-a1',
directives: [upgradeAdapter.upgradeNg1Component('a1Upgradable')]
})
export class A2UsingA1Component {
message = 'Angular 2 using Angular 1: ';
}
First thing you may notice here is the UpgradeAdapter class. We use the method upgradeNg1Component by passing in the name of our Angular 1.x component directive (as defined in the controllerAs property). This returns an instance of our Angular 1.x component directive that is now of the same type as an Angular 2 component. Since now our Angular 1.x component directive has been upgraded to work with Angular 2 components, we can pass it into the directives field of another Angular 2 component decorator and have it magically work!
Finally, our upgrading process requires one final step:
import {a1UpgradableDirective} from './components/a1-upgradable';
import * as angular from 'angular';
angular
.module(APPNAME)
.directive('a1Upgradable', a1UpgradableDirective)
Since our application is operating with both Angular 1.x and Angular 2, and using ng-upgrade to bridge the gap, we still must register any angular 1.x component directive using the old angular 1.x way.
Services
Unlike components, the way Angular 2 services are constructed remains more or less the same. Services are essentially classes with methods and properties that the rest of our application uses to get or post data. The big change in Angular 2 is how these services are injected throughout the application. If we have a service written in Angular 1.x, how can we make this service avaliable to Angular 2 components? And vice versa, how can we make Angular 2 services avaliable to components in Angular 1.x?
Upgrading Services
The strategy used to upgrade an Angular 1.x service to work with an Angular 2 component is fairly straightforward - we explicitly inject the Angular 1.x service into an Angular 2 component as a provider, and then upgrade that provider. Lets take a look at how this is done - consider the following simple Angular 1.x service:
class A1UpgradeService {
getMessage () {
return 'Hello from Angular 1 service!';
}
}
app.service('A1UpgradeService', A1UpgradeService);
Now we want to inject this into an Angular 2 component. In order to do this we must use the upgrade adapter to make the A1UpgradeService available to Angular 2's dependecy injection system.
...
let upgradeAdapter = new UpgradeAdapter();
upgradeAdapter.upgradeNg1Provider('A1UpgradeService');
...
Looks good, now we can use this Angular 1.x service in our Angular 2 components!
import {Component, Inject} from 'angular2/core';
import {A1UpgradeService} from '../services/a1-upgrade-service';
@Component({
selector: 'a2-using-a1-service',
template: `<p>{{ message }}</p>`
})
export class A2UsingA1Service {
message = '';
constructor(a1UpgradeService: A1UpgradeService) {
this.message = a1UpgradeService.getMessage();
}
}
As you can see, our Angular 2 component now has access to the Angular 1.x service which has been upgraded to work with the Angular 2 parts of our application.
Downgrading Services
Turning an Angular 2 service into a compatable Angular 1.x service is exceptionally easy. Using the addProvider method from the upgrade adapter we can register an Angular 2 service in an Angular 1.x context and use it with Angular 1.x component directives. Take a look at the following Angular 2 service:
import {Injectable} from 'angular2/core';
@Injectable()
export class A2DowngradeService {
getMessage () {
return 'Angular 2 service';
}
}
Since Angular 2 is bootstrapped with the upgrade adapter, there is no place to register Angular 2 services. Fortunately the upgrade adapter's addProvider method can do this:
upgradeAdapter.addProvider(A2DowngradeService);
Now, Angular 1.x must be informed of this new service
import {A2DowngradeService} from './services/a2-downgrade'
import * as angular from 'angular';
import {UpgradeAdapter} from 'angular/upgrade';
let upgradeAdapter = new UpgradeAdapter();
// Register classic Angular 1 modules
angular
.module('angular-upgrade-example')
.factory('a2DowngradeService',
upgradeAdapter.downgradeNg2Provider(A2DowngradeService));
To register our Angular 2 service with Angular 1.x, we use the downgradeNg2Provider which gives us a downgraded version we can use in the Angular 1.x parts of our application!'
Conclusions
We've seen how to downgrade and upgrade components and services using the upgrade module. With such a powerful tool, we can incrementally upgrade parts of an application and take advantage of Angular 2 features and performance, without entirely throwing away old code.