23

I'd like to attach a component to a route asynchronously, given a condition.

The following example, which works (but is asynchronous), loads one component or another depending on the user role:

import { UserDashboardComponent }  from './user-dashboard.component'
import { AdminDashboardComponent } from './admin-dashboard.component'

const role = 'admin' // Just for the example
const comp = role === 'admin' ? AdminDashboardComponent : UserDashboardComponent

const routes: Routes = [
  { path: '', component: comp },
]

But, let's say we want to retrieve the role from an API, thus asynchronous. What's the way to accomplish that?

Alex JM
  • 1,084
  • 2
  • 11
  • 32

6 Answers6

9

You could create a generic.module.ts which will have both components in declarations array:

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UserDashboardComponent }  from './user-dashboard.component'
import { AdminDashboardComponent } from './admin-dashboard.component    

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ UserDashboardComponent,AdminDashboardComponent ]
})
export class GenericModule { }

this way you will have a module that contains the modules which you want to load.

Now next step will be to load them asynchronously using compiler: inside your component do following:

import {GenericModule} from './generic.module';
import { Component, Input,ViewContainerRef, Compiler, NgModule,ModuleWithComponentFactories,OnInit,ViewChild} from '@angular/core';
@Component({
  selector: 'generic',
  template: '<div #target></div>'
})
export class App implements AfterViewInit {
  @ViewChild('target', {read: ViewContainerRef}) target: ViewContainerRef;

  constructor(private compiler: Compiler) {}

  ngAfterViewInit() {
    this.createComponent('<u>Example template...</u>');
  }

  private createComponent(template: string,role:string) {
    @Component({template: template});
    const mod = this.compiler.compileModuleAndAllComponentsSync(GenericModule);
    const factory = mod.componentFactories.find((comp) =>
    //you can add your comparison condition here to load the component
    //for eg. comp.selector===role where role='admin'
    );
    const component = this.target.createComponent(factory);
  }
}

Hope this helps.

Bhushan Gadekar
  • 13,485
  • 21
  • 82
  • 131
  • 1
    This makes lots of sense. Although, is quite surprising to me that for this case one has to do that many things, specially on the low level framework scope. For now I cannot make this the accepted answer, since I wanna open an issue to the angular team for this. But sure the answer rewards the bounty. – Alex JM Oct 05 '16 at 12:14
  • @AlexJ Glad to hear this could help you. – Bhushan Gadekar Oct 05 '16 at 13:09
  • 1
    I dont think this is a solution worth attempting as you would need to ship compiler to the browser for this to work & even then hacky. – Ashok Koyi Jun 20 '17 at 12:00
8

Angular 2 supports lazy loading at module level. Feature modules are loaded asynchronously, not component. You can make that component feature module. https://angular.io/docs/ts/latest/guide/ngmodule.html

Manish
  • 2,092
  • 16
  • 18
  • 3
    Sorry, I think I didn't express myself correctly. I emphasized "asynchronous" , but my problem is more regarding the "condition". Lazy loading is not "directly" related to my problem. My problem is how to load one component or another (or one module or another) depending on a condition that may be asynchronous (let's say, the condition is the user role that comes from an API) when the app loads. I've updated my question making that more clear. Any way to solve that? – Alex JM Sep 25 '16 at 16:53
4

You could just define a higher-level component f.e. DashboardComponent which is included at route /dashboards

The parent component is then supposed to load the children in its template based on the asynchronous condition.

This way you will also need to use the *ngIf but at least its not cluttering the child components templates.

tom
  • 61
  • 3
  • This sounds interesting and makes sense. I'll try and get back here! For now I leave the thread open in order to welcome other answers – Alex JM Oct 01 '16 at 07:53
4

Since you will be using one route for either component, one solution is create another component for that route then its template could refer to both components but with an ngIf like below:

<user-dashboard *ngIf="role==='user'"></user-dashboard>
<admin-dashboard *ngIf="role==='admin'"></admin-dashboard>
jovani
  • 669
  • 8
  • 16
1

I advice you make use of navigating programatically using the router's navigate method . So you list all the posible routes in the router file .Then in your component you call router.navigate() based on the specific case .

luxury
  • 53
  • 1
  • 6
  • 1
    In that case url won't be same. – Manish Sep 25 '16 at 18:31
  • Exactly. What I want (at first at least) is to have the same url, let's say /dashboard, and depending if it is a User or an Admin to show UserDashboard or AdminDashboard. Maybe is not the best approach, but I want to avoid different url for this cases or even worse fill the components with all kind of {{*ngIf}} conditions.... – Alex JM Sep 25 '16 at 21:11
0

Very good solution could be "Use auth-guard". You can try implementing interfaces CanActivate/CanLoad and put your condition in that class.

Based on condition, routes gets activated.

Example:

import { Injectable }       from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot
}                           from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';

@Injectable()
export class AuthorizationGuard implements CanActivate {
  constructor() {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 
       Observable<boolean> {
    let url: string = state.url;
    return this.canbeLoaded(url);
  }

  canbeLoaded(url: string): Observable<boolean> {
    return Observable.of(true);  //put your condition here
  }
}
srikanth_k
  • 2,807
  • 3
  • 16
  • 18
  • If used with some sense, the method described in this answer can work. I don't understand the downvote. – retr0 Dec 10 '18 at 06:01