Actual Behavior:
What is the issue? * When using angular-material in a hybrid application digest cycles are beeing triggered for every even, even a simple mouse move.What is the expected behavior? Digests should be triggered only when needed. The same behavior as in a regular AngularJs appCodePen (or steps to reproduce the issue): *
CodePen Demo which shows your issue: http://plnkr.co/edit/88OeubiJcFDFcHNd0On7Details: Open the attached plunk and watch the console as you move the mouse on the screen. You should see the word "digesting" being constantly printed whilst the mouse is moving.AngularJS Versions: *
AngularJS Version: 1.6.4AngularJS Material Version: 1.1.4Additional Information:
Browser Type: * allBrowser Version: * allOS: * allStack Traces: N/A // Listen to all events to cover all platforms.
var START_EVENTS = 'mousedown touchstart pointerdown';
var MOVE_EVENTS = 'mousemove touchmove pointermove';
var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
angular.element(document)
.on(START_EVENTS, gestureStart)
.on(MOVE_EVENTS, gestureMove)
.on(END_EVENTS, gestureEnd)
// For testing
.on('$$mdGestureReset', function gestureClearCache () {
lastPointer = pointer = null;
});
as a temporary workaround I'm overriding the gestures module, but this is clearly not ideal:
ng.module('material.core.gestures', [ ])
.value("$mdGesture",{register:()=>{}});
You will find these lines of code in the plunk as well, in the file main.ts. Uncoment them, and the digests will stop
Shortcut to create a new CodePen Demo.
Note: * indicates required information. Without this information, your issue may be auto-closed.
Do not modify the titles or questions. Simply add your responses to the ends of the questions.
Add more lines if needed.
I can confirm that we are experiencing the same thing with an AngularJS 1.5.5 / Angular 4.0.2 hybrid application, also confirming that overriding the gestures module makes the issue go away.
I've been doing some research on this today, it would appear it's related to this feature request for Angular: https://github.com/angular/angular/issues/16491, and this might not necessarily be an Angular Material issue but with the way ZoneJS handles AngularJS events by default.
Update
Also confirming that running those event handlers in the parent Zone I'm not seeing the $digests running every time. Since we don't use/rely on $mdGesture I can't determine if that breaks any existing functionality.
$window.Zone.current.parent.run(function() {
angular.element(document)
.on(START_EVENTS, gestureStart)
.on(MOVE_EVENTS, gestureMove)
.on(END_EVENTS, gestureEnd)
// For testing
.on('$$mdGestureReset', function gestureClearCache () {
lastPointer = pointer = null;
});
});
So I have a potential route to a fix for this which might be more flexible than individually fixing different libraries that bind to events to the document to avoid $digests. The intention here is to remove all of the event handlers from the AngularJS zone, then rebind them to the parent zone.
angular
.module('myApp')
.run(bindEventsToParentZone);
bindEventsToParentZone.$inject = ['$window'];
function bindEventsToParentZone($window) {
// grab existing events in this zone
var existingEventsObj = angular.copy(angular.element._data(angular.element(document)[0], 'events'));
// remove all events from this zone
$window.Zone.current.run(function() {
angular.element(document).off();
});
// bind all existing events to parent zone
angular.forEach(existingEventsObj, function(events, eventName) {
$window.Zone.current.parent.run(function() {
angular.forEach(events, function(event) {
angular.element(document).on(eventName, event);
});
});
});
}
I am not sure however whether angular.element(document).off() removes all event handlers from any zone, my assumption is that it will only be operating under its current zone. So I'll do some more research, but welcome any feedback to the above.
On second thought this is going to unbind all of the angular zone events, so likely you just have to go through and select the specific events that you want to unbind then bind them to the parent zone. So don't do the above :)
Well just going to update with the solution that appears to be working. I basically did what I mentioned above, but now also rebinding any events that I don't want in the parent zone back to the current zone.
bindEventsToParentZone.$inject = ['$window'];
function bindEventsToParentZone($window) {
// grab a copy of existing events in this zone
var existingEventsObj = angular.copy(angular.element._data(angular.element(document)[0], 'events'));
angular.element(document).off();
angular.forEach(existingEventsObj, function(eventsForEventName, eventName) {
angular.forEach(eventsForEventName, function(event) {
if (_isEventToRunoutsideZone(event)) {
$window.Zone.current.parent.run(function() {
angular.element(document).on(eventName, event);
});
}
else {
// bind all other events back to current zone (where they were)
$window.Zone.current.run(function() {
angular.element(document).on(eventName, event);
});
}
});
});
}
function _isEventToRunoutsideZone(event) {
var handlerFunctionName = event.handler.name;
var namespace = event.namespace;
var handlerFunctionNamesToRunOutsideZone = [
'gestureStart',
'gestureMove',
'gestureEnd',
'gestureClearCache'
];
var namespacePrefixesToRunOutsideZone = new RegExp(
'bs\\.button|' +
'bs\\.carousel|' +
'bs\\.collapse|' +
'bs\\.data\\-api'
);
return (
handlerFunctionNamesToRunOutsideZone.indexOf(handlerFunctionName) !== -1 ||
namespacePrefixesToRunOutsideZone.test(namespace)
);
}
In AngularJS 1.1.9 we added a new feature that allows disabling all gestures via:
app
.module('myApp',['ngMaterial'])
.config(['$mdGestureProvider', function($mdGestureProvider) {
// Useful for environments where swipe and touch events are not important.
$mdGestureProvider.disableAll();
}])
That said, I tried the above Plunker, which seems to use the latest released versions of most of the libraries including Angular and AngularJS Material, and I don't see the console log messages about the digests on mouse move.
I'm going to close this for now, but if you are still seeing this issue, please let me know.
@Splaktar, thanks for the update. The plunker was referencing the latest version of all libraries. At the time when the issue was opened, with the versions mentioned, the behavior was exactly as described. I'm happy to see that this was resolved and now we are no longer required to use any workarounds.
I'll inform my teammates about the good news
@andrei-ivanovici you may also want to look at ngUpgradeLite which has much better performance with AngularJS Material and Angular v5+. I created a StackBlitz demo for it and started a thread on it here: https://groups.google.com/forum/?pli=1#!topic/ngmaterial/9dgqZG2e13o. You probably don't need to disable gestures if using ngUpgradeLite.
Most helpful comment
Well just going to update with the solution that appears to be working. I basically did what I mentioned above, but now also rebinding any events that I don't want in the parent zone back to the current zone.