5

We have a requirement where depending on a static/global setting which will be set depending on the customer they want a different component to load for a route. The reason being they want slightly different functionality for a part of the application so we are going to write a component for each that caters for their scenarios. Is there a way to choose the component for a route dynamically/at runtime and keep the same route/url. Below is a simplified example of what we would like to achieve:

Component1:

@Component({
  selector: 'customeronecomponent',
  templateUrl: './customeronecomponent.component.html'
})

export class CustomerOneComponent implements OnInit {
}

Component2:

@Component({
  selector: 'customertwocomponent',
  templateUrl: './customertwocomponent.component.html'
})

export class CustomerTwoComponent implements OnInit {
}

Route:

{ path: 'home', component: CustomerComponentProvider },

In this instance the CustomerComponentProvider will internally check the setting and either return CustomerOneComponent or CustomerTwoComponent.

In angular2/4 is that the best way to do this or is it better to have a single component and on that component load the right component, the downside I see there is we would have three components instead of two for each route we need this.

user351711
  • 3,171
  • 5
  • 39
  • 74
  • Cant you have two route, home-1 and home-2 both registered and based upon customer condition you navigate to either one of them? may be this is totally not applicable, you can provide more detail in your question. – Madhu Ranjan Aug 11 '17 at 19:09
  • I have tried to add more information, I cannot find a way to do this in Angular, there may not be a way in which case hopefully someone can provide the best option that keeps the URL the same. We are hoping to use this for additional components and don't want to add route-1, route-2, etc. for many components but rather have providers that get the right component as there may be more than just 2 options. – user351711 Aug 12 '17 at 06:44
  • How exactly should that component resolve be performed? Is the correct component read from config and then whole application restarted (browser refresh) or you need on-the-fly change without app restart? – dee zg Aug 13 '17 at 08:23
  • No on the fly change needed, there will be a const value set and on deployment will be changed and not changed again for that deployment. – user351711 Aug 14 '17 at 15:35
  • You can use a global variable in routing configuration. The routing generator would be a function that build the routing array based on that/those global variables. – Leonardo Neninger Aug 16 '17 at 21:48

4 Answers4

8

You may already be doing this internally in your CustomerComponentProvider, but in case not - there is Dynamic Component Loader.

The example given in the docs is a bit busy, so here's a dumbed-down version: Plunker

Essentially, a wrapper is used to switch between components depending on config string passed in, or retrieved from a service.

import { Component, Input, OnChanges, 
  ComponentFactoryResolver, ViewContainerRef } from '@angular/core';
import { Component1 } from './component1';
import { Component2 } from './component2';

@Component({
  selector: 'component-wrapper',
  template: `<div></div>`
})
export class ComponentWrapper implements OnChanges {

  @Input() config;

  private componentMap = {
    component1 : Component1,
    component2 : Component2,
  }

  constructor(
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver
  ) {}

  ngOnChanges() { 
    this.setComponent();
  }

  setComponent() {
    const componentFactory = this.componentFactoryResolver
      .resolveComponentFactory(this.componentMap[this.config]);
    this.viewContainerRef.clear();
    this.viewContainerRef.createComponent(componentFactory);
  }

}
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • Thank you for the comprehensive example, can you tell me if this is a better (performance or best practices) that the solution by Younis below which shows or hides the right component? Will this not create an instance of each component unless it's used as opposed to switching in the template which does? – user351711 Aug 14 '17 at 12:02
  • I have just tested and the constructor is not called for the second component so I will assume while this is more complicated than switching with an *ngIf in the template it means not creating more instances than we want to show. – user351711 Aug 14 '17 at 12:05
  • Actually I have just tried Younis's solution and again only one constructor is called, can you clarify why this is better than the simpler solution below? – user351711 Aug 14 '17 at 12:10
  • I personally would use Younis' solution as it is simpler, (always better for cost of ownership), also Adrian's route configuration answer is simpler. I mention ComponentFactoryResolver / ViewContainerRef as it's put out there by Angular as the way to do dynamic components. This example rounds out the question. I also wanted to pare down the example given in Angular docs. – Richard Matsen Aug 15 '17 at 00:49
  • The only advantage I see with dynamic component is when coupling is a concern. The component map or the component itself can be injected via @Input. See Günter Zöchbauer's comment here (https://stackoverflow.com/questions/37649358/angular2-choose-template-dynamically-according-to-service-response) Aside from that, dynamic component is hot-switchable. If I'm not mistaken, ngIf requires a reload to change components. (Not one of your requirements). – Richard Matsen Aug 15 '17 at 00:49
  • Talking about hot switching, there's a step missing in my code. The old component should probably be destroyed upon switch (at least, there are examples out there that show it). I've updated the Plunker to do so. – Richard Matsen Aug 15 '17 at 00:50
  • I actually went with a simpler solution but I learnt more from this answer so thank you for your help and bounty awarded – user351711 Aug 19 '17 at 09:46
  • I liked the idea of this approach but it didn't work as I expected as the component loads into the current component - I ended up manipulating the router as you can actually update the route.config[some-index].component to set it per user login. – Rob Dec 01 '19 at 22:37
2

You can update the router configuration whenever you want, so depending on the setting you have you can add or update a route config to resolve to the component that you need.

Have a look at the config property or maybe more suited for this, the resetConfig method on the Router. You could inject the Router in the component where you want to handle this and update the routes according to the global setting that you have.

Adrian Fâciu
  • 12,414
  • 3
  • 53
  • 68
  • 1
    Of all the approaches I think this is the cleanest - Richard and Younis's approaches result in you having a third component that manages two other components. In my case I had two standalone admin "end-user" UIs. They both display independently to admin but when the given end-user is logged in I wanted to display their respective UI on a /my-profile route - I load a config from the server when they log in so I was able to tap into this and manipulate the route - I just find it by path and then set it accordingly. – Rob Dec 01 '19 at 22:42
2

in the template for CustomerComponentProvider , you can display either of the components by checking the setting

say your setting returns the type on a customer (1/2) based on which you can set showCustomerOne/showCustomerTwo to true

<app-customer-one-component *ngIf="showCustomerOne"> </app-customer-one-component>
<app-customer-two-component *ngIf="showCustomerTwo"> </app-customer-two-component>

check for the setting in constructor of CustomComponentProvider and set either showCustomerOne to True or showCustomerTwo to True.

  showCustomerOne;
  showCustomerTwo;

  constructor() {
     if(customer.type == '1'){
         this.showCustomerOne = true;
      }else if (customer.type == '2'){
         this.showCustomerTwo = true;
      }
  };
Younis Ar M
  • 913
  • 6
  • 15
0

You could try something like reflect-metadata

You would define metadata key for type and then get the type of the component based on whatever is in your config and feed that into routeConfig or by resetting the router through router.resetConfig as @Adrian Faciu already suggested:

https://angular.io/api/router/Router#resetConfig

dee zg
  • 13,793
  • 10
  • 42
  • 82