angularjs create custom event directive - angularjs

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

Related

AngularJS: how do I capture $event object in a custom directive?

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

Common directive ng-click guidance needed

I have a directive which consists of a form text element and a continue button along with the associated controller etc. This directive is going to be used in about 5 different pages, but on each page it is used the continue button will do something different.
My question is where can/should I put the code for the continue button if it does different things for each page?
Since its a directive I cant simply pass a different function into ng-click depending on what page im on (ie, if i simply replicated the code on each page it is used I could simply change the function called on ng-click and have that function in each of the page controllers.
Hopefully Im not being too vague with my question and you can make sense of what im asking. If not just say so and ill try to explain in more detail.
I would really appreciate some guidance on this matter.
Thanks.
There are two ways that you can do it. If you are creating your directive as a true component you can use isolated scope with & binding that binds to an expression.
Assume your directive looks like
<div do-work on-click="save()"></div>
and the generated html
<div>
<input ...>
<button ng-click="doAction()"><button>
</div>
The directive scope will be defined
scope:{
onClick:'&'
}
In your directive controller or link function you need to implement the button doAction, which in turns evaluates the onClick action
scope.doAction=function() {
scope.onClick({//if params are required});
}
Now you have linked the parent through the direct onClick reference. One thing to remember here is that this creates a directive with isolated scope.
In case you do not want isolated scope created you need to use
scope.$eval(attr.onClick); // this evaluates the expression on the current scope.
Hope this helps.
Ideally you should not create directives which are not re-usable.
In your case, you may do it like following -
create an isolated scope in the directive
add a function to be called and pass the page/ page id as parameter
call functions in controller based on parameter
Directive
myApp.directive('someDirecive', function () {
return {
// restrict options are EACM. we want to use it like an attribute
restrict: 'A',
// template : <inline template string>
// templateUrl = path to directive template.
// templateUrl: '',
scope: {
onButtonClick : '&'
},
controller: function ($scope, $element, $attrs, $transclude) {
$scope.onButtonClick = function(pageId) {
if (pageId == 1) {
// do something
}
else if (pageId == 2) {
// do something
}
}
},
//link: function (scope, iElement, iAttrs) {
//}
};
});
HTML
<div some-directive on-button-click="DoSomething(1)" />

Directive trigger controller before model is updated

I created a custom directive that create two buttons. Each button use ng-click to call a method inside the directive to change a value of the model and then call the controller to update the value in the database. In this way the controller is called before the model is updated because it always use the old value.
If I apply inside the directive $scope.$apply() everything works fine, except an error is fired in the consolle because there are an $apply() executing yet. How properly wait the model update before to call the controller?
The directive is used into an ng-repeat that iterate an array.
Logic I used is:
User Click one of the Buttons of the switch (On or Off i.e.)
Button Trigger directive setModel(value) method
setModel(value) set value to the model and call controller passing index of the object
controller get the object by index and update in database
If i try to send the value edited to the controller, of course, it works, but i would play with models and not with each values. Why the model is updated before controller do it's $http calls?
This is the directive:
app.directive('ngSwitch', function () {
return {
scope: {
model: '=ngSwitch',
index: '=index', //Index in the array
switchValue1: '#switchValue1',
switchDesc1: '#switchDesc1',
switchValue2: '#switchValue2',
switchDesc2: '#switchDesc2',
callback: '=callback'
},
link: function (scope, elm, attr) {
scope.saving = false;
scope.setModel = function (value) {
scope.model = value == 1 ? scope.switchValue1 : scope.switchValue2;
scope.saving = true;
scope.$apply();//IF I DON'T USE $apply, IT DOESN'T WORK
scope.callback(scope.index)
}
},
templateUrl: 'components/direttive/ngSwitch.html'
};
});

How to implement an ng-change for a custom directive

I have a directive with a template like
<div>
<div ng-repeat="item in items" ng-click="updateModel(item)">
<div>
My directive is declared as:
return {
templateUrl: '...',
restrict: 'E',
require: '^ngModel',
scope: {
items: '=',
ngModel: '=',
ngChange: '&'
},
link: function postLink(scope, element, attrs)
{
scope.updateModel = function(item)
{
scope.ngModel = item;
scope.ngChange();
}
}
}
I would like to have ng-change called when an item is clicked and the value of foo has been changed already.
That is, if my directive is implemented as:
<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>
I would expect to call bar when the value of foo has been updated.
With code given above, ngChange is successfully called, but it is called with the old value of foo instead of the new updated value.
One way to solve the problem is to call ngChange inside a timeout to execute it at some point in the future, when the value of foo has been already changed. But this solution make me loose control over the order in which things are supposed to be executed and I assume that there should be a more elegant solution.
I could also use a watcher over foo in the parent scope, but this solution doesn't really give an ngChange method to be implmented and I have been told that watchers are great memory consumers.
Is there a way to make ngChange be executed synchronously without a timeout or a watcher?
Example: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview
If you require ngModel you can just call $setViewValue on the ngModelController, which implicitly evaluates ng-change. The fourth parameter to the linking function should be the ngModelCtrl. The following code will make ng-change work for your directive.
link : function(scope, element, attrs, ngModelCtrl){
scope.updateModel = function(item) {
ngModelCtrl.$setViewValue(item);
}
}
In order for your solution to work, please remove ngChange and ngModel from isolate scope of myDirective.
Here's a plunk: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview
tl;dr
In my experience you just need to inherit from the ngModelCtrl. the ng-change expression will be automatically evaluated when you use the method ngModelCtrl.$setViewValue
angular.module("myApp").directive("myDirective", function(){
return {
require:"^ngModel", // this is important,
scope:{
... // put the variables you need here but DO NOT have a variable named ngModel or ngChange
},
link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically eval your ng-change
};
}
};
});
More precisely
ng-change is evaluated during the ngModelCtrl.$commitViewValue() IF the object reference of your ngModel has changed. the method $commitViewValue() is called automatically by $setViewValue(value, trigger) if you do not use the trigger argument or have not precised any ngModelOptions.
I specified that the ng-change would be automatically triggered if the reference of the $viewValue changed. When your ngModel is a string or an int, you don't have to worry about it. If your ngModel is an object and your just changing some of its properties, then $setViewValue will not eval ngChange.
If we take the code example from the start of the post
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically evalyour ng-change
};
scope.updateValue = function(prop1Value){
var vv = ctrl.$viewValue;
vv.prop1 = prop1Value;
ctrl.$setViewValue(vv); // this line won't eval the ng-change expression
};
After some research, it seems that the best approach is to use $timeout(callback, 0).
It automatically launches a $digest cycle just after the callback is executed.
So, in my case, the solution was to use
$timeout(scope.ngChange, 0);
This way, it doesn't matter what is the signature of your callback, it will be executed just as you defined it in the parent scope.
Here is the plunkr with such changes: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview
Samuli Ulmanen and lucienBertin's answers nail it, although a bit of further reading in the AngularJS documentation provides further advise on how to handle this (see https://docs.angularjs.org/api/ng/type/ngModel.NgModelController).
Specifically in the cases where you are passing objects to $setViewValue(myObj). AngularJS Documentatation states:
When used with standard inputs, the view value will always be a string (which is in some cases parsed into another type, such as a Date object for input[date].) However, custom controls might also pass objects to this method. In this case, we should make a copy of the object before passing it to $setViewValue. This is because ngModel does not perform a deep watch of objects, it only looks for a change of identity. If you only change the property of the object then ngModel will not realize that the object has changed and will not invoke the $parsers and $validators pipelines. For this reason, you should not change properties of the copy once it has been passed to $setViewValue. Otherwise you may cause the model value on the scope to change incorrectly.
For my specific case, my model is a moment date object, so I must clone the object first before then calling setViewValue. I am lucky here as moment provides a simple clone method: var b = moment(a);
link : function(scope, elements, attrs, ctrl) {
scope.updateModel = function (value) {
if (ctrl.$viewValue == value) {
var copyOfObject = moment(value);
ctrl.$setViewValue(copyOfObject);
}
else
{
ctrl.$setViewValue(value);
}
};
}
The fundamental issue here is that the underlying model does not get updated until the digest cycle that happens after scope.updateModel has finished executing. If the ngChange function requires details of the update that is being made then those details can be made available explicitly to ngChange, rather than relying on the model updating having been previously applied.
This can be done by providing a map of local variable names to values when calling ngChange. In this scenario, you can mapping the new value of the model to a name which can be referenced in the ng-change expression.
For example:
scope.updateModel = function(item)
{
scope.ngModel = item;
scope.ngChange({newValue: item});
}
In the HTML:
<my-directive ng-model="foo" items=items ng-change="bar(newValue)"></my-directive>
See: http://plnkr.co/edit/4CQBEV1S2wFFwKWbWec3?p=preview

create "nested" ng-click inside of Angular Directive

I have a directive which open up a bootstrap-tours on call of t.start():
app.directive('tourGuide', function ($parse, $state) {
var directiveDefinitionObject = {
restrict: 'E',
replace: false,
link: function (scope, element, attrs) {
var t = new Tour({container: $("#main"),
backdrop: false,
debug:true
});
t.addStep({
element: "#main",
title: "Title123",
content: "Content123"
});
t.init();
t.start();
}};
return directiveDefinitionObject;
});
I want to create a button which on click could call variable t.start(). Is it even possible? I want to achieve this so could be independent of functions inside controllers, because this directive will be on every single view of the application, so it would be nice if it could call a parameter inside itself. Ive tryed to create a template in directive with a button, and add a ng-clikc action with t.start() and ofcourse it failed because variable t is not known to controller where ever my directive is.
EXAMPLE:
Lets say i have 2 views ShowItems and CreateItem they have 2 dirfferent controllers. in those views i have 1 button/link, on click of it i want to show my TourGuide. Thats simple.
Now in my TourGuide i have 2 different Steps, and when i press on a button in CreateItem view i want to see the step in Tour Guide for CreateItem view, and vise versa.
Thats simple if i use functions inside my controller. But is it possible to use directive ONLY, because i could have 20 different controllers?
Based on a few assumptions - I assume what you want here is to dynamically call a routine in scope from a directive. Take the following code as an example
HTML/View Code
<div my-directive="callbackRoutine">Click Here</div>
Controller
function MyController($scope) {
$scope.callbackRoutine = function () {
alert("callback");
};
}
Directive
app.directive("myDirective", function () {
return {
restrict: 'A',
link: function (scope, element, attr){
element.bind('click', function (){
if (typeof scope[attr.myDirective] == "function"){
scope[attr.myDirective]();
}
});
}
};
});
In this, you specify the callback routine as part of the directive. The key to the equation is that the scope for the directive inherits from any parent scope(s) which means you can call the routine even from the scope passed to the directive. To see a working example of this, see the following plunkr: http://plnkr.co/edit/lQ1QlwwWdpNvoYHlWwK8?p=preview. Hope that helps some!

Resources