Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

chore(datepicker): Unregister parent watchers on $destroy #5260

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 43 additions & 31 deletions src/datepicker/datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
var self = this,
ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
ngModelOptions = {};
ngModelOptions = {},
watchListeners = [];

// Modes chain
this.modes = ['day', 'month', 'year'];
Expand All @@ -44,24 +45,24 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
// Watchable date attributes
angular.forEach(['minDate', 'maxDate'], function(key) {
if ($attrs[key]) {
$scope.$parent.$watch($attrs[key], function(value) {
watchListeners.push($scope.$parent.$watch($attrs[key], function(value) {
self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null;
self.refreshView();
});
}));
} else {
self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
}
});

angular.forEach(['minMode', 'maxMode'], function(key) {
if ($attrs[key]) {
$scope.$parent.$watch($attrs[key], function(value) {
watchListeners.push($scope.$parent.$watch($attrs[key], function(value) {
self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
$scope.datepickerMode = self[key];
}
});
}));
} else {
self[key] = $scope[key] = datepickerConfig[key] || null;
}
Expand All @@ -72,22 +73,22 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst

if (angular.isDefined($attrs.initDate)) {
this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
$scope.$parent.$watch($attrs.initDate, function(initDate) {
watchListeners.push($scope.$parent.$watch($attrs.initDate, function(initDate) {
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
self.refreshView();
}
});
}));
} else {
this.activeDate = new Date();
}

$scope.disabled = angular.isDefined($attrs.disabled) || false;
if (angular.isDefined($attrs.ngDisabled)) {
$scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
$scope.disabled = disabled;
self.refreshView();
});
}));
}

$scope.isActive = function(dateObject) {
Expand Down Expand Up @@ -248,6 +249,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
self.refreshView();
}
};

$scope.$on("$destroy", function() {
//Clear all watch listeners on destroy
while (watchListeners.length) {
watchListeners.shift()();
}
});
}])

.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
Expand Down Expand Up @@ -573,12 +581,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst

.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
var self = this;
var cache = {},
isHtml5DateInput = false;
isHtml5DateInput = false;
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
ngModel, ngModelOptions, $popup, altInputFormats;
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];

scope.watchData = {};

Expand All @@ -600,17 +607,17 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
} else {
dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
// Invalidate the $modelValue to ensure that formatters re-run
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
if (newDateFormat !== dateFormat) {
dateFormat = newDateFormat;
ngModel.$modelValue = null;

if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
// Invalidate the $modelValue to ensure that formatters re-run
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
if (newDateFormat !== dateFormat) {
dateFormat = newDateFormat;
ngModel.$modelValue = null;

if (!dateFormat) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix spacing, damn ide

throw new Error('uibDatepickerPopup must have a date format specified.');
}
}
});
}

Expand Down Expand Up @@ -658,9 +665,9 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi

angular.forEach(['minMode', 'maxMode'], function(key) {
if (attrs[key]) {
scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
watchListeners.push(scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
scope.watchData[key] = value;
});
}));
datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
}
});
Expand Down Expand Up @@ -692,13 +699,13 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
if (attrs[key]) {
var getAttribute = $parse(attrs[key]);

scope.$parent.$watch(getAttribute, function(value) {
watchListeners.push(scope.$parent.$watch(getAttribute, function(value) {
if (key === 'minDate' || key === 'maxDate') {
cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium'));
}

scope.watchData[key] = cache[key] || dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
});
}));

datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
}
Expand Down Expand Up @@ -730,7 +737,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
}
scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
dateFormat = dateFormat.replace(/M!/, 'MM')
.replace(/d!/, 'dd');
.replace(/d!/, 'dd');

return dateFilter(scope.date, dateFormat);
});
Expand Down Expand Up @@ -770,6 +777,11 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
$popup.remove();
element.unbind('keydown', inputKeydownBind);
$document.unbind('click', documentClickBind);

//Clear all watch listeners on destroy
while (watchListeners.length) {
watchListeners.shift()();
}
});
};

Expand All @@ -783,7 +795,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
}

return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 ||
scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
};

scope.compare = function(date1, date2) {
Expand Down Expand Up @@ -833,9 +845,9 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi

scope.disabled = angular.isDefined(attrs.disabled) || false;
if (attrs.ngDisabled) {
scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
watchListeners.push(scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
scope.disabled = disabled;
});
}));
}

scope.$watch('isOpen', function(value) {
Expand Down