Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 342ee53

Browse files
devversionjelbourn
authored andcommitted
chore(components): use $onInit hook for binding assignments (#9973)
A few components are using the `bindToController` option for the scope bindings. Recently a Breaking Change commit landed in the AngularJS master branch, which changed the way the bindings are assigned. The bindings are no longer pre-assigned in the constructors, and will be only set before the `$onInit` lifecycle hook. Angular Material supports Angular versions which do not support lifecycle hooks and so we can't ensure that Snapshot and the older versions work together with `$onInit` approach. --- * Adds the $onInit hook to all affected components and manually dispatches the $onInit hook for AngularJS 1.4 and older versions. * Fixes two checks in the `datepicker`, which required the controller to have a binding set after $onInit (unnecessary here)
1 parent 47e4c1b commit 342ee53

File tree

6 files changed

+130
-67
lines changed

6 files changed

+130
-67
lines changed

src/components/datepicker/js/calendar.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,15 @@
111111
/** @final */
112112
this.$$rAF = $$rAF;
113113

114+
/** @final */
115+
this.$mdDateLocale = $mdDateLocale;
116+
114117
/** @final {Date} */
115118
this.today = this.dateUtil.createDateAtMidnight();
116119

117120
/** @type {!angular.NgModelController} */
118121
this.ngModelCtrl = null;
119122

120-
/**
121-
* The currently visible calendar view. Note the prefix on the scope value,
122-
* which is necessary, because the datepicker seems to reset the real one value if the
123-
* calendar is open, but the value on the datepicker's scope is empty.
124-
* @type {String}
125-
*/
126-
this.currentView = this._currentView || 'month';
127-
128123
/** @type {String} Class applied to the selected date cell. */
129124
this.SELECTED_DATE_CLASS = 'md-calendar-selected-date';
130125

@@ -219,18 +214,42 @@
219214
handleKeyElement.off('keydown', boundKeyHandler);
220215
});
221216

222-
if (this.minDate && this.minDate > $mdDateLocale.firstRenderableDate) {
217+
// For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
218+
// manually call the $onInit hook.
219+
if (angular.version.major === 1 && angular.version.minor <= 4) {
220+
this.$onInit();
221+
}
222+
223+
}
224+
225+
/**
226+
* Angular Lifecycle hook for newer Angular versions.
227+
* Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
228+
*/
229+
CalendarCtrl.prototype.$onInit = function() {
230+
231+
/**
232+
* The currently visible calendar view. Note the prefix on the scope value,
233+
* which is necessary, because the datepicker seems to reset the real one value if the
234+
* calendar is open, but the value on the datepicker's scope is empty.
235+
* @type {String}
236+
*/
237+
this.currentView = this._currentView || 'month';
238+
239+
var dateLocale = this.$mdDateLocale;
240+
241+
if (this.minDate && this.minDate > dateLocale.firstRenderableDate) {
223242
this.firstRenderableDate = this.minDate;
224243
} else {
225-
this.firstRenderableDate = $mdDateLocale.firstRenderableDate;
244+
this.firstRenderableDate = dateLocale.firstRenderableDate;
226245
}
227246

228-
if (this.maxDate && this.maxDate < $mdDateLocale.lastRenderableDate) {
247+
if (this.maxDate && this.maxDate < dateLocale.lastRenderableDate) {
229248
this.lastRenderableDate = this.maxDate;
230249
} else {
231-
this.lastRenderableDate = $mdDateLocale.lastRenderableDate;
250+
this.lastRenderableDate = dateLocale.lastRenderableDate;
232251
}
233-
}
252+
};
234253

235254
/**
236255
* Sets up the controller's reference to ngModelController.

src/components/datepicker/js/calendarMonthBody.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
// of repeated items that are linked, and then those elements have their bindings updated.
3434
// Since the months are not generated by bindings, we simply regenerate the entire thing
3535
// when the binding (offset) changes.
36-
scope.$watch(function() { return monthBodyCtrl.offset; }, function(offset, oldOffset) {
37-
if (offset !== oldOffset) {
36+
scope.$watch(function() { return monthBodyCtrl.offset; }, function(offset) {
37+
if (angular.isNumber(offset)) {
3838
monthBodyCtrl.generateContent();
3939
}
4040
});

src/components/datepicker/js/calendarYearBody.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
yearBodyCtrl.calendarCtrl = calendarCtrl;
2424
yearBodyCtrl.yearCtrl = yearCtrl;
2525

26-
scope.$watch(function() { return yearBodyCtrl.offset; }, function(offset, oldOffset) {
27-
if (offset !== oldOffset) {
26+
scope.$watch(function() { return yearBodyCtrl.offset; }, function(offset) {
27+
if (angular.isNumber(offset)) {
2828
yearBodyCtrl.generateContent();
2929
}
3030
});

src/components/datepicker/js/datepickerDirective.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,8 @@
256256
/** @final */
257257
this.$$rAF = $$rAF;
258258

259-
/**
260-
* Holds locale-specific formatters, parsers, labels etc. Allows
261-
* the user to override specific ones from the $mdDateLocale provider.
262-
* @type {!Object}
263-
*/
264-
this.locale = this.dateLocale ? angular.extend({}, $mdDateLocale, this.dateLocale) : $mdDateLocale;
259+
/** @final */
260+
this.$mdDateLocale = $mdDateLocale;
265261

266262
/**
267263
* The root document element. This is used for attaching a top-level click handler to
@@ -376,10 +372,6 @@
376372
$mdTheming($element);
377373
$mdTheming(angular.element(this.calendarPane));
378374

379-
this.installPropertyInterceptors();
380-
this.attachChangeListeners();
381-
this.attachInteractionListeners();
382-
383375
var self = this;
384376

385377
$scope.$on('$destroy', function() {
@@ -397,8 +389,33 @@
397389
}
398390
});
399391
}
392+
393+
// For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
394+
// manually call the $onInit hook.
395+
if (angular.version.major === 1 && angular.version.minor <= 4) {
396+
this.$onInit();
397+
}
398+
400399
}
401400

401+
/**
402+
* Angular Lifecycle hook for newer Angular versions.
403+
* Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
404+
*/
405+
DatePickerCtrl.prototype.$onInit = function() {
406+
407+
/**
408+
* Holds locale-specific formatters, parsers, labels etc. Allows
409+
* the user to override specific ones from the $mdDateLocale provider.
410+
* @type {!Object}
411+
*/
412+
this.locale = this.dateLocale ? angular.extend({}, this.$mdDateLocale, this.dateLocale) : this.$mdDateLocale;
413+
414+
this.installPropertyInterceptors();
415+
this.attachChangeListeners();
416+
this.attachInteractionListeners();
417+
};
418+
402419
/**
403420
* Sets up the controller's reference to ngModelController and
404421
* applies Angular's `input[type="date"]` directive.

src/components/fabSpeedDial/fabController.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) {
88
var vm = this;
9+
var initialAnimationAttempts = 0;
910

1011
// NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops
1112

@@ -26,12 +27,23 @@
2627
$scope.$evalAsync("vm.isOpen = !vm.isOpen");
2728
};
2829

29-
setupDefaults();
30-
setupListeners();
31-
setupWatchers();
30+
/*
31+
* Angular Lifecycle hook for newer Angular versions.
32+
* Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
33+
*/
34+
vm.$onInit = function() {
35+
setupDefaults();
36+
setupListeners();
37+
setupWatchers();
3238

33-
var initialAnimationAttempts = 0;
34-
fireInitialAnimations();
39+
fireInitialAnimations();
40+
};
41+
42+
// For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
43+
// manually call the $onInit hook.
44+
if (angular.version.major === 1 && angular.version.minor <= 4) {
45+
this.$onInit();
46+
}
3547

3648
function setupDefaults() {
3749
// Set the default direction to 'down' if none is specified

src/components/tabs/js/tabsController.js

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,9 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
1515
destroyed = false,
1616
loaded = false;
1717

18-
// define one-way bindings
19-
defineOneWayBinding('stretchTabs', handleStretchTabs);
20-
21-
// define public properties with change handlers
22-
defineProperty('focusIndex', handleFocusIndexChange, ctrl.selectedIndex || 0);
23-
defineProperty('offsetLeft', handleOffsetChange, 0);
24-
defineProperty('hasContent', handleHasContent, false);
25-
defineProperty('maxTabWidth', handleMaxTabWidth, getMaxTabWidth());
26-
defineProperty('shouldPaginate', handleShouldPaginate, false);
27-
28-
// define boolean attributes
29-
defineBooleanAttribute('noInkBar', handleInkBar);
30-
defineBooleanAttribute('dynamicHeight', handleDynamicHeight);
31-
defineBooleanAttribute('noPagination');
32-
defineBooleanAttribute('swipeContent');
33-
defineBooleanAttribute('noDisconnect');
34-
defineBooleanAttribute('autoselect');
35-
defineBooleanAttribute('noSelectClick');
36-
defineBooleanAttribute('centerTabs', handleCenterTabs, false);
37-
defineBooleanAttribute('enableDisconnect');
38-
39-
// define public properties
40-
ctrl.scope = $scope;
41-
ctrl.parent = $scope.$parent;
42-
ctrl.tabs = [];
43-
ctrl.lastSelectedIndex = null;
44-
ctrl.hasFocus = false;
45-
ctrl.styleTabItemFocus = false;
46-
ctrl.shouldCenterTabs = shouldCenterTabs();
47-
ctrl.tabContentPrefix = 'tab-content-';
48-
49-
// define public methods
18+
19+
// Define public methods
20+
ctrl.$onInit = $onInit;
5021
ctrl.updatePagination = $mdUtil.debounce(updatePagination, 100);
5122
ctrl.redirectFocus = redirectFocus;
5223
ctrl.attachRipple = attachRipple;
@@ -66,12 +37,56 @@ function MdTabsController ($scope, $element, $window, $mdConstant, $mdTabInkRipp
6637
ctrl.updateTabOrder = $mdUtil.debounce(updateTabOrder, 100);
6738
ctrl.getFocusedTabId = getFocusedTabId;
6839

69-
init();
40+
// For Angular 1.4 and older, where there are no lifecycle hooks but bindings are pre-assigned,
41+
// manually call the $onInit hook.
42+
if (angular.version.major === 1 && angular.version.minor <= 4) {
43+
this.$onInit();
44+
}
45+
46+
/**
47+
* Angular Lifecycle hook for newer Angular versions.
48+
* Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
49+
*/
50+
function $onInit() {
51+
// Define one-way bindings
52+
defineOneWayBinding('stretchTabs', handleStretchTabs);
53+
54+
// Define public properties with change handlers
55+
defineProperty('focusIndex', handleFocusIndexChange, ctrl.selectedIndex || 0);
56+
defineProperty('offsetLeft', handleOffsetChange, 0);
57+
defineProperty('hasContent', handleHasContent, false);
58+
defineProperty('maxTabWidth', handleMaxTabWidth, getMaxTabWidth());
59+
defineProperty('shouldPaginate', handleShouldPaginate, false);
60+
61+
// Define boolean attributes
62+
defineBooleanAttribute('noInkBar', handleInkBar);
63+
defineBooleanAttribute('dynamicHeight', handleDynamicHeight);
64+
defineBooleanAttribute('noPagination');
65+
defineBooleanAttribute('swipeContent');
66+
defineBooleanAttribute('noDisconnect');
67+
defineBooleanAttribute('autoselect');
68+
defineBooleanAttribute('noSelectClick');
69+
defineBooleanAttribute('centerTabs', handleCenterTabs, false);
70+
defineBooleanAttribute('enableDisconnect');
71+
72+
// Define public properties
73+
ctrl.scope = $scope;
74+
ctrl.parent = $scope.$parent;
75+
ctrl.tabs = [];
76+
ctrl.lastSelectedIndex = null;
77+
ctrl.hasFocus = false;
78+
ctrl.styleTabItemFocus = false;
79+
ctrl.shouldCenterTabs = shouldCenterTabs();
80+
ctrl.tabContentPrefix = 'tab-content-';
81+
82+
// Setup the tabs controller after all bindings are available.
83+
setupTabsController();
84+
}
7085

7186
/**
72-
* Perform initialization for the controller, setup events and watcher(s)
87+
* Perform setup for the controller, setup events and watcher(s)
7388
*/
74-
function init () {
89+
function setupTabsController () {
7590
ctrl.selectedIndex = ctrl.selectedIndex || 0;
7691
compileTemplate();
7792
configureWatchers();

0 commit comments

Comments
 (0)