I have the following code in angular.
<div vh-accordion-group id="{{$index}}" panel-class="panel-info">
<div vh-accordion-header> </div>
<div vh-accordion-body> </div>
</div>
The HTML for the accordion-group is :
<div class="panel panel-info" ng-transclude>
</div>
Also, the js for the same is :
home.directive("vhAccordionGroup", ['version', function (version) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: {
panelClass: '#'
},
templateUrl: "JS/HomeModule/Directives/vhAccordion/vhAccordionGroup.html?v=" + version,
controller: ['$scope', '$element', function ($scope, $element) {
$scope.accordionId = $element.attr("id") + "vhAcc";
this.getAccordionId = function () {
return $scope.accordionId;
};
}],
link: function (scope, element, attrs, vhAccordionGroupController) {
element.addClass(scope.panelClass);
scope.accordionId = element.attr("id") + "vhAcc";
}
};
}]);
These accordions are called in a loop using ng-repeat.
The problem is the {{$index}} which I am passing in the Id is being taken as a string rather then an expression.
I need each of the accordions to have a unique Id so that they can open and close correctly.
Please let me know what I am missing, as I am a newbie to angular.
Simply add id to the isolated scope:
home.directive("vhAccordionGroup", ['version', function (version) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: {
panelClass: '#',
id: '#'
},
templateUrl: "JS/HomeModule/Directives/vhAccordion/vhAccordionGroup.html?v=" + version,
controller: ['$scope', '$element', function ($scope, $element) {
$scope.accordionId = $scope.id + "vhAcc";
this.getAccordionId = function () {
return $scope.accordionId;
};
}],
link: function (scope, element, attrs, vhAccordionGroupController) {
element.addClass(scope.panelClass);
scope.accordionId = scope.id + "vhAcc";
}
};
}]);
Then, use $scope.id (and scope.id) instead of $element.attr. $element.attr gives access to the actual DOM element and so it makes sense that the value would not be interpolated.
Related
I'm using compile in my directive controller to get the first directive element and compile it and then use it for other purpose I don't want to use the linking method of my directive, is there anyway to get rid this error ?
I've reproduced the issue in this JSFIDDLE:
var app = angular.module('app', []);
app.directive('panel', function ($compile) {
return {
restrict: "E",
replace: true,
transclude: true,
template: "<div><h1>handrouss</h1><div ng-transclude ></div></div>",
controller: function($scope, $element) {
var el = $compile($element.find('div')[0])($scope); // <--- this causing the issue
$scope.handrouss = el.html();
},
link: function (scope, elem, attrs) {
}
}
});
app.directive('panel1', function ($compile) {
return {
restrict: "E",
replace:true,
transclude: true,
template:"<div ng-transclude></div>",
link: function (scope, elem, attrs) {
elem.children().wrap("<div>");
}
}
});
HTML :
<div data-ng-app="app">
<panel1>
<panel>
<input type="text" ng-model="firstName" />{{firstName}}
</panel>
<input type="text" ng-model="lastname" />
</panel
Remove the ng-transclude attribute from the element before compiling in the controller.
app.directive('panel', function ($compile) {
return {
restrict: "E",
replace: true,
transclude: true,
template: "<div><h1>handrouss</h1><div ng-transclude ></div></div>",
controller: function($scope, $element) {
var div = $element.find('div');
//REMOVE ng-transclude attribute
div.removeAttr('ng-transclude');
var el = $compile(div[0])($scope);
$scope.handrouss = el.html();
},
link: function (scope, elem, attrs) {
}
}
});
As the transclusion has already been done in the compile phase of the directive the ng-transclude directive is no longer needed when compiling in the controller.
The DEMO on JSFiddle
I have a scenario where i need to apply different directives (attribute) to a DIV inside a Angular bootstrap Modal at runtime (button click).
I would know the name of the directive to apply. But i am not able to figure out how to change the template at runtime to add necessary directive name as an attribute to the DIV.
consider this plunker
Basically i want the div to have child directive as an attribute using synstax like this
<div {{child}}></div>
So when it works, it should generate <div child-directive></div>
How can this be done? is this even possible? What is the best way to change the template before opening the Modal so that it wires up correctly when loaded.
// Code goes here
var app = angular.module('main-module', ['ui.bootstrap']);
app.directive('parentDirective', function($uibModal, $compile) {
return {
restrict: 'E',
template: "<h2>I am Parent</h2><button ng-click='click()'>Click Me</button>",
scope: {
child:'#'
},
link: function($scope, elem, attrs) {
console.log('?',$scope.child, $scope);
var template = "<div><h3>This is modal</h3>"
+ "Ideally you should see the child directive below"
+ "<hr />"
+ "<div "+ $scope.child + "></div></div>";
$scope.click = function() {
$uibModal.open({
template: template,
scope: $scope,
size: 'lg',
});
}
}
};
})
.directive('childDirective', function() {
return {
restrict: 'A',
template: "<div><h4>I am Child</h4><a ng-click='click()'>Click Me!!</a></div>",
replace: true,
scope: {},
link: function($scope, elem, attrs) {
$scope.click = function() {
alert("I am in child scope");
}
}
};
}).directive('anotherChildDirective', function() {
return {
restrict: 'A',
template: "<div><h4>I am another Child</h4><a ng-click='click()'>Click Me!!</a></div>",
replace: true,
scope: {},
link: function($scope, elem, attrs) {
$scope.click = function() {
alert("I am in child scope");
}
}
};
});;
Suppose, I have controller:
angular.module('tf').controller('Ctrl', function($scope){
$scope.params = {
orderBy: null
};});
And a directive "common":
angular.module('tf').directive("common", function() {
return {
restrict: 'E',
replace: true,
template: '<div><outer order-by="orderBy"><inner order-by-field="name1"></inner><inner order-by-field="name2"></inner></outer></div>',
controller: function ($scope) {
},
scope: {
orderBy: '='
},
link: function (scope, element, attrs) {
}
}});
Controller is using directive within it's template:
<div ng-app="tf">
<div ng-controller="Ctrl">
<common order-by="params.orderBy"></common>
<div style="color:red">{{params.orderBy}}</div>
</div>
This directive is using directive "outer":
angular.module('tf').directive("outer", function() {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-transclude></div>',
controller: function ($scope) {
this.order = function (by) {
$scope.orderBy = by
};
},
scope: {
orderBy: '=',
}
}});
Which is parent for the directive "inner":
angular.module('tf').directive("inner", function() {
return {
require: '^outer',
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-click="onClicked()">{{orderByField}}</div>',
controller: function ($scope) {
$scope.onClicked = function () {
$scope.outer.order($scope.orderByField);
}
},
scope: {
orderByField: '#'
},
link: function (scope, element, attrs, outer) {
scope.outer = outer;
}
}});
The directive "outer" shares "order" method with directive "inner" by it's controller. The directive "inner" is accessing it by using "require" mechanism.
For some reason, this is not working as expected (Property of the controller isn't updated each time it's changed by directive). If I place "orderBy" into object (e.g. {"order": {"by": null }} ) and use object instead of string value, everything is working as expected ( controller scope is properly updated by the directive). I know about "always use dots" best practices principle, but I don't wanna use it here, because it would make my directive's API less intuitive.
Here is jsfiddle:
http://jsfiddle.net/A8Vgk/1254/
Thanks
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>
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?
I wrote two directives in angularjs, one is embedded in the other one.
below is the scripts for the directives:
module.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
this.scope = $scope;
return $scope.panes = [];
},
link: function(scope, element, attrs) {
return $log.log('test1', scope.panes);
}
};
}
]);
module.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
controller: function($scope) {
return this.x = 1;
},
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, fooCtrl) {
return $log.log('test2', fooCtrl);
}
};
}
]);
below is the piece of html:
<foo ng-controller="IndexController">
<bar></bar>
</foo>
below is the element generated, inspected from the chrome developer tools
<div id="test" ng-transclude="" ng-controller="IndexController" class="ng-scope">
<div ng-transclude="" class="ng-scope"></div>
</div>
below is chrome console output:
test2
Array[0]
length: 0
__proto__: Array[0]
angular.js:5930
test1
Array[0]
length: 0
__proto__: Array[0]
Question:
The child directive can not get the parent directive's controller, so the fourth parameter "fooCtrl" for link function of "bar" is an empty array. what's the thing that I do it wrong?
update and answer:
At last I found the reason that made the weird result. It's just a stupid mistake that I have made:
// in directive "foo"
controller: function($scope) {
this.scope = $scope;
// This line below is wrong. It is here
// because I transcompiled coffeescript to js.
// return $scope.panes = [];
// It should be like below:
$scope.panes = []
// I should have written .coffee like below
// controller: ($scope) ->
// #scope = $scope
// $scope.panes = []
// return # prevent coffeescript returning the above expressions.
// # I should rather have added the above line
}
After correcting the mistake, I tried and found there's nothing to prevent using controller or providing empty content in child directives.
AFAIK, you cannot have a controller in the child directive.
Demo: http://plnkr.co/edit/kv9udk4eB5B2y8SBLGQd?p=preview
app.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
$scope.panes = ['Item1','Item2','Item3']
return {
getPanes: function() { return $scope.panes; }
};
},
link: function(scope, element, attrs, ctrl) {
$log.log('test1', ctrl, ctrl.getPanes(), scope.panes);
}
};
}
]);
I removed the child controller.
app.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, ctrl) {
scope.x = 1;
$log.log('test2', ctrl, ctrl.getPanes(), scope.panes, scope.x);
}
};
}
]);