I understand that I can dynamically set a templateUrl base on an option DOM attribute template-url="foo.html" given the following code:
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return attrs.templateUrl || 'some/path/default.html'
}
}
});
However, I need to take this a step further and pass this string one level deeper, to a child directive.
Given this HTML:
Usage in Main project
<parent parent-template="bar.html" child-template="foo.html"></parent>
The child will not be exposed in most cases, so if child-template is set, it needs to implicitly replace templateUrl for all child <child></child> elements that are located in the parent foo.html.
The require: '^parent' attribute passes data from scope to scope, but I'm not seeing this available in templateUrl when it's declared.
foo.html
<h1>Title</h1>
<child ng-repeat="item in array"></child>
Directives
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'some/path/default.html'
},
scope: {
childTemplate: '=childTemplate'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return ??? // parent.attribute.childTemplate? || 'some/path/default.html'
},
require: '^parent',
scope: {
childTemplate: '=childTemplate'
}
}
});
Update
The old answer (see bellow) won't work because it's only possible to access the controller of the required directives inside the link functions, and the templateUrl function gets executed before the link functions.
Therefore the only way to solve this is to handle everything in the templateUrl function of the child directive. However this function only takes 2 arguments: tElement and tArgs.
So, we will have to find the element of the parent directive and access the attribute child-template. Like this:
angular.module('testApp', [])
.directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
},
transclude:true,
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'default.html'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
require:'^parent',
templateUrl: function(elem,attrs) {
//if jQuery is loaded the elem will be a jQuery element, so we can use the function "closest"
if(elem.closest)
return elem.closest("parent").attr("child-template") || 'default.html';
//if jQuery isn't loaded we will have to do it manually
var parentDirectiveElem=elem;
do{
parentDirectiveElem=parentDirectiveElem.parent();
}while(parentDirectiveElem.length>0 && parentDirectiveElem[0].tagName.toUpperCase()!="PARENT");
return parentDirectiveElem.attr("child-template") || 'default.html';
}
}
});
Example
Old Answer
Since you are isolating the scope, you could try this, it's a bit hacky but I guess that it should work:
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
controller: function($scope) {
this.childTemplate=$scope.childTemplate;
},
link: function(scope, element, attrs) {
},
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'some/path/default.html'
},
scope: {
childTemplate: '#'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
require: '^parent',
link: function(scope, element, attrs, parentController) {
if(parentController.childTemplate)
element.data("childTemplate", parentController.childTemplate);
},
templateUrl: function(elem,attrs) {
return elem.data("childTemplate") || 'some/path/default.html'
}
}
});
In my question, I was attempting to provide an override for the templateUrl of an off-the-shelf directive that didn't have one. My original question doesn't mention this, however, I wanted to add this as a reference to others who may have forgotten, as I did. Angular allows you to decorate directives and override their properties.
app.config(function($provide) {
$provide.decorator('child', function($delegate) {
var directive = $delegate[0];
directive.templateUrl = 'path/to/custom.html';
return $delegate;
});
});
Related
I am creating a custom directive in angularjs, but for some attributes i am receiving undefined value.
function processinfo(ProcessInfoService, $timeout) {
console.log("processInfo directive");
return {
restrict: 'E',
scope: {
start: '=',
end: '=',
uuid: '='
},
templateUrl: 'k2-modules/js/directives/templates/processInfoTemplate.html',
controller: function($scope) {
var self = this;
console.log($scope.uuid); // undefined
console.log($scope.end); // 164982555555
console.log($scope.start); // 0
self.processData = ProcessInfoService.getInfo($scope.start, $scope.end);
}
}
}
<processinfo start="0" end="164982555555" uuid="a57cf6f8"></processinfo>
For uuid I am getting undefined but for end and start values everything is working fine. I don't know why this is happening since syntax is same for all three. Any help will be appreciated
Your mixing directive syntax with component/controller syntax, in directives $scope is called scope (no dollar). Here's the correct syntax to build a angularjs directive:
angular.module('app').directive('myDirective', MyDirective);
MyDirective.$inject = ['$timeout'];
function MyDirective($timeout) {
return {
scope: {
'propBinding1': '<',
'propBinding2': '&'
},
replace: true,
restrict: 'EA',
templateUrl: 'path to html',
link: function link(scope, element, attrs) {
//... do stuff here ...
}
};
}
I have a controller:
function myController($scope) {
$scope.clicked = false;
}
and a directive:
function myDirective() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
elem.bind('click', function() {
// need to update controller $scope.clicked value
});
},
template: '<div>click me</div>';
replace: true;
}
}
and I´m using it like this:
<div ng-controller="myController">
<my-directive></my-directive>
</div>
How can I change the controller value of $scope.clicked ?
thanks!
As you don't use isolated scope in your directive, you can use scope.$parent.clicked to access the parent scope property.
link: function(scope, elem, attrs) {
elem.bind('click', function() {
scope.$parent.clicked = ...
});
},
I would not recommend using scope.$parent to update or access the parent scope values, you can two way bind the controller variable that needs to be updated into your directive, so your directive becomes:
function myDirective() {
return {
restrict: 'E',
scope: {
clicked: '='
},
link: function(scope, elem, attrs) {
elem.bind('click', function() {
// need to update controller $scope.clicked value
$scope.clicked = !$scope.clicked;
});
},
template: '<div>click me</div>';
replace: true;
}
}
now pass this clicked from parent:
<div ng-controller="myController as parentVm">
<my-directive clicked="parentVm.clicked"></my-directive>
</div>
function myController() {
var parentVm = this;
parentVm.clicked = false;
}
I would recommend reading up on using controllerAs syntax for your controller as that would really solidify the concept of using two way binding here.
I like to use $scope.$emit for such purposes. It allows to send data from directive to the controller.
You should create custom listener in your controller:
$scope.$on('cliked-from-directive', function(event, data){
console.log(data)
})
As you can see, now you have full access to your controller scope and you can do whatever you want. And in your directive just to use scope.$emit
link: function(scope, elem, attrs) {
elem.bind('click', function() {
scope.$emit('cliked-from-directive', {a:10})
});
Here I've created jsfiddle for you
I want to get a value straight from an attribute directive:
<form cronos-dataset="People as p">
Form Content
</form>
In my JS I tried:
app.directive('cronosDataset',[function() {
return {
restrict: 'A',
controller: 'CronosGenericDatasetController',
scope: {
"cronos-dataset" : '#'
}
};
}])
.controller("CronosGenericDatasetController",['$scope', function($scope) {
alert($scope["cronos-dataset"]);
}]);
I want to alert "People as p" string but I get undefined. Is that right path or should I go thorough a different approach?
You are supposed to have camelCase in the scope declaration
app.directive('cronosDataset',[function() {
return {
restrict: 'A',
controller: 'CronosGenericDatasetController',
scope: {
cronosDataset : '#'
}
};
}])
Here is a demo to see different variations
http://plnkr.co/edit/G6BiGgs4pzNqLW2sSMt7?p=preview
Make a link function instead:
app.directive('cronosDataset',[function() {
return {
scope: {},
restrict: 'A',
link: function (scope, elem, attrs) {
alert(attrs.cronosDataset);
}
Is there a way for not losing connection to the current controller when you are wrapping data with a directive ?
My problem is, that the directive within the wrapped template has no connection to the outside controller any more and so I can not execute the function.
Wrapping Directive:
myApp.directive('wrapContent', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "="
},
templateUrl: "./any/template.php",
link: function(scope, element, attr) {
// any
}
};
});
Directive within the wrapped Template
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
$(elem).click(function(e) {
scope.$apply(attrs.doAction);
});
}
}
});
Conroller:
lmsApp.controller('OutsideController', function ($scope){
$sope.sayHello = function() {
alert("hello");
};
});
HTML where I want to execute the function (template.php):
<div>
<do-action="sayHello()"></do-action>
</div>
How I call the wrapContent directive which is outside (Updated):
<div ng-controller="OutsideController">
<wrap-content model="any" datas="data_any"></wrap-content>
</div>
How can I execute the sayHello() function?
Thank you for your help! I would appreciate every answer.
wrapContent directive will be processed with the scope of controller.
DoAction directive will be processed with the isolateScope of wrapContent directive.
Solution1:
Get a reference to the sayHello function in wrapContent using '&' and execute it in event handler.
Solution2:
Instead of using scope in your event handler, use scope.$parent.
You should pass sayHallo function to your parent directive using &
myApp.directive('wrapContent', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "=",
sayHallo: "&"
},
templateUrl: "./any/template.php",
link: function(scope, element, attr) {
// any
}
};
});
HTML
<div ng-controller="OutsideController">
<wrap-content model="any" datas="data_any" sayHallo="sayHallo()"></wrap-content>
</div>
Then in your child directive, you will have sayHallo in your scope, to call it just do it this:
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
scope.sayHallo();
}
}
});
And you dont need pass it again. So your child directive should looks like this:
<div>
<do-action></do-action>
</div>
UPDATE
If you want to use all your parent model functions,without passing each function. In your child directive,just use scope.model to have access to model attributes and functions.
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
scope.model.sayHallo();
}
}
});
I have a list of person objects. I have a directive for displaying some read only data about the person and nested inside it a directive that acts as the toolbar for actions on the person (delete, friend etc). When you click on the first directive a second nested directive shows up to for editing the person.
In the simple example in plnker this works fine, but in actual life this gets really flimsy with fields not updating, or updating infinitely (editor has quite a few ngRepeats making things even weirder) etc.
It seems awkward that I have 3 isolated scopes and pass the the same object to all three, but on the other hand I need a lot of properties/methods of that object in all 3 directives so it makes sense to pass the whole object. Is there a better way of doing this?
app.directive('personCard', [function () {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'personcard.tpl.html',
scope: {
person: '='
}
};
return directive;
function link(scope, element, attrs) {
scope.isOpen = false;
scope.person.close = function(){
scope.isOpen = false;
}
scope.person.edit = function (){
scope.isOpen = true;
}
}
}]);
app.directive('personToolbar', [
function () {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'personcardtoolbar.tpl.html',
scope: {
person: '=',
close: '&',
edit: '&'
}
};
return directive;
function link(scope, element, attrs) {
}
}]);
app.directive('personEditor', [
function () {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'personeditor.tpl.html',
scope: {
person: '='
}
};
return directive;
function link(scope, element, attrs) {
}
}]);