This migration path focuses on AngularJS UI Router and Angular Router interopability,
This method not good for everyone! The main characteristics of this path is that the AngularJS and Angular apps does not share the same layout. So new components are always introduced in Angular, old components are rewritten and downgraded for AngularJS. Migration of old modules happens at once, when almost all components are updated. UI Router states cannot be reused in Angular, so every state and listener should bre rewriten to routes and event listeners or strategies for Angular Router.
- AngularJS 1.6.5, UI Router 0.4.2, Angular 4.3.5
- AngularJS app architecture follows John Papa's AngularJS Styleguide
- Webpack and ES6 introduced to the AngularJS project
- Started migration to Angular (at least bootstrap and module exports are done)
- Victor Savkin has a really good blog, how to get started
- The official migration guide gives a good context about the steps to be taken
- The Webpack Introduction written by the Angular team also a great source to get started
- The official Angular quickstart project also great to get started, but I think it is a bit out-of-date (contains more SystemJS than it should)
- The Angular CLI project as a cheat sheet for Webpack configuration
First, configure an UrlHandlingStrategy
in the Angular application:
// src/app/app.module.ts
import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import { RouterModule, UrlHandlingStrategy } from '@angular/router';
class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy {
shouldProcessUrl(url) {
console.log('Should process called for: ' + url);
return url.toString().startsWith('/usage');
}
extract(url) { return url; }
merge(url, whole) { return url; }
}
@Component({
selector: 'app-root',
template: `<div class="ui-view"></div>
<router-outlet></router-outlet>
`
})
export class AppRootComponent {}
@NgModule({
imports: [
BrowserModule,
UpgradeModule,
RouterModule.forRoot([], {useHash: false, initialNavigation: true}),
/* other modules */
],
bootstrap: [AppRootComponent],
declarations: [AppRootComponent],
providers: [
{ provide: UrlHandlingStrategy, useClass: Ng1Ng2UrlHandlingStrategy }
],
})
export class AppModule {
constructor(private upgrade: UpgradeModule) { }
}
RouterModule
configuration initialNavigation: true
will enable us to navigate to Angular routes directly, without going into the AngularJS app first.
Next, we want to inject Angular Router and UrlHandlingStrategy components into our AngualrJS app. Modify the bootstrapping code as follows:
// src/main.ts
import 'angular';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app/app.module';
import { Router, UrlHandlingStrategy } from '@angular/router';
import { modules } from './app/index'; // exports [ RootModule ]
// import { modules } from '../e2e/mock_backend/index'; // exports [ MocksModule, RootModule ]
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
// add factories to RootModule
modules[modules.length - 1].factory('ng2Injector', () => ref.injector)
.factory('ng2UrlHandlingStrategy', () => ref.injector.get(UrlHandlingStrategy))
.factory('ng2Router', () => ref.injector.get(Router));
(<any>ref.instance).upgrade.bootstrap(document.body, modules.map(module => module.name), { strictDi: true });
});
At this point, the Angular Router could intercept navigation, but those events always handeled by UI Router.
In the next steps we want to achive the following behaviour, when navigating to URL that is not a UI Router state, then unload AngularJS content for app root component and delegate routing to Angular.
I have achived it with the following configuration:
// AngularJS states
*
|- guest (abstract)
| |- login
| |- 2FA verify
|- auhtenticated (abstract)
|- app (abstract, provide standard layout)
| |- // all the states
|- ng2 (without template*)
I introduced an ng2
state, which has no template and only servers as a monkey patch for UI Router to unload the content from the top level ui-view
. See the configuration below:
// src/app/components/auth/auth.module.ts
AuthModule.config(($stateProvider, $httpProvider) => {
$stateProvider
.state('guest', {
url: '',
template: main,
redirectTo: 'guest.login'
})
.state('guest.login', {
url: '/login',
template: '<login></login>'
})
.state('guest.verify', {
url: '/verify',
template: `
<verification
credentials="$resolve.credentials">
</verification>
`})
.state('authenticated', {
redirectTo: 'app',
template: '<ui-view/>'
});
// ...
});
// src/app/app.module.ts
AppModule.config(($stateProvider, $urlRouterProvider) => {
// ...
$stateProvider
.state('app', {
url: '',
parent: 'authenticated',
template: '<app></app>',
redirectTo: 'home'
})
.state('ng2', { // state to clean up view
parent: 'authenticated',
});
//...
Next we have to hook into UI Router $urlRouter
to chatch events, where it did not found state defined for the URL:
// src/app/app.module.ts
AppModule.config(($stateProvider, $urlRouterProvider) => {
$urlRouterProvider.otherwise(($injector, $location) => {
const $state = $injector.get('$state');
const ng2UrlHandlingStrategy = $injector.get('ng2UrlHandlingStrategy');
const ng2Router = $injector.get('ng2Router');
const url = $location.url();
if (ng2UrlHandlingStrategy.shouldProcessUrl(url)) {
$state.go('ng2');
ng2Router.navigate([url]);
} else {
$state.go('app');
}
});
// ...
}
And finally, we provide nice directive to AngularJS, that will execute routing to the Angular Router:
// src/app/app.module.ts
AppModule.directive('routerLink', (ng2Router) => {
return {
restrict: 'A',
scope: {
routerLink: '@'
},
link: function(scope, element, attr) {
element.on('click', () => {
ng2Router.navigate([scope.routerLink]);
});
}
};
});
Then we can route to Angular from AngularJS template by:
<a router-link="/usage">Usage report</a>
What exactly do you export/import at this place?