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

Commit 877551c

Browse files
fix(sidenav): mdSideNav should support deferred or instant component lookups
Previous API does not support any way to determine if the component exists (has been instantiated) since the response will give a "fake" instance reference. The current API supports `$mdSideNav().find(id)`, `$mdSideNav().waitFor(id)`, `$mdSideNav(id)` usages. This fix will allow code like this: ```js // If the component has already been instantiated $scope.isOpenRight = function(){ return $mdSidenav('right').isOpen(); }; var right; // If the component will be created later $scope.isOpenRight = function(){ if ( angular.isUndefined(right) ) { $mdSidenav() .waitFor('right') .then(function(instance){ right = instance; }) } return right ? right.isOpen() : false; }; ``` Closes #7900
1 parent 4bff2bb commit 877551c

File tree

4 files changed

+161
-100
lines changed

4 files changed

+161
-100
lines changed

src/components/sidenav/demoBasicUsage/script.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ angular
44
$scope.toggleLeft = buildDelayedToggler('left');
55
$scope.toggleRight = buildToggler('right');
66
$scope.isOpenRight = function(){
7-
return $mdSidenav('right').isOpen();
7+
var right = $mdSidenav('right');
8+
return right && right.isOpen();
89
};
910

1011
/**
@@ -31,6 +32,7 @@ angular
3132
*/
3233
function buildDelayedToggler(navID) {
3334
return debounce(function() {
35+
// Component lookup should always be available since we are not using `ng-if`
3436
$mdSidenav(navID)
3537
.toggle()
3638
.then(function () {
@@ -41,6 +43,7 @@ angular
4143

4244
function buildToggler(navID) {
4345
return function() {
46+
// Component lookup should always be available since we are not using `ng-if`
4447
$mdSidenav(navID)
4548
.toggle()
4649
.then(function () {
@@ -51,6 +54,7 @@ angular
5154
})
5255
.controller('LeftCtrl', function ($scope, $timeout, $mdSidenav, $log) {
5356
$scope.close = function () {
57+
// Component lookup should always be available since we are not using `ng-if`
5458
$mdSidenav('left').close()
5559
.then(function () {
5660
$log.debug("close LEFT is done");
@@ -60,6 +64,7 @@ angular
6064
})
6165
.controller('RightCtrl', function ($scope, $timeout, $mdSidenav, $log) {
6266
$scope.close = function () {
67+
// Component lookup should always be available since we are not using `ng-if`
6368
$mdSidenav('right').close()
6469
.then(function () {
6570
$log.debug("close RIGHT is done");

src/components/sidenav/sidenav.js

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -57,58 +57,52 @@ angular
5757
* $mdSidenav(componentId).isLockedOpen();
5858
* </hljs>
5959
*/
60-
function SidenavService($mdComponentRegistry, $q) {
61-
return function(handle) {
60+
function SidenavService($mdComponentRegistry, $mdUtil, $q, $log) {
61+
var errorMsg = "SideNav '{0}' is not available! Did you use md-component-id='{0}'?";
62+
var service = {
63+
find : findInstance, // sync - returns proxy API
64+
waitFor : waitForInstance // async - returns promise
65+
};
6266

63-
// Lookup the controller instance for the specified sidNav instance
64-
var self;
65-
var errorMsg = "SideNav '" + handle + "' is not available!";
66-
var instance = $mdComponentRegistry.get(handle);
67+
/**
68+
* Service API that supports three (3) usages:
69+
* $mdSidenav().find("left") // sync (must already exist) or returns undefined
70+
* $mdSidenav("left").toggle(); // sync (must already exist) or returns undefined; deprecated
71+
* $mdSidenav("left",true).then( function(left){ // async returns instance when available
72+
* left.toggle();
73+
* });
74+
*/
75+
return function(handle, enableWait) {
76+
if ( angular.isUndefined(handle) ) return service;
6777

68-
if(!instance) {
69-
$mdComponentRegistry.notFoundError(handle);
70-
}
78+
var instance = service.find(handle);
79+
return !instance && (enableWait === true) ? service.waitFor(handle) : instance;
80+
};
7181

72-
return self = {
73-
// -----------------
74-
// Sync methods
75-
// -----------------
76-
isOpen: function() {
77-
return instance && instance.isOpen();
78-
},
79-
isLockedOpen: function() {
80-
return instance && instance.isLockedOpen();
81-
},
82-
// -----------------
83-
// Async methods
84-
// -----------------
85-
toggle: function() {
86-
return instance ? instance.toggle() : $q.reject(errorMsg);
87-
},
88-
open: function() {
89-
return instance ? instance.open() : $q.reject(errorMsg);
90-
},
91-
close: function() {
92-
return instance ? instance.close() : $q.reject(errorMsg);
93-
},
94-
then : function( callbackFn ) {
95-
var promise = instance ? $q.when(instance) : waitForInstance();
96-
return promise.then( callbackFn || angular.noop );
82+
/**
83+
* Synchronously lookup the controller instance for the specified sidNav instance which has been
84+
* registered with the markup `md-component-id`
85+
*/
86+
function findInstance(handle) {
87+
var instance = $mdComponentRegistry.get(handle);
88+
if(!instance) {
89+
// Report missing instance
90+
$log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );
91+
92+
// The component has not registered itself... most like NOT yet created
93+
// return null to indicate that the Sidenav is not in the DOM
94+
return undefined;
9795
}
98-
};
96+
return instance;
97+
}
9998

10099
/**
100+
* Asynchronously wait for the component instantiation,
101101
* Deferred lookup of component instance using $component registry
102102
*/
103-
function waitForInstance() {
104-
return $mdComponentRegistry
105-
.when(handle)
106-
.then(function( it ){
107-
instance = it;
108-
return it;
109-
});
103+
function waitForInstance(handle) {
104+
return $mdComponentRegistry.when(handle);
110105
}
111-
};
112106
}
113107
/**
114108
* @ngdoc directive

src/components/sidenav/sidenav.spec.js

Lines changed: 116 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('mdSidenav', function() {
55
var el;
66
inject(function($compile, $rootScope) {
77
var parent = angular.element('<div>');
8-
el = angular.element('<md-sidenav ' + (attrs||'') + '>');
8+
el = angular.element('<md-sidenav ' + (attrs || '') + '>');
99
parent.append(el);
1010
$compile(parent)($rootScope);
1111
$rootScope.$apply();
@@ -79,7 +79,6 @@ describe('mdSidenav', function() {
7979
expect(backdrop.length).toBe(0);
8080
}));
8181

82-
8382
it('should focus sidenav on open', inject(function($rootScope, $material, $document) {
8483
jasmine.mockElementFocus(this);
8584
var el = setup('md-is-open="show"');
@@ -92,11 +91,11 @@ describe('mdSidenav', function() {
9291
it('should focus child with md-sidenav-focus', inject(function($rootScope, $material, $document, $compile) {
9392
jasmine.mockElementFocus(this);
9493
var parent = angular.element('<div>');
95-
var markup = '<md-sidenav md-is-open="show">'+
96-
'<md-input-container><label>Label</label>' +
97-
'<input type="text" md-sidenav-focus>' +
98-
'</md-input-container>' +
99-
'<md-sidenav>';
94+
var markup = '<md-sidenav md-is-open="show">' +
95+
' <md-input-container><label>Label</label>' +
96+
' <input type="text" md-sidenav-focus>' +
97+
' </md-input-container>' +
98+
'<md-sidenav>';
10099
var sidenavEl = angular.element(markup);
101100
parent.append(sidenavEl);
102101
$compile(parent)($rootScope);
@@ -110,11 +109,11 @@ describe('mdSidenav', function() {
110109
it('should focus child with md-autofocus', inject(function($rootScope, $material, $document, $compile) {
111110
jasmine.mockElementFocus(this);
112111
var parent = angular.element('<div>');
113-
var markup = '<md-sidenav md-is-open="show">'+
114-
'<md-input-container><label>Label</label>' +
115-
'<input type="text" md-autofocus>' +
116-
'</md-input-container>' +
117-
'<md-sidenav>';
112+
var markup = '<md-sidenav md-is-open="show">' +
113+
'<md-input-container><label>Label</label>' +
114+
'<input type="text" md-autofocus>' +
115+
'</md-input-container>' +
116+
'<md-sidenav>';
118117
var sidenavEl = angular.element(markup);
119118
parent.append(sidenavEl);
120119
$compile(parent)($rootScope);
@@ -125,16 +124,15 @@ describe('mdSidenav', function() {
125124
expect($document.activeElement).toBe(focusEl[0]);
126125
}));
127126

128-
129127
it('should focus on last md-sidenav-focus element', inject(function($rootScope, $material, $document, $compile) {
130128
jasmine.mockElementFocus(this);
131129
var parent = angular.element('<div>');
132-
var markup = '<md-sidenav md-is-open="show">'+
133-
'<md-button md-sidenav-focus>Button</md-button>'+
134-
'<md-input-container><label>Label</label>' +
135-
'<input type="text" md-sidenav-focus>' +
136-
'</md-input-container>' +
137-
'<md-sidenav>';
130+
var markup = '<md-sidenav md-is-open="show">' +
131+
'<md-button md-sidenav-focus>Button</md-button>' +
132+
'<md-input-container><label>Label</label>' +
133+
'<input type="text" md-sidenav-focus>' +
134+
'</md-input-container>' +
135+
'<md-sidenav>';
138136
var sidenavEl = angular.element(markup);
139137
parent.append(sidenavEl);
140138
$compile(parent)($rootScope);
@@ -208,22 +206,27 @@ describe('mdSidenav', function() {
208206
$material.flushInterimElement();
209207
}
210208

211-
beforeEach( inject(function(_$material_,_$rootScope_,_$timeout_) {
212-
$material = _$material_;
213-
$rootScope = _$rootScope_;
214-
$timeout = _$timeout_;
209+
beforeEach(inject(function(_$material_, _$rootScope_, _$timeout_) {
210+
$material = _$material_;
211+
$rootScope = _$rootScope_;
212+
$timeout = _$timeout_;
215213
}));
216214

217-
218-
it('should open(), close(), and toggle() with promises', function () {
215+
it('should open(), close(), and toggle() with promises', function() {
219216
var el = setup('');
220217
var scope = el.isolateScope();
221218
var controller = el.controller('mdSidenav');
222219

223220
var openDone = 0, closeDone = 0, toggleDone = 0;
224-
var onOpen = function() { openDone++; };
225-
var onClose = function() { closeDone++; };
226-
var onToggle = function() { toggleDone++; };
221+
var onOpen = function() {
222+
openDone++;
223+
};
224+
var onClose = function() {
225+
closeDone++;
226+
};
227+
var onToggle = function() {
228+
toggleDone++;
229+
};
227230

228231
controller
229232
.open()
@@ -253,14 +256,17 @@ describe('mdSidenav', function() {
253256
expect(scope.isOpen).toBe(true);
254257
});
255258

256-
257-
it('should open() to work multiple times before close()', function () {
259+
it('should open() to work multiple times before close()', function() {
258260
var el = setup('');
259261
var controller = el.controller('mdSidenav');
260262

261263
var openDone = 0, closeDone = 0;
262-
var onOpen = function() { openDone++; };
263-
var onClose = function() { closeDone++; };
264+
var onOpen = function() {
265+
openDone++;
266+
};
267+
var onClose = function() {
268+
closeDone++;
269+
};
264270

265271
controller
266272
.open()
@@ -288,12 +294,10 @@ describe('mdSidenav', function() {
288294
describe('$mdSidenav Service', function() {
289295
var $rootScope, $timeout;
290296

291-
292-
beforeEach( inject(function(_$rootScope_,_$timeout_) {
293-
$rootScope = _$rootScope_;
294-
$timeout = _$timeout_;
295-
}));
296-
297+
beforeEach(inject(function(_$rootScope_, _$timeout_) {
298+
$rootScope = _$rootScope_;
299+
$timeout = _$timeout_;
300+
}));
297301

298302
it('should grab instance', inject(function($mdSidenav) {
299303
var el = setup('md-component-id="left"');
@@ -344,27 +348,85 @@ describe('mdSidenav', function() {
344348
expect(instance.isLockedOpen()).toBe(true);
345349
}));
346350

347-
it('should find a deferred instantiation', inject(function($mdSidenav) {
348-
var instance;
351+
});
352+
353+
it('should find an instantiation using `$mdSidenav(id)`', inject(function($mdSidenav) {
354+
var el = setup('md-component-id="left"');
355+
$timeout.flush();
349356

350-
// Lookup deferred (not existing) instance
351-
$mdSidenav('left').then( function(inst) { instance = inst; });
352-
expect(instance).toBeUndefined();
357+
// Lookup instance still available in the component registry
358+
var instance = $mdSidenav('left');
359+
expect(instance).toBeTruthy();
360+
}));
353361

354-
// Instantiate `left` sidenav component
355-
var el = setup('md-component-id="left"');
362+
it('should find a deferred instantiation using `$mdSidenav(id, true)`', inject(function($mdSidenav) {
363+
var instance;
356364

357-
$timeout.flush();
358-
expect(instance).toBeTruthy();
359-
expect(instance.isOpen()).toBeFalsy();
365+
// Lookup deferred (not existing) instance
366+
$mdSidenav('left', true).then(function(inst) {
367+
instance = inst;
368+
});
369+
expect(instance).toBeUndefined();
360370

361-
// Lookup instance still available in the component registry
362-
instance = undefined;
363-
instance = $mdSidenav('left');
371+
// Instantiate `left` sidenav component
372+
var el = setup('md-component-id="left"');
373+
$timeout.flush();
364374

365-
expect(instance).toBeTruthy();
375+
expect(instance).toBeDefined();
376+
expect(instance.isOpen()).toBeFalsy();
366377

367-
}));
368-
});
378+
// Lookup instance still available in the component registry
379+
instance = $mdSidenav('left', true);
380+
expect(instance).toBeTruthy();
381+
}));
382+
383+
it('should find a deferred instantiation using `$mdSidenav().waitFor(id)` ', inject(function($mdSidenav) {
384+
var instance;
385+
386+
// Lookup deferred (not existing) instance
387+
$mdSidenav().waitFor('left').then(function(inst) {
388+
instance = inst;
389+
});
390+
expect(instance).toBeUndefined();
391+
392+
// Instantiate `left` sidenav component
393+
var el = setup('md-component-id="left"');
394+
$timeout.flush();
395+
396+
expect(instance).toBeDefined();
397+
expect(instance.isOpen()).toBeFalsy();
398+
399+
// Lookup instance still available in the component registry
400+
instance = undefined;
401+
instance = $mdSidenav('left');
402+
403+
expect(instance).toBeTruthy();
404+
}));
405+
406+
it('should not find a lazy instantiation without waiting `$mdSidenav(id)`', inject(function($mdSidenav) {
407+
var instance = $mdSidenav('left');
408+
expect(instance).toBeUndefined();
409+
410+
// Instantiate `left` sidenav component
411+
var el = setup('md-component-id="left"');
412+
$timeout.flush();
413+
414+
instance = $mdSidenav('left');
415+
expect(instance).toBeDefined();
416+
expect(instance.isOpen()).toBeFalsy();
417+
}));
418+
419+
it('should not find a lazy instantiation without waiting `$mdSidenav().find(id)`', inject(function($mdSidenav) {
420+
var instance = $mdSidenav().find('left');
421+
expect(instance).toBeUndefined();
422+
423+
// Instantiate `left` sidenav component
424+
var el = setup('md-component-id="left"');
425+
$timeout.flush();
426+
427+
instance = $mdSidenav().find('left');
428+
expect(instance).toBeDefined();
429+
expect(instance.isOpen()).toBeFalsy();
430+
}));
369431

370432
});

0 commit comments

Comments
 (0)