16

I have a number of angular 1.5 components that all take the same attributes and data structure. I think they could be re-factored into a single component, but I need a way to dynamically choose a template based upon the interpolated value of the type attribute.

var myComponentDef = {
    bindings: {
        type: '<'
    },
    templateUrl: // This should be dynamic based on interpolated type value
};

angular.module('myModule').component('myComponent', myComponentDef);

I can't use the templateUrl function($element, $attrs) {} because the values in the $attrs are uninterpolated so I wouldn't get the type specified in the passed in data.

I could just have one big template with a series of ng-if or ng-switch directives, but I would like to keep the templates separate.

Alternatively, I could just keep the components separate and use ng-switch etc in the parent component, but I don't like this as it seems like a lot of repetition.

I'm looking for a solution where I can use the interpolated type passed into the bindings to match a template url for each type which will then be used to build the component.

Is this possible?

Thanks

isherwood
  • 58,414
  • 16
  • 114
  • 157
Joe
  • 4,852
  • 10
  • 63
  • 82
  • I don't think this is possible since it requires the `$compile` service which is not available to components. You can use directive though. – Muli Yulzary Jul 28 '16 at 22:07
  • 2
    see this simple and elegant solution: https://stackoverflow.com/a/41743424/1274852 – hkong Jun 14 '17 at 16:42

3 Answers3

18

This is not something that components were specially made for. The task narrows down to using a directive with dynamic templates. The existing one is ng-include.

To use it within a component, it should be:

var myComponentDef = {
  bindings: {
    type: '<'
  },
  template: '<div ng-include="$ctrl.templateUrl">',
  controller: function () {
    this.$onChanges = (changes) => {
      if (changes.type && this.type) {
        this.templateUrl = this.type + '.html';
      }
    }
  }
}
isherwood
  • 58,414
  • 16
  • 114
  • 157
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks for your help. I really like this. I injected an angular constant into the controller which contains all of the config mapping types to particular urls, so the line can be `this.templateUrl = typeMapperConfig[this.type];` – Joe Jul 29 '16 at 05:33
12

You can inject any service and set dynamic url

angular.module('myApp').component("dynamicTempate", {
        controller: yourController,
        templateUrl: ['$routeParams', function (routeParams) {
           
            return 'app/' + routeParams["yourParam"] + ".html";
        
        }],
        bindings: {
        },
        require: {
        }
    });
Ram ch
  • 1,633
  • 1
  • 13
  • 5
0

You have to have the switching logic somewhere in any case so why not simply have it in a parent component template?

Having clean and understandable AngularJS template in that case is IMO more valuable than a bit of repetition:

<ng-container ng-switch="$ctrl.myComponentDef.type">
  <component-type1 ng-switch-when="type1" param="$ctrl.myComponentDef"></component-type1>
  <component-type2 ng-switch-when="type2" param="$ctrl.myComponentDef"></component-type2>
</ng-container>

Even if you change the myComponentDef.type on the fly, the components in the switch will correctly call their respective $onDestroy and $onInit methods and load data as expected - no magic, no voodoo.

Vedran
  • 10,369
  • 5
  • 50
  • 57