5

I want to create a directive that has dynamic view with dynamic controller. the controller and the template view is coming from the server.

The Directive

var DirectivesModule = angular.module('BPM.Directives', []);
(function () {
    'use strict';

    angular
        .module('BPM.Directives')
        .directive('bpmCompletedTask', bpmCompletedTask);

    bpmCompletedTask.$inject = ['$window'];

    function bpmCompletedTask ($window) {
        // Usage:
        //     <bpmCompletedTask></bpmCompletedTask>
        // Creates:
        // 
        var directive = {
            link: link,
            restrict: 'E',
            scope: {
                type: '=',
                taskdata: '=',
                controllername:'@'
            },
            template: '<div ng-include="getContentUrl()"></div>',
            controller: '@',
            name: 'controllername'
            };
        return directive;

        function link(scope, element, attrs) {
            scope.getContentUrl = function () {
                return '/app/views/TasksViews/' + scope.type + '.html';
            }
            scope.getControllerName = function ()
            {
                console.warn("Controller Name is " + scope.type);
                return scope.type;
            }
        }
    }

})();

Here how I'm trying to use the directive

<div ng-controller="WorkflowHistoryController as vm">
    <h2>Workflow History</h2>
    <h3>{{Id}}</h3>
    <div ng-repeat="workflowStep in CompletedWorkflowSteps">
        <bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome" type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
        </bpm-completed-task>
    </div>    
</div>

The problem now is when the directive gets the controller name it get it as literal string not as a parameter.

Is it doable ? if it's not doable, What is the best solution to create dynamic views with its controllers and display them dynamically inside ng-repeat?

Thanks,

Update 20 Jan I just updated my code in case if some one interested in it. All the Credit goes to @Meligy.

The First Directive:

(function () {
    'use strict';

    angular
        .module('BPM.Directives')
        .directive('bpmCompletedTask', bpmCompletedTask);

    bpmCompletedTask.$inject = ['$compile', '$parse'];

    function bpmCompletedTask ($compile, $parse) {
        var directive = {
            link: function (scope, elem, attrs) {
                console.warn('in the first directive - before if');
                if (!elem.attr('bpm-completed-task-inner'))
                {
                    console.warn('in the first directive');
                    var name = $parse(elem.attr('controllername'))(scope);
                    console.warn('Controller Name : ' + name);
                    elem = elem.removeAttr('bpm-completed-task');
                    elem.attr('controllernameinner', name);
                    elem.attr('bpm-completed-task-inner', '');
                    $compile(elem)(scope);
                }
            },
            restrict: 'A',
            };
        return directive;        
    }

})();

The Second Directive

angular
.module('BPM.Directives')
.directive('bpmCompletedTaskInner',['$compile', '$parse',
function ($window, $compile, $parse) {
    console.warn('in the second directive');
    return {
        link: function (scope, elem, attrs) {
            console.warn('in the second directive');
            scope.getContentUrl = function () {
                return '/app/views/TasksViews/' + scope.type + '.html';
            }
        },
        restrict: 'A',
        scope: {
            type: '=',
            taskdata: '=',
            controllernameinner: '@'
        },
        template: '<div ng-include="getContentUrl()"></div>',
        controller: '@',
        name: 'controllernameinner'
    };

}]);

The Html

 <div ng-repeat="workflowStep in CompletedWorkflowSteps">
        <div bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome"
                            type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
        </div>
    </div>

1 Answer 1

3

Update:

I got it working, but it's really ugly. Check:

http://jsfiddle.net/p6Hb4/13/

Your example has a lot of moving pieces, so this one is simple, but does what you want.

Basically you need a wrapper directive that takes the JS object and converts into a string property, then you can use هى your directive for everything else (template, scope, etc).

.

Update 2:

Code Inline:

var app = angular.module('myApp', []).
directive('communicatorInner', ["$parse", "$compile",
  function($parse, $compile) {
    return {
      restrict: 'A',
      template: "<input type='text' ng-model='message'/><input type='button' value='Send Message' ng-click='sendMsg()'><br/>",
      scope: {
        message: '='
      },
      controller: '@'
    };
  }
]).
directive('communicator', ['$compile', '$parse',
  function($compile, $parse) {
    return {
      restrict: 'E',
      link: function(scope, elem) {
        if (!elem.attr('communicator-inner')) {
          var name = $parse(elem.attr('controller-name'))(scope);
          elem = elem.removeAttr('controller-name')
          elem.attr('communicator-inner', name);
          $compile(elem)(scope);
        }
      }
    };
  }
]).
controller("PhoneCtrl", function($scope) {
  $scope.sendMsg = function() {
    alert($scope.message + " : sending message via Phone Ctrl");
  }
}).
controller("LandlineCtrl", function($scope) {
  $scope.sendMsg = function() {
    alert($scope.message + " : sending message via Land Line Ctrl ");
  }
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>

<div ng-app="myApp">

<div ng-init="test = {p: 'PhoneCtrl', l: 'LandlineCtrl' }">
  <communicator controller-name="test.p" message="'test1'"></communicator>
  <communicator controller-name="test.l"></communicator>

</div>
  
  
</div>

.

Original (irrelevant now but can help other related issues)

Yes, it should work.

A test with Angular 1.3:

http://jsfiddle.net/p6Hb4/9/

Things to check:

  • Is the controller defined and added to the module? It will not work

    If the controller is just a global function it won't work. It has to be added via the <myModule>.controller("<controllerName>", <functiion>) API

  • Does ng-controller work? Just adding it to the template

    Similarly, does using ng-controller directly outside of the directive work?

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you for your answer Meligy. My problem starts when the controller name is an object value not a plain text. In the example you shared think about the controllers names are coming from the server and I'm passing them to the directive as parameters.
I think I might have found the problem. Check the update in my answer
No, Still. See, the directive receive the value correctly and start searching for a controller called "workflowStep.WorkflowTaskType.DataMessageViewViewName" however this is not my controller and him to search for a controller after evaluating the value of this object. I tried the following <bpm-completed-task controllername="{{workflowStep.WorkflowTaskType.DataMessageViewViewName}}" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome" type="workflowStep.WorkflowTaskType.DataMessageViewViewName"> </bpm-completed-task> and also it doesn't work
Yeah, I was wrong again. You need something like: stackoverflow.com/questions/24762229/dynamic-ng-controller-name - I have some work now but will return with an example combining both of them
Thank You. I will be waiting for your contribution.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.