12

I'm working on migrating little by little a big angular.js application (that uses ui-router) to angular and I opted by using the angular.js application as a base and migrate the different routes one at a time so that once I'm finished I switch everything at once to angular.

These are the steps I've followed:

Bootstap the angular.js application in my main.ts file:

export function bootstrapAngular(extra: StaticProvider[]): any {
  setAngularJSGlobal(angular);
  if (environment.production) {
    enableProdMode();
  }
  return platformBrowserDynamic(extra)
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
}

const downgraded = angular
  .module('downgraded', [downgradeModule(bootstrapAngular)])
  .directive('appRoot', downgradeComponent({ component: RootComponent, propagateDigest: false }))
  ;

angular.bootstrap(document, ['app', downgraded.name]);

Inside my index.html

<app id="app"></app>

This works fine.

Inside my main angular.js component I add the tag of my downgraded main Angular component:

<div class="app__view" id="appContent">
  <ui-view></ui-view>
  <app-root></app-root>
</div>

This is how my main module is configured

const COMPONENTS = [
  TestComponent,
  RootComponent,
];

@NgModule({
  declarations: COMPONENTS,
  imports: [
    BrowserModule,
    NxModule.forRoot(),
    RouterModule.forRoot(routes, {
      initialNavigation: 'enabled',
      useHash: true
    })
  ],
  providers: [
    { provide: APP_BASE_HREF, useValue: '/' }
  ],
  entryComponents: COMPONENTS,
  exports: COMPONENTS
})
export class AppModule {
  ngDoBootstrap(): void { }
}

Everything works fine so far. I can see my angular component inside my angular.js application.

The problem comes when I add the to my main root component I can see the router-outlet rendering but nothin next to it, eventhough the route matches.

export const routes: Route[] = [
  { path: 'dashboard', component: TestComponent }
];

When I point my browser to /#/dashboard this is the router tracing that I see:

router tracing

And the test component just doesn't render.

enter image description here

I need some help, can't think of anything else to try.

Patricio Vargas
  • 5,236
  • 11
  • 49
  • 100
curial
  • 514
  • 4
  • 17
  • 1
    I really can't see the Angular router and ui-router playing well together. I might be wrong but how do you see route changes being handled by the correct router? – Adrian Brand Sep 11 '18 at 05:21
  • 1
    @AdrianBrand You need some logic in both routers that makes them hide themselves when they are not relevant. – curial Sep 13 '18 at 13:30

2 Answers2

6

First of all: if you want to go hybrid and start moving parts of ng1 to ngx, you need to bootstrap your ng1 app from ngx as you did, but not by downgrading:

platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
  (<any>platformRef.instance).upgrade.bootstrap(document.body, ['nameOfNg1App']);
});

Than you should provide the entry point for ui-router within your app.component.html:

<div ui-view></div>

You also need to provide an url handling strategy to tell angular, which routes to handle. I had an AppRoutingModule, which was imported by the AppModule. And this one provided the handler:

@NgModule({
  imports  : [
    RouterModule.forRoot(routes, {useHash: true})
  ],
  exports  : [
    RouterModule
  ],
  // provide custom UrlHandlingStrategy to separate AngularJs from Angular routes
  providers: [
    {
      provide : UrlHandlingStrategy,
      useClass: Ng1Ng2UrlHandlingStrategy
    }
  ]
})
export class AppRoutingModule {
}

And the Handling strategy, I used path prefixes to separate ng1 from ngx routes, but you can choose a simpler separation if needed:

import { UrlHandlingStrategy } from '@angular/router';

export class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy {
    private ng1Urls: string[];

    constructor() {
        this.ng1Urls = [
            'prefix1',
        ];
    }

    shouldProcessUrl(url) {
        url = url.toString();
        return this.ng1Urls.findIndex(ng1Url => new RegExp(`^\/${ng1Url}([|\?|\/](.*)){0,1}$`).test(url)) === -1;
    }

    extract(url) {
        return url;
    }

    merge(url, whole) {
        return url;
    }
}

Oh, and for some reasons I had to stick to # URLs, while running hybrid.

With this setup you start an ngx app, that has a container that runs the ui-router. Within your ng1 app you can then use downgraded ngx components and services. To have ngx-routes and ng1 routes in parallel, your (ngx) app.component.html consists of

<div ui-view></div>
<router-outlet></router-outlet>

You find more details of this strategy here: https://blog.nrwl.io/upgrading-angular-applications-upgrade-shell-4d4f4a7e7f7b

André
  • 254
  • 1
  • 2
  • 1
    I tried the approach and it doesn't work for us, I need the ng1 component to be the shell (it has some navigation bars). I need the main ng2 component to be inside the main ng1 component. Here's a diagram of what I'm trying to achieve: https://i.imgur.com/tRyFged.png Red is ng1 and blue is ng2. I managed to get the components to render fine, my problem is that routing inside the ng2 main component doesn't work, the html is rendered but nothing is appended in it's router-outlet :( – curial Sep 13 '18 at 13:28
  • I awarded the bounty to this answer because of the effort. See my solution below – curial Sep 17 '18 at 07:18
2

The solution involved:

  • Getting rid of UrlHandlingStrategy completely
  • Disabling initialNavigation
  • Creating empty routes in both route configurations
  • Injecting the ng2 router in the ng1 application and adding the following logic

    angular.module('app').config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
      $stateProvider.state('ng2', { });
    
      $urlRouterProvider.otherwise(($injector, $location) => {
        const $state = $injector.get('$state');
        const ng2Routes = ['dashboard'];
        const ng2Router = $injector.get('ng2Router');
        const url = $location.url();
        if (ng2Routes.some(feature => url.startsWith('/' + feature))) {
          $state.go('ng2');
          ng2Router.navigate([url]);
        } else {
          $state.go('messages');
        }
      });
    }]);
    
curial
  • 514
  • 4
  • 17
  • Where i inject 'ng2Router'? – Dhanam Nov 23 '19 at 14:13
  • you need to provide it first. platformBrowserDynamic().bootstrapModule(CoreModule).then(ref => { angular.module('app') .factory('ng2Router', downgradeInjectable(Router)) (ref.instance).upgrade.bootstrap(document.body, ['app']); }); – curial Nov 26 '19 at 09:36
  • Could you please share what your angular2 routes looked like? – g0rb Feb 20 '20 at 01:54