pwray
pwray

Reputation: 1125

Angular dynamic polymorphic directives

I'm a beginner to Angular but am poking into some slightly more advanced corners to ensure it has the features I need.

Specifically I need to:

I think I have solved the requirement described below and implemented at http://jsfiddle.net/cUTt4/5/

Questions:

  1. Is this correct, best practice, and reasonably fast?
  2. Any improvements I should add?
  3. It would be better if the widget directives had no explicit reference { item : '=' } to obtain their isolated scope, but their sub-scopes should be built by the renderform directive. How do I do that?

My solution: HTML (Note the Angular templates are in script here due to limitations of jsfiddle)

<div ng-app="myApp">

    <script type="text/ng-template" id="widget-type-a">
        <div>
            <label>{{ item.label}} </label> 
            <input type="text" ng-model="item.val" >
        </div>
    </script>

    <script type="text/ng-template" id="widget-type-b">
        <div>
            <label>{{ item.label}}</label> 
            <input type="text" ng-model="item.val" >
        </div>
    </script>

    <div ng-controller="FormCtrl">
        <renderform></renderform>
    </div>
</div>

main.js :

var app = angular.module('myApp', []);

function FormCtrl($scope) {
    items = [
        {
            type: 'widget-type-a',
            label : 'Widget A instance 1',
            val: 1
        },
        {
            type: 'widget-type-b',
            label : 'Widget B instance 1',      
            val : 2
        },
        {
            type: 'widget-type-a',
            label : 'Widget A instance 2',
            val : 3
        }
    ];
    $scope.items = items

}

app.directive('renderform', function($compile) {
    function linkFn(scope, element) {
        var item, 
            itemIdx,
            templStr = '',
            newParent,
            data,
            newEl;

        newParent = angular.element('<div></div>')
        for(itemIdx in scope.items) {
            item = items[itemIdx];
            templStr += '<div ' + item.type + ' item="items[' + itemIdx + ']"></div>';
        }
        newEl = angular.element(templStr);
        $compile(newEl)(scope);
        element.replaceWith(newEl);
    }

    return {
        restrict: 'E',
        link:linkFn

    };

});

app.directive('widgetTypeA', function() {
    return {
        restrict: 'A',
        templateUrl: 'widget-type-a',
        scope: { item: '=' } 
    };

});

app.directive('widgetTypeB', function() {
    return {
        restrict: 'A',
        templateUrl: 'widget-type-b',
        scope: { item: '='}
    };

});

Upvotes: 3

Views: 2352

Answers (2)

Euan
Euan

Reputation: 419

I have been thinking about this problem for some time now and, although the ng-switch option work for simple cases, it introduces quite a maintenance overhead.

I've come up with a solution which allows for a single point of maintenance. Consider the following:

var app = angular.module('poly', []);


app.controller('AppController', function ($scope) {
    $scope.items = [
        {directive: 'odd-numbers'},
        {directive: 'even-numbers'},
        {directive: 'odd-numbers'}
    ];
});


app.directive('component', function ($compile) {
    return {
        restrict: 'E',
        controller: function () {

        },
        controllerAs: 'component_ctrl',
        link: function (scope, element, attrs) {
            var child_directive = scope.$eval(attrs.directive);
            var child_element = $compile('<' + child_directive + ' data="data"></' + child_directive + '>')(scope);
            element.append(child_element);
        }
    }
});


app.directive('oddNumbers', function ($interval) {
    return {
        restrict: 'E',
        link: function (scope) {
            scope.number = 0;
            $interval(function () {
                scope.number += 2;
            }, 1000);
        },
        template: '<h1>{{ number }}</h1>'
    }
});


app.directive('evenNumbers', function ($interval) {
    return {
        restrict: 'E',
        link: function (scope) {
            scope.number = 1;
            $interval(function () {
                scope.number += 2;
            }, 1000);
        },
        template: '<h1>{{ number }}</h1>'
    };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<section ng-app="poly" ng-controller="AppController">
  <div ng-repeat="item in items">
    <component directive="item.directive" data="item.data"></component>
  </div>
</section>

This allows for the components to be specified in the controller in an ad hoc way and the repeater not having to delegate responsibility via a switch.

NB I didn't implement how the data is passed between components

Upvotes: 2

DEY
DEY

Reputation: 1810

sorry fast answer, not tested :

<div data-ng-repeat="item in items">
  <div data-ng-switch data-on="item.type">
    <div data-ng-switch-when="widget-type-a" data-widget-type-a="item"></div>
    <div data-ng-switch-when="widget-type-b" data-widget-type-b="item"></div>
  </div>
</div>

If this is what you're looking for, please improve this answer.

Upvotes: 3

Related Questions