I have a function that requires the $event obj to be passed into as a parameter. The function works just fine when I call it using ng-click. However, now I am told to change the function into a custom directive. But now I don't know how to access the $event object every time the element containing the custom directive gets clicked. Any suggestions? I have this so far but console logging is giving me undefined.
(function () {
angular.module(APPNAME)
.directive('clickTracker', clickTracker);
function clickTracker() {
return {
scope: true,
link: function (scope, element, attrs) {
element.bind('click', function () {
var mouseEvent = scope.$eval(scope.$event);
console.log(mouseEvent);
}
}
}
}
})()
event is first argument of a jQlite event handler callback ... the same as with jQuery
element.bind('click', function (evt) {
var mouseEvent = scope.$eval(evt);
console.log(mouseEvent);
}
As for this being custom directive you can still use ng-click and assign the scope function in directive
Related
To start off, I know there are loads of similar questions but none that I found which supports execution of arbitrary methods with event listeners.
I have this directive which executes functions passed when the window resizes.
app.directive('onResize', function() {
var directive = {
'link': function(scope, element, attrs) {
var onResizeHandler = scope.$eval(attrs.onResize);
angular.element(window).on('resize', onResizeHandler);
angular.element(window).on('$destory', function() {element.off();});
}
};
return directive;
});
I can trigger the above directive with
<div data-on-resize="stickyHeader">...</div>
Which runs my method, inside my controller.
app.controller('myController', [$scope, function($scope) {
$scope.stickyHeader = function() {
console.log('event triggered')
};
}]);
All the above code works fine, but I need to pass some arguments to stickyHeader as in data-on-resize="stickyHeader(arg1, arg2)" When I try that, I get Cannot read property 'call' of undefined at ng (angular.js:3795) in the console. Not sure what I can do to make my directive support arbitrary methods with arguments.
The directive needs to evaluate the AngularJS expression defined by the on-resize attribute every time the events occurs:
app.directive('onResize', function($window) {
var directive = {
link: function(scope, elem, attrs) {
angular.element($window).on('resize', onResizeHandler);
scope.$on('$destroy', function() {
angular.element($window).off('resize', onResizeHandler);
});
function onResizeHandler(event) {
scope.$eval(attrs.onResize, {$event: event});
scope.$apply();
}
}
};
return directive;
});
Also since the resize event comes from outside the AngularJS framework, the event needs to be brought into the AngularJS execution context with $apply().
Further, to avoid memory leaks, the event handler needs to be unbound when the scope is destroyed.
Usage:
<div data-on-resize="stickyHeader($event)">...</div>
For more information, see AngularJS Developer Guide - $event.
I am going through this doc, the confusion I have is what is 'this' in link.apply(this, attrs). Can someone help?
$provide.decorator('fooDirective', function($delegate) {
var directive = $delegate[0];
directive.scope.fn = "&";
var link = directive.link;
directive.compile = function() {
return function(scope, element, attrs) {
link.apply(this, arguments);
element.bind('click', function() {
scope.$apply(function() {
scope.fn();
});
});
};
};
return $delegate;
});
});
when I try to debug it using console debugger, 'this' is undefined while link function is running.
There's no special this context in Angular decorator, so it may be window in loose mode or undefined in strict mode.
In nested functions this may refer to non-lexical context, which may take place in Angular directives:
directive.compile = function() {
// `this` is directive DDO in compile function
return function(scope, element, attrs) {
// `this` is `undefined` in link function
...
};
};
In compile function this is directive DDO. In controller function this is controller instance. There's no lexical this in link function.
link.apply(this, arguments) is an attempt to play safe but here it is just misleading. It may be link.apply(null, arguments) instead.
You need to create a compile function which will return your new link function.
Inside there, you call apply (passing as the first parameter the function itself) in the old link function to get the old functionality.
With that set, you just need to add the extra behavior (in this case you bind the click event into the element which will call the new function upon click).
I have a simple attribute-restricted directive like
app.directive('akMouseOver', function () {
return {
restrict: 'A',
scope: {
mouseOver: '&akMouseOver'
},
controller: function ($scope) {
},
link: function (scope, elem, attrs) {
elem.bind('mouseover', function () {
scope.mouseOver({ hValue: value });
});
}
}})
that I am calling on a simple HTML button as
<button ak-mouse-over='btnMouseOver('Win Win')' class='btn btn-primary'> Hello There !</button>
and my parent controller method is
$scope.btnMouseOver = function (hValue) {
alert(hValue + 'Hello !!!');
}
Here, somehow, I am unable to pass a parameter to the parent method. If I make this implementation without parameter, it is working and I see alert(), if I hover the mouse over the button.
Looking for passing (a) parameter/s without adding additional attribute/directive/scope variable.
In your case it should work & then it would alert with Win Win Hello !!! because you had hardcoded value of function level, even if you pass value from directive it will just pass the same.
While passing value from directive to registered function of isolated scope, you should have btnMouseOver(hValue), because when you are calling mouseOver function of directive which will basically going to call btnMouseOver method registered on ak-mouse-over attribute.
At the time of passing value you need to have pass value to parent controller function in JSON kind of format like {hValue: value} where hValue will represent parameter of btnMouseOver function, placed over a ak-mouse-over and then value is value which you are passing to function.
<button ak-mouse-over="btnMouseOver(hValue)">
Hello There !
</button>
Also you need to call scope.$apply() from mouserover event handler to keep of digest cycle as you are running an event outside angular context.
Demo here
I have a simple Angular directive that adds a CSS class to an element when being clicked:
.directive("addClass", function () {
return {
scope: {
name: "=addClass"
},
link: function (scope, element, attributes) {
element.on("click", function () {
element.addClass(scope.name);
console.log("Element activated");
});
element.on("mouseleave", function () {
element.removeClass(scope.name);
console.log("Element deactivated");
});
}
}
});
I'm using it on my links:
...
But when I click my link my event handlers execute, although scope.name is undefined. I could read attribute value using attributes, but that beats the purpose of assigning attribute values to scope properties as described in Angular Directives API.
What am I doing wrong?
Replace =addClass with #addClass, or, in case you don't need an isolate scope, just read the class name right out of the attribute object:
element.on("click", function() {
element.addClass(attributes.addClass);
...
});
The reason = doesn't work in your case is that it expects a property so a two-way binding can be established and you're providing a static string (I'm assuming you are since class-name isn't a valid property name).
try ..., note the single-quoted 'class-name'
Perhaps I have a fundamental misunderstanding of how directive controllers work, from what I understand they are used as a sort of API to be exposed to other directives & controllers. I am trying to get the controller and link function to communicate internally.
For example I would like to be able to set a variable via the controller function and then use it in the link function:
var app = angular.module('test-app', []);
app.directive('coolDirective', function () {
return {
controller: function () {
this.sayHi = function($scope, $element, $attrs) {
$scope.myVar = "yo"
}
},
link: function(scope, el, attrs) {
console.log(scope.myVar);
}
}
});
How can I access myVar or sayHi within the link function? Or have I just missed the point completely?
Both controller's $scope (defined in the controller, not in the sayHi function) and link scope are the same. Setting something in the controller will be usable from the link or viceversa.
The problem you have is that sayHi is a function that is never fired so myVar is never set.
Since sayHi is not in the scope, you need a reference to the controller and to do it, you can add a fourth parameter like this:
link: function(scope, element, attr, ctrl) {}
Then you could do a ctrl.sayHi() (But again, those params of sayHi belongs to the controller function.)
If you ever need to require another controller and still wanting to use its own directive, then you will need to require it too. So if this coolDirective needs to access to the controller of notCoolAtAll you could do:
require: ['coolDirective', 'notCoolAtAll']
That will do the trick. The link function will receive then an array of controllers as the fourth param and in this case the first element will be coolDirective ctrl and the second one the notCoolAtAll one.
Here is a little example: http://plnkr.co/edit/JXahWE43H3biouygmnOX?p=preview
Rewriting your code above, it would look something like this:
var app = angular.module('test-app', []);
app.directive('coolDirective', function() {
return {
controller: function($scope) {
// bind myVar property to scope
$scope.myVar = 'yo';
// bind sayHi method to scope
$scope.sayHi = sayHi;
// abstracting out the sayHi function
function sayHi() {
console.log($scope.myVar);
}
},
link: function(scope, el, attrs) {
// execute the sayHi function from link
scope.sayHi(); // "yo" in console
}
};
});
Good Luck.