Consider the following directive example: (Live Demo)
app.directive('phone', function() {
return {
restrict: 'E',
scope: {
tel: '#'
},
template: '<div>{{tel}}</div>',
link: function(scope, element, attrs) {
console.log(scope.tel); // undefined
}
};
});
which is used like this:
<phone tel="1234"></phone>
tel is accessible in the template, but in the linking function it is undefined. Why? How could I access the isolated scope from the linking function?
It won't get interpolated before the linking function is done (I'm not sure why this is), but you have a couple of options:
app.directive('phone', function($timeout, $interpolate) {
return {
restrict: 'E',
scope: {
tel: '#'
},
template: '<div>{{tel}}</div>',
link: function(scope, element, attrs) {
//Use $timeout service with no delay:
$timeout(function(){
console.log(scope.tel); // 1234
});
//Use $watch - will get called every time the value changes:
scope.$watch('tel',function(tel){
console.log(scope.tel); // 1234
});
//You can even use the $intrapolate service, this is basically what `#` does:
console.log($interpolate(element.attr('tel'))(scope.$parent));
// in your example tel isn't an expression but a constant so you could also do this:
console.log(attrs.tel); // 1234
}
};
});
Since # isolate scope can only pass strings, your only option is
console.log(attrs.tel)
"The # local scope property is used to access string values that are defined outside the directive" - http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-2-isolate-scope
Related
I'm trying to generate a smart-table directive from within a custom directive I've defined:
<div ng-controller="myContrtoller">
<containing-directive></containing-directive>
</div>
The directive definition:
angular.module('app')
.directive('containingDirective', function() {
return {
restrict: 'E',
replace: true,
template: '<table st-table="collection" st-pipe="scopeFun"></table>',
link: function(scope, elem, attrs) {
scope.scopeFun = function () {
// solve the misteries of life
}
}
}
});
As you can see my directive tries to replace the element by the template generated by the st-table directive, using the st-pipe directive depending on the first, briefly:
ng.module('smart-table')
.controller('stTableController' function () {
// body...
})
.directive('stTable', function () {
return {
restrict: 'A',
controller: 'stTableController',
link: function (scope, element, attr, ctrl) {
// body
}
};
})
.directive('stPipe', function (config, $timeout) {
return {
require: 'stTable',
scope: {
stPipe: '='
},
link: {
pre: function (scope, element, attrs, ctrl) {
var pipePromise = null;
if (ng.isFunction(scope.stPipe)) { // THIS IS ALWAYS UNDEFINED
// DO THINGS
}
},
post: function (scope, element, attrs, ctrl) {
ctrl.pipe();
}
}
};
});
Problem:
The st-pipe directive checks the scope var stPipe if it is defined or not by: if (ng.isFunction(scope.stPipe)). This turns out to be ALWAYS undefined. By inspecting I found two things:
From the stPipe directive, the value supposed to be scope.stPipe that is my scopeFun defined within my containingDirective is undefined on the scope object BUT defined within the scope.$parent object.
If I define my $scope.scopeFun within the myContrtoller I don't have any problem, everything works.
Solution:
I did find a solutions but I don't know what really is going on:
Set replace: false in the containingDirective
Define the scope.scopeFun in the pre-link function of containingDirective
Questions:
Why is the scopeFun available in the stPipe directive scope object if defined in the controller and why it is available in the scope.$parent if defined in the containingDirective?
What is really going on with my solution, and is it possible to find a cleaner solution?
From the docs: "The replacement process migrates all of the attributes / classes from the old element to the new one" so what was happening was this:
<containing-directive whatever-attribute=whatever></containing-directive>
was being replaced with
<table st-table="collection" st-pipe="scopeFun" whatever-attribute=whatever></table>
and somehow st-table did not enjoy the extra attributes (even with no attributes at all..).
By wrapping the containingDirective directive template within another div fixed the problem (I can now use replace:true):
<div><table st-table="collection" st-pipe="scopeFun"></table></div>
If someone has a more structured answer would be really appreciated
I was wondering if its possible to access attrs that were passed into a custom directive directly within the template config? So something like this... which currently isn't working?
angular.module('msDirectives', [])
.directive('msPerson', function(){
return{
restrict: 'E',
link: function(scope, element, attrs){
},
template: '<h1>{{attrs.firstName}}</h1>',
};
});
I realize I could assign attrs.firstName to scope within the link function to get this to work (like the following), just trying to understand whether scope is the only thing accessible within the template or whether attrs also get passed to it.
angular.module('msDirectives', [])
.directive('msPerson', function(){
return{
restrict: 'E',
link: function(scope, element, attrs){
scope.name = attrs.firstName;
},
template: '<h1>{{name}}</h1>'
};
});
The purpose of isolated scope (besides the isolation itself) is the assignment of attribute values to scope.
angular.module('msDirectives', [])
.directive('msPerson', function(){
return{
restrict: 'E',
scope: {
name: '#firstName'
},
template: '<h1>{{name}}</h1>'
};
});
I can't seem to reach the link function scope variable from inside a function in my directive. The "elem" variable is defined, but the scope isn't. Why is that??
Here's my directive:
function contextMenu($document){
return {
scope: {
target: '=',
},
restrict: 'A',
link: function(scope, elem, attr) {
elem.bind('contextmenu',handleRightClick);
function handleRightClick(event) {
// do something with scope (scope is undefined)
}
}
}
};
How can I user the scope variable?
Thanks!
Uri
EDIT1:
I found I can use this to pass the scope to the function:
Passing parameters to click() & bind() event in jquery?, but this still doesn't explain why the scope variable is undefined.
EDIT2:
For completeness sake, this is how my directive is set up:
app.js
angular
.module('myModule', [])
.directive('contextMenu', ['$document', components.contextMenu])
and in the html:
<div context-menu target="testObject">
Make sure you are using the directive correctly. Since you didn't include the use of the directive in your template, I hope you used it something like this:
<div context-menu target="something"></div>
Then I am confused about the setup of your directive. Try this:
MyDirectiveModule.directive('contextMenu', function(){
return {
restrict: 'A',
scope: {
target: '#'
},
link: function(scope, element){
console.log(scope);
// don't use $scope!
}
};
});
Make sure to use scope instead of $scope in the link: part.
Is there a way to pass configuration object into custom directive which defined as a attribute-directive?
I've got an object in Controller that I want to send to directive:
$scope.configObject = {
count: 5,
mode: 'fast',
getData: myService.getData // function of external service that avaliable in controller
}
In my View I declare directive:
<div class='list-class' my-list='configObject'></div>
Directive looks like:
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var config = angular.fromJson(attrs.myList);
}
}
i've tried to get config object using angular.getJson - but it doesn't work for functions (it's possible to get only count and mode). Is .getJson() the incorrect way to get config?
Also (I guess it's not even possible) - is there a way to get config object avoiding accessing to
attrs.myList
directly? I mean if I change initializing of directive from
.directive('myList', function() { ... }) to
.directive('myCustomList', function() { ... })
shall I change accessing to
attrs.myCustomList
because view would look like
<div class='list-class' my-custom-list='configObject'></div>
you can pass it using isolate scope if you want
return {
restrict: 'A',
scope: { config : '=myList' }
link: function(scope, elem, attrs) {
//access using scope.config
}
}
or as already answered you can parse it from attrs
$parse(attrs["myList"])(scope);
and yes if you change the directive to myCustomList, you will have to change the code
scope: { config : '=myCustomList' }
or
$parse(attrs["myCustomList"])(scope);
You can use $parse service to fetch the config object.
(function(){
var directiveName = 'myList';
angular.module('YourModule').directive(directiveName,['$parse',function($parse){
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var config = $parse(attrs[directiveName])(scope);
}
};
}]);
})();
You can $eval the attribute
link: function(scole, element, attrs) {
var config = scope.$eval(attrs['myList']);
}
Say I have a directive like such:
<my-directive>This is my entry!</my-directive>
How can I bind the content of the element into my directive's scope?
myApp.directive('myDirective', function () {
return {
scope : {
entry : "" //what goes here to bind "This is my entry" to scope.entry?
},
restrict: "E",
template: "<textarea>{{entry}}</textarea>"
link: function (scope, elm, attr) {
}
};
});
I think there's much simpler solution to the ones already given. As far as I understand, you want to bind contents of an element to scope during initialization of directive.
Given this html:
<textarea bind-content ng-model="entry">This is my entry!</textarea>
Define bind-content as follows:
directive('bindContent', function() {
return {
require: 'ngModel',
link: function ($scope, $element, $attrs, ngModelCtrl) {
ngModelCtrl.$setViewValue($element.text());
}
}
})
Here's a demo.
I may have found a solution. It relies on the transclude function of directives. It works, but I need to better understand transclusion before being sure this is the right way.
myApp.directive('myDirective', function() {
return {
scope: {
},
restrict: 'E',
replace: false,
template: '<form>' +
'<textarea ng-model="entry"></textarea>' +
'<button ng-click="submit()">Submit</button>' +
'</form>',
transclude : true,
compile : function(element,attr,transclude){
return function (scope, iElement, iAttrs) {
transclude(scope, function(originalElement){
scope.entry = originalElement.text(); // this is where you have reference to the original element.
});
scope.submit = function(){
console.log('update entry');
}
}
}
};
});
You will want to add a template config to your directive.
myApp.directive('myDirective', function () {
return {
scope : {
entry : "=" //what goes here to bind "This is my entry" to scope.entry?
},
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "E",
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
myApp.controller('fooController', function($scope){
$scope.foo = "BLAH BLAH BLAH!";
});
And then use your directive like this:
<div ng-controller="fooController">
<!-- sets directive "entry" to "foo" from parent scope-->
<my-directive entry="foo"></my-directive>
</div>
And angular will turn that into:
<div>THIS IS MY ENTRY</div>
Assuming that you have angular setup correctly and are including this JS file onto your page.
EDIT
It sounds like you want to do something like the following:
<my-directive="foo"></my-directive>
This isn't possible with ELEMENT directives. It is, however, with attribute directives. Check the following.
myApp.directive('myDirective', function () {
return {
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "A",
scope : {
entry : "=myDirective" //what goes here to bind "This is my entry" to scope.entry?
},
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
Then use it like this:
<div my-directive="foo"></div>
This will alias the value passed to my-directive onto a scope variable called entry. Unfortunately, there is no way to do this with an element-restricted directive. What is preventing it from happening isn't Angular, it is the html5 guidelines that make what you are wanting to do impossible. You will have to use an attribute directive instead of an element directive.