Hammad Hammad - 1 month ago 14
AngularJS Question

AngularJs Directive with dynamic Controller and template

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>

Answer

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?

Comments