Is it possible to have angularjs ng-click process events during the capturing phase instead of bubbling phase? I want to aggregate data from each of the parent elements in order starting from the parent and ending with the element that was clicked on.
Lets see the source code of ng-click at ngEventDirs.js#L50
As you can see the ng-click and all other event directives using .on().
So, the answer is No, it is not possible.
If you really need it, you could write a custom directive for that. For example, modify the code of ng-click a bit:
.directive('captureClick', function($parse) {
return {
restrict: 'A',
compile: function(element, attrs) {
var fn = $parse(attrs.captureClick);
return function(scope, element) {
element[0].addEventListener('click', function(event) {
scope.$apply(function() {
fn(scope, {
$event: event
});
});
}, true);
};
}
}
});
and use it like this:
<div title="A" ng-click="onBubbled($event)" capture-click="onCaptured($event)">
<div title="B" ng-click="onBubbled($event)" capture-click="onCaptured($event)">
<div title="C" ng-click="onBubbled($event)" capture-click="onCaptured($event)">
Yo!
</div>
</div>
</div>
Example Plunker: http://plnkr.co/edit/SVPv0fCNRQX4JXHeL47X?p=preview
Related
I've been searching for an answer to simple but not trivial question: What is a right way to catch image' onload event in Angular only with jqLite? I found this question , but I want some solution with directives.
So as I said, this is not accepted for me:
.controller("MyCtrl", function($scope){
// ...
img.onload = function () {
// ...
}
because it is in controller, not in directive.
Here's a re-usable directive in the style of angular's inbuilt event handling directives:
angular.module('sbLoad', [])
.directive('sbLoad', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var fn = $parse(attrs.sbLoad);
elem.on('load', function (event) {
scope.$apply(function() {
fn(scope, { $event: event });
});
});
}
};
}]);
When the img load event is fired the expression in the sb-load attribute is evaluated in the current scope along with the load event, passed in as $event. Here's how to use it:
HTML
<div ng-controller="MyCtrl">
<img sb-load="onImgLoad($event)">
</div>
JS
.controller("MyCtrl", function($scope){
// ...
$scope.onImgLoad = function (event) {
// ...
}
Note: "sb" is just the prefix I use for my custom directives.
Ok, jqLite' bind method doing well its job. It goes like this:
We are adding directive' name as attribute in our img tag . In my case , after loading and depending on its dimensions , image have to change its class name from "horizontal" to "vertical" , so directive's name will be "orientable" :
<img ng-src="image_path.jpg" class="horizontal" orientable />
And then we are creating simple directive like this:
var app = angular.module('myApp',[]);
app.directive('orientable', function () {
return {
link: function(scope, element, attrs) {
element.bind("load" , function(e){
// success, "onload" catched
// now we can do specific stuff:
if(this.naturalHeight > this.naturalWidth){
this.className = "vertical";
}
});
}
}
});
Example (explicit graphics!): http://jsfiddle.net/5nZYZ/63/
AngularJS V1.7.3 Added the ng-on-xxx directive:
<div ng-controller="MyCtrl">
<img ng-on-load="onImgLoad($event)">
</div>
AngularJS provides specific directives for many events, such as ngClick, so in most cases it is not necessary to use ngOn. However, AngularJS does not support all events and new events might be introduced in later DOM standards.
For more information, see AngularJS ng-on Directive API Reference.
In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}
HTML
<div my-dir>
<tour step=" currentstep">
<span tourtip="Few more steps to go."
tourtip-next-label="Close"
tourtip-placement="bottom"
tourtip-offset="80"
tourtip-step="0">
</span>
</tour>
</div>
I have written below directive to detect the x element of tour directive.But it always shows the parent div element even though I have clicked the x.So how can I do this ? Thanks in advance.
Directive
.directive('myDir', [
'$document',
function($document) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
element.on('click', function(e) {
scope.$apply(function() {
if (element[0].className === 'tour-close-tip') {
console.log('my task');
}
});
e.stopPropagation(); //stop event from bubbling up to document object
});
}
};
}
]);
UI
This is the generated HTML on the browser:
<div hide-element-when-clicked-out-side="" class="ng-scope">
<tour step=" currentstep" class="ng-scope">
<span tourtip="Few more steps to go.!" tourtip-next-label="Close" tourtip-placement="bottom" tourtip-offset="80" tourtip-step="0" class="ng-scope">
</span><div class="tour-tip" tour-popup="" style="display: block; top: 80px; left: 0px;">
<span class="tour-arrow tt-bottom"></span>
<div class="tour-content-wrapper">
<p ng-bind="ttContent" class="ng-binding">Few more steps to go.!</p>
<a ng-click="setCurrentStep(getCurrentStep() + 1)" ng-bind="ttNextLabel" class="small button tour-next-tip ng-binding">Close</a>
<a ng-click="closeTour()" class="tour-close-tip">×</a>
</div>
</div>
Can you tell me how to access class="tour-close-tip" element within the above directive ? For me it always shows the ng-scope as the class.
You can either bind directly to that element or check which element has been clicked on, using the target attribute:
element.on('click', function (e) {
scope.$apply(function () {
if (angular.element(e.target).hasClass('tour-close-tip')) {
Your eventListener is not on the X but on the outer div element. One option would be to add the listener to the X element using a query selector on the element
You could try something like the following to get the X span and add the listener
element[0].querySelector('span').on...
Another probably better approach would be to use event delegation such as
element.on('click', selector, function(e){
});
Edit: I see your comment regarding not using JQuery so this may not work as Angular doesn't support event delegation with .on as far as I am aware.
you could use this:
app.directive('myDir', [
'$document',
function($document) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
var x = angular.element(document.querySelector('.tour-close-tip'));
x.bind('click', function() {
console.log('clicked');
});
}
};
}
]);
here's a demo plnkr:
http://plnkr.co/edit/cUCJRetsqKmSbpI0iNoJ?p=preview
there's a heading with class 'tour-close-tip' there, and we attached a click event to it.
try it out, click the heading and look in your browser's console.
from this demo hopefuly you can make progress with your code.
Angular's ng-model is not updating when using jquery-ui spinner.
Here is the jsfiddle http://jsfiddle.net/gCzg7/1/
<div ng-app>
<div ng-controller="SpinnerCtrl">
<input type="text" id="spinner" ng-model="spinner"/><br/>
Value: {{spinner}}
</div>
</div>
<script>
$('#spinner').spinner({});
</script>
If you update the text box by typing it works fine (you can see the text change). But if you use the up or down arrows the model does not change.
Late answer, but... there's a very simple and clean "Angular way" to make sure that the spinner's spin events handle the update against ngModel without resorting to $apply (and especially without resorting to $parse or an emulation thereof).
All you need to do is define a very small directive with two traits:
The directive is placed as an attribute on the input element you want to turn into a spinner; and
The directive configures the spinner such that the spin event listener calls the ngModel controller's $setViewValue method with the spin event value.
Here's the directive in all its clear, tiny glory:
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
Note that $setViewValue is intended for exactly this situation:
This method should be called when an input directive wants to change
the view value; typically, this is done from within a DOM event
handler.
Here's a link to a working demo.
If the demo link provided above dies for some reason, here's the full example script:
(function () {
'use strict';
angular.module('ExampleApp', [])
.controller('ExampleController', ExampleController)
.directive('jqSpinner', jqSpinner);
function ExampleController() {
var c = this;
c.exampleValue = 123;
};
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
})();
And the minimal example template:
<div ng-app="ExampleApp" ng-controller="ExampleController as c">
<input jq-spinner ng-model="c.exampleValue" />
<p>{{c.exampleValue}}</p>
</div>
Your fiddle is showing something else.
Besides this: Angular can not know about any changes that occur from outside its scope without being aknowledged.
If you change a variable of the angular-scope from OUTSIDE angular, you need to call the apply()-Method to make Angular recognize those changes. Despite that implementing a spinner can be easily achieved with angular itself, in your case you must:
1. Move the spinner inside the SpinnerCtrl
2. Add the following to the SpinnerCtrl:
$('#spinner').spinner({
change: function( event, ui ) {
$scope.apply();
}
}
If you really need or want the jQuery-Plugin, then its probably best to not even have it in the controller itself, but put it inside a directive, since all DOM-Manipulation is ment to happen within directives in angular. But this is something that the AngularJS-Tutorials will also tell you.
Charminbear is right about needing $scope.$apply(). Their were several problems with this approach however. The 'change' event only fires when the spinner's focus is removed. So you have to click the spinner then click somewhere else. The 'spin' event is fired on each click. In addition, the model needs to be updated before $scope.$apply() is called.
Here is a working jsfiddle http://jsfiddle.net/3PVdE/
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
var mdlAttr = $(this).attr('ng-model').split(".");
if (mdlAttr.length > 1) {
var objAttr = mdlAttr[mdlAttr.length - 1];
var s = $scope[mdlAttr[0]];
for (var i = 0; i < mdlAttr.length - 2; i++) {
s = s[mdlAttr[i]];
}
s[objAttr] = ui.value;
} else {
$scope[mdlAttr[0]] = ui.value;
}
$scope.$apply();
}
}, 0);
});
Here's a similar question and approach https://stackoverflow.com/a/12167566/584761
as #Charminbear said angular is not aware of the change.
However the problem is not angular is not aware of a change to the model rather that it is not aware to the change of the input.
here is a directive that fixes that:
directives.directive('numeric', function() {
return function(scope, element, attrs) {
$(element).spinner({
change: function(event, ui) {
$(element).change();
}
});
};
});
by running $(element).change() you inform angular that the input has changed and then angular updates the model and rebinds.
note change runs on blur of the input this might not be what you want.
I know I'm late to the party, but I do it by updating the model with the ui.value in the spin event. Here's the updated fiddle.
function SpinnerCtrl($scope, $timeout) {
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
$scope.spinner = ui.value;
$scope.$apply();
}
}, 0);
});
}
If this method is "wrong", any suggestions would be appreciated.
Here is a solution that updates the model like coder’s solution, but it uses $parse instead of parsing the ng-model parameter itself.
app.directive('spinner', function($parse) {
return function(scope, element, attrs) {
$(element).spinner({
spin: function(event, ui) {
setTimeout(function() {
scope.$apply(function() {
scope._spinnerVal = = element.val();
$parse(attrs.ngModel + "=_spinnerVal")(scope);
delete scope._spinnerVal;
});
}, 0);
}
});
};
});
In the link function, is there a more "Angular" way to bind a function to a click event?
Right now, I'm doing...
myApp.directive('clickme', function() {
return function(scope, element, attrs) {
scope.clickingCallback = function() {alert('clicked!')};
element.bind('click', scope.clickingCallback);
} });
Is this the Angular way of doing it or is it an ugly hack? Perhaps I shouldn't be so concerned, but I'm new to this framework and would like to know the "correct" way of doing things, especially as the framework moves forward.
You may use a controller in directive:
angular.module('app', [])
.directive('appClick', function(){
return {
restrict: 'A',
scope: true,
template: '<button ng-click="click()">Click me</button> Clicked {{clicked}} times',
controller: function($scope, $element){
$scope.clicked = 0;
$scope.click = function(){
$scope.clicked++
}
}
}
});
Demo on plunkr
More about directives in Angular guide. And very helpfull for me was videos from official Angular blog post About those directives.
I think it is fine because I've seen many people doing this way.
If you are just defining the event handler within the directive,
you do not have to define it on the scope, though.
Following would be fine.
myApp.directive('clickme', function() {
return function(scope, element, attrs) {
var clickingCallback = function() {
alert('clicked!')
};
element.bind('click', clickingCallback);
}
});
Shouldn't it simply be:
<button ng-click="clickingCallback()">Click me<button>
Why do you want to write a new directive just to map your click event to a callback on your scope ? ng-click already does that for you.
You should use the controller in the directive and ng-click in the template html, as suggested previous responses. However, if you need to do DOM manipulation upon the event(click), such as on click of the button, you want to change the color of the button or so, then use the Link function and use the element to manipulate the dom.
If all you want to do is show some value on an HTML element or any such non-dom manipulative task, then you may not need a directive, and can directly use the controller.
In this case, no need for a directive. This does the job :
<button ng-click="count = count + 1" ng-init="count=0">
Increment
</button>
<span>
count: {{count}}
</span>
Source: https://docs.angularjs.org/api/ng/directive/ngClick
myApp.directive("clickme",function(){
return function(scope,element,attrs){
element.bind("mousedown",function(){
<<call the Controller function>>
scope.loadEditfrm(attrs.edtbtn);
});
};
});
this will act as onclick events on the attribute clickme