I want something to begin on ng-mousedown
and end on mouseup
. I also want that something to continue happening while the mouse button remains down if the mouse leaves the element. A pretty common way to implement this in vanilla Javascript is to bind a document mouseup
event in the element mousedown
event, like so:
HTML:
<button onMouseDown='onMouseDown()'>Click Me</button>
<div id="val"></div>
JS:
function onMouseDown() {
var el = document.querySelector('#val'),
changeValue = function() {
el.innerHTML = 'done!';
document.removeEventListener('mouseup', changeValue);
};
el.innerHTML = 'waiting for mouseup...';
document.addEventListener('mouseup', changeValue);
}
I am having trouble implementing this behavior in Angular with jQuery's one()
, angular.element.one
, and addEventListener
/ removeEventListener
:
HTML:
<div ng-app="MyApp" ng-controller="AppCtrl">
<div>Note that 'done!' is not rendered, despite 'mouseup' being logged to the console</div>
<button ng-mousedown='changeValue()'>Click Me</button>
<div>{{value}}</div>
</div>
JS (jQuery's one()
):
angular
.module('MyApp', [])
.controller('AppCtrl', function($scope) {
$scope.changeValue = function() {
console.log('mousedown');
$scope.value = 'waiting for mouseup...';
$(document).one('mouseup', function() {
console.log('mouseup');
$scope.value = 'done!';
});
}
});
JS (angular.element.one
):
angular
.module('MyApp', [])
.controller('AppCtrl', function($scope) {
$scope.changeValue = function() {
console.log('mousedown');
$scope.value = 'waiting for mouseup...';
angular.element(document).one('mouseup', function() {
console.log('mouseup');
$scope.value = 'done!';
});
}
});
JS (addEventListener
/ removeEventListener
):
angular
.module('MyApp', [])
.controller('AppCtrl', function($scope) {
$scope.changeValue = function() {
var changeValue = function() {
console.log('mouseup');
$scope.value = 'done!';
document.removeEventListener('mouseup', changeValue);
};
console.log('mousedown');
$scope.value = 'waiting for mouseup...';
document.addEventListener('mouseup', changeValue);
}
});
I also tried leveraging ng-mouseup
and ng-mouseleave
like this:
HTML:
<div ng-app="MyApp" ng-controller="AppCtrl">
<div>While clicking the button, move the mouse off of the button and release the mouse button. Note that 'done!' is not rendered, despite 'mouseup' being logged to the console</div>
<button ng-mousedown='mouseDown()' ng-mouseup="mouseUp()" ng-mouseleave="mouseLeave()">Click Me</button>
<div>{{value}}</div>
</div>
JS:
angular
.module('MyApp', [])
.controller('AppCtrl', function($scope) {
$scope.mouseDown = function() {
console.log('mousedown');
$scope.value = 'waiting for mouseup...';
}
$scope.mouseUp = function() {
console.log('mouseup');
$scope.value = 'done!';
}
$scope.mouseLeave = function() {
if ($scope.value && $scope.value.indexOf('waiting') > -1) {
console.log('mouseleave, binding mouseup');
$(document).one('mouseup', function() {
$scope.mouseUp();
});
}
}
});
The result is the same. $scope.mouseUp
is executed, but 'done!'
is never rendered. This creates additional problems with $scope.$watch
:
HTML:
<div ng-app="MyApp" ng-controller="AppCtrl">
<div>value never === 'done!' in $scope.$watch</div>
<button ng-mousedown='changeValue()'>Click Me</button>
<div>{{value}}</div>
</div>
JS:
angular
.module('MyApp', [])
.controller('AppCtrl', function($scope) {
$scope.changeValue = function() {
console.log('mousedown');
$scope.value = 'waiting for mouseup...';
$(document).one('mouseup', function() {
console.log('mouseup');
$scope.value = 'done!';
});
}
$scope.$watch('value', function(value) {
if (value === 'done!') {
console.log('value set to "done!"');
}
});
});
Have no idea why this question has been left without an answer for more than a month.
The main problem here is simple - the code that updates $scope.value
is executed outside of AngularJS. In order to make your data binding work you should wrap it with $scope.$apply
like this:
$scope.$apply(function () {
$scope.value = 'done!';
});
Note that ng-mouseup
works fine, you can see it when mouseup is triggered above the button. But if you move the mouse outside the button $(document).one('mouseup'..)
event will occur.
Just keep in mind that you must wrap all the external callbacks with $scope.$apply
(JQuery HTTP callbacks, DOM events binded not with ng-* directives, setTimeout
, etc.) if you need it to work with AngularJS binding or watchers. But think twice - calling $scope.$apply
inside another $scope.$apply
will cause an error.
You can look through the documentation or read more here.