angular directive - scope undefined inside function - angularjs

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.

Related

Directive within another directive - scope var undefined

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

binding data in a directive returns inconsistent values

i have this wierd problem with binding data to a directive. this is how i declare my directive:
<my-directive data="myArray"></my-directive>
my directive code looks like:
angular.module('ngApp')
.directive('myDirective', function () {
return {
scope:{
data: '='
},
template: '<div steps="data.length"></div>',
restrict: 'E',
link: function postLink(scope, element, attrs) {
console.log(scope);
console.log(scope.data);
}
};
});
in the first log, the data property is correct:
screenshot of console.log output
but the second log is undefined.
any idea why?
here is your solution :
<my-directive data-example="myArray"></my-directive>
you should always name your variables data-<name>. This is far easier to maintain and this prevent errors with keywords like this one ;)
angular.module('ngApp')
.directive('myDirective', function () {
return {
scope:{
data: '=example'
},
template: '<div steps="data.length"></div>',
restrict: 'E',
link: function postLink(scope, element, attrs) {
console.log(scope);
console.log(scope.data);
}
};
});
i hope this help,
Live example : JsFiddle
thanks to #hadiJZ and #Unex i figured it out:
my directive was nested in another directive. but the parent directive used the link function for the logic, since on creation it wasn't having child directives.
so moving the logic out to a controller solved my problem.
i figured it out when i added a $timeout to the child directive, and the log (scope.data) was correct.

angular directive with attributes linked to function outside the parent scope

I have an angular directive (restrict to an element), its have an attribute to link to a function (using "&" attribute)
for example:
app.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
myonclick: "&",
},
controller: 'myController',
template: '<button ng-click="clicked()">Click me!</button>'
}
});
i'm assigning a function to the attribute like this:
<my-directive myonclick="inScopeFunction()"></my-directive>
its work fine if the function is inside the angular controller scope.
but i cant assign it to a function outside the angular scope.
there is a working example at plunker
Thanks
Since inScopeFunction() is a global method, you can call it directly inside your directive. Register a click listener on the element inside directive and call inScopeFunction() in listener callback. Here is the updated plunk
link: function postLink (scope, element, attrs) {
element.on('click', function () {
window[attrs.myonclick]()
});
}
html:
<my-directive myonclick="outScopeFunction"></my-directive>
Your controller will have access to global objects so you can access it from there
$scope.inScopeFunction = outScopeFunction
To access it from your directive controller you could pass in a string and call the global object from there
<my-directive myonclick="'outScopeFunction'"></my-directive>
...
app.controller('myController', function($scope) {
$scope.clicked = function(){
window[$scope.myonclick()]()
}
});

Directive view does not get updated while updating scope variable in post link

I am not able to get my view updated while updating scope variable in post link function.
Following is the use of my directive.
<my-directive color='purple'>
</my-directive>
Following is the definition of my directive.
app.directive('myDirective', function () {
console.log('My Directive Called');
return {
restrict: 'E',
scope: {
localVar: '#color'
},
//template: '<span></span>', // When I enable this template it works fine.
/* This link way it is working fine.
link: function (scope, iElement, iAttrs) {
console.log(iElement);
iAttrs.color = 'red';
}*/
//This is not working Reason don't know.
compile: function (tElement, tAttrs) {
var spanElem = angular.element('<span> {{ localVar }} </span>');
spanElem.attr('color', tAttrs.color);
tElement.replaceWith(spanElem);
return function (scope, iElement, iAttrs) {
iAttrs.color = 'red';
};
}
};
});
I want to know the reason why this code is not working. It will work if I specify the template property in directive definition object. But I want to know what is going wrong in above code.
Please help me.
It's much easy if you do somehting like this:
JSFiddle
angular.module('myApp', [])
.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
localVar: '#color'
},
template: '<span> {{ localVar }} </span>'
};
});
Without calling link function there is no two way data binding between template created by compile function and scope.
That's why when you turn on link function you get the desired result.
From angular docs.Please read this point.
HTML compilation happens in three phases:
$compile traverses the DOM and matches directives.
If the compiler finds that an element matches a directive, then the directive is added to the list of directives that match the DOM element. A single element may match multiple directives.
Once all directives matching a DOM element have been identified, the compiler sorts the directives by their priority.
Each directive's compile functions are executed. Each compile function has a chance to modify the DOM. Each compile function returns a link function. These functions are composed into a "combined" link function, which invokes each directive's returned link function.
$compile links the template with the scope by calling the combined linking function from the previous step. This in turn will call the linking function of the individual directives, registering listeners on the elements and setting up $watchs with the scope as each directive is configured to do.
The result of this is a live binding between the scope and the DOM. So at this point, a change in a model on the compiled scope will be reflected in the DOM.
EDIT CODE :
If you want to do it withour compile and link function,try to use isolated scope
EDIT CODE 2:
.directive('myDirective', function () {
console.log('My Directive Called');
return {
restrict: 'E',
scope: {
localVar: '#color'
},
template : '<span> {{ localVar }} </span>'
};
});
HTML :
<my-directive color='purple'>
</my-directive>
EDIT CODE 3:
directive('myDirective', function () {
console.log('My Directive Called');
return {
restrict: 'EA',
template: '<span>{{ localVar }}</span>', // When I enable this template it works fine.
compile: function (tElement, tAttrs) {
return {
post: function postLink(scope, iElement, iAttrs, controller) {
scope.localVar = 'red';
}
}
}
};
})

$watch in a link function of my directive is not updated

I have this directive which tries to watch for changes in a json object on scope. The JSON object is retrieved using a restangular based service, but somehow the $watch seems to be executed only once, logging 'undefined'.
The directive is used in the index.html of the app, so I suspect this has to do with the controller only working for the specific view or form...is there a way to get the directive to see those changes?
update: figured I could just call the TextsService from the directive itself, seems like a good solution to the problem. If anyone has better suggestions I'd welcome them still though.
service:
angular.module('main').service('TextsService', function(Restangular) {
this.getTexts = function(jsonRequestBase, jsonRequest, callback) {
Restangular.one(jsonRequestBase, jsonRequest).get().then(
function(texts) {
callback(texts);
}
);
};
});
call in controller:
TexstService.getTexts("content", "file.json", function (texts) {
$scope.mytest = texts;
});
directive:
app.directive('myDirective',
function() {
return {
restrict: 'A',
templateUrl:'test.html',
transclude: true,
link: function(scope, elm, attrs) {
scope.$watch('mytest', function(){
console.log(scope.mytest);
}, true);
I figured I could just call the TextsService from the directive itself, seems like a good solution to the problem. If anyone has better suggestions I'd welcome them still though.
Create the variable with null before receving it. Maybe that works.
The problem is that your directive's scope does not know about the mytest variable. You need to define the binding when you define the directive:
app.directive('myDirective',
function() {
return {
scope: {
myDirective: '='
},
restrict: 'A',
templateUrl:'test.html',
transclude: true,
link: function(scope, elm, attrs) {
scope.$watch('myDirective', function(newVal){
console.log(newVal);
}, true);
};
}
);
This will get whatever variable is assigned to the directive attribute and watch its' value.
And your directive in the view should look like this to bind it to the 'mytest':
<div my-directive="mytest"></div>

Resources