angularjsangularjs-ng-transcludeangularjs-components

How to switch between components that uses ng-transclude?


Switching Between Components

We have two components, <hello></hello> and <goodbye></goodbye>. Both components have transclusion, allowing them to be used in manners such as <hello>World</hello> or <goodbye>World</goodbye>.

angular
  .module('myApp', [])
  .component('hello', {
    template: '<h1>Hello, <ng-transclude></ng-transclude>!</h1>',
    transclude: true
  })
  .component('goodbye', {
    template: '<h1>Goodbye, <ng-transclude></ng-transclude>!</h1>',
    transclude: true
  });

Now, we want the ability to switch between using the <hello></hello> component or the <goodbye></goodbye>. One way that this can be done is using ng-if. (Fiddle)

script.js

...
.controller('MyController', ['$scope', function($scope) {
  $scope.component = 'hello';
}]);

index.html

<div ng-controller="MyController">
    <hello ng-if="component === 'hello'">World</hello>
    <goodbye ng-if="component === 'goodbye'">World</goodbye>
</div>

Problem With Repeated Code

However, what if our transclusion included significantly more lines? Instead of simply <hello>World</hello>, we may have <hello><!-- Many lines which we'd rather not repeat twice --></hello>. If we use the same method to do this, we would end up with a lot of repeated code. So it would be nice if we can simply "switch out" components. (Fiddle)

<div ng-controller="MyController">
    <hello ng-if="component === 'hello'">
    <goodbye ng-if="component === 'goodbye'">
    Lorem Ipsum.........
    </goodbye>
    </hello>
</div>

Unfortunately, this doesn't work as intended. Setting $scope.component = 'hello' would yield Hello, !, and setting $scope.component = 'goodbye' would yield a blank page. My interpretation of this behavior is that angularjs is parsing this as <goodbye></goodbye> nested within <hello></hello>, rather than switching between using <hello></hello> or <goodbye></goodbye>. The desired behavior is more along the lines of an if-else if statement.

I have also tried using ng-switch on. (Fiddle)

<div ng-controller="MyController">
  <div ng-switch on="component">
    <hello ng-switch-when="hello">
    <goodbye ng-switch-when="goodbye">
    Lorem Ipsum.........
    </goodbye>
    </hello>
  </div>
</div>

However, this yields a Multiple Directive Resource Contention error.

Error: $compile:multidir

Multiple Directive Resource Contention

Multiple directives [ngSwitchWhen, hello] asking for transclusion on: <hello ng-switch-when="hello">

Similar Issue

From the question Using ng-transclude inside ng-switch, the Github Issue claims to have fixed a similar bug. However, the fix only applies when the ng-transclude is nested within a <div></div> block as follows:

<div ng-controller="MyController">
  <div ng-switch on="component">
    <div ng-switch-when="hello">
      <hello>Lorem Ipsum.........</hello>
    </div>
    <div ng-switch-when="goodbye">
      <goodbye>Lorem Ipsum.........</goodbye>
    </div>
  </div>
</div>

Unfortunately, this will not solve the issue of repeated code that I described earlier.

So my question is

Is there a way to switch out components while keeping the transcluded code the same, but without having to rewrite the transcluded code multiple times?

If not, what alternate methods can I use to achieve my goal while keeping the amount of repeated code to a minimal?


Solution

  • One work around to this problem is to make the <!-- Many lines which we'd rather not repeat twice --> into another component. (Fiddle)

    .component('common', {
      template: 'Lorem Ipsum.........'
    })
    

    Now, the only repeated code would be the tag for the component.

    <div ng-controller="MyController">
      <hello ng-if="component === 'hello'"><common></common></hello>
      <goodbye ng-if="component === 'goodbye'"><common></common></goodbye>
    </div>
    

    If an additional component is undesirable, it is also possible to do the same with ng-include.

    <div ng-controller="MyController">
      <hello ng-if="component === 'hello'"><ng-include src="'common.html'"></ng-include></hello>
      <goodbye ng-if="component === 'goodbye'"><ng-include src="'common.html'"></ng-include></goodbye>
    </div>