Material: Async lookup for sidenav instance logs an error in the console (when it should just wait until the instance is available).

Created on 2 May 2016  路  6Comments  路  Source: angular/material

From the docs:
https://material.angularjs.org/latest/api/service/$mdSidenav

// Async lookup for sidenav instance; will resolve when the instance is available
$mdSidenav(componentId).then(function(instance) {
$log.debug( componentId + "is now ready" );
});

However, when I do that, I get an error in the logs:

angular.js:13550 SideNav 'left' is not available! Did you use md-component-id='left'?
(anonymous function) @ angular.js:13550findInstance @ angular-material.js:15747(anonymous function) @ angular-material.js:15712(anonymous function) @ index.dev.html:520invoke @ angular.js:4665(anonymous function) @ angular.js:4473forEach @ angular.js:322createInjector @ angular.js:4473doBootstrap @ angular.js:1746bootstrap @ angular.js:1767gamingPlatformInitFinished @ index.dev.html:530(anonymous function) @ app-gameinvite-common-code.ts:258trigger @ angular.js:3166defaultHandlerWrapper @ angular.js:3456eventHandler @ angular.js:3444

Even when passing true, it still logs the error:
$mdSidenav('left', true);

Except the error logs, things work correctly: the promise is resolved correctly :)
The problem is that it spams the error logs (and i have protractor tests that make sure the error log is empty).

The problem is in this code:

/**
   * Service API that supports three (3) usages:
   *   $mdSidenav().find("left")                       // sync (must already exist) or returns undefined
   *   $mdSidenav("left").toggle();                    // sync (must already exist) or returns reject promise;
   *   $mdSidenav("left",true).then( function(left){   // async returns instance when available
   *    left.toggle();
   *   });
   */
  return function(handle, enableWait) {
    if ( angular.isUndefined(handle) ) return service;

    var instance = service.find(handle); // THIS IS THE BUG: if enableWait is true, then it should use waitForInstance (and not findInstance).


Because findInstance logs an error if the instance is not ready:
function findInstance(handle) {
      var instance = $mdComponentRegistry.get(handle);
      if(!instance) {
        // Report missing instance
        $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );

See:

   var service = {
        find    : findInstance,     //  sync  - returns proxy API
        waitFor : waitForInstance   //  async - returns promise
      };
Pull Request bug

Most helpful comment

BTW, the documentation is buggy as it should mention the boolean argument (true):
$mdSidenav(componentId, true).then(function(instance) {

The workaround I found to avoid the log.error is to use [the undocumented] $mdComponentRegistry as follows:

$mdComponentRegistry.when('left').then(function() {
  // Now you can use $mdSidenav('left') or $mdSidenav('left', true) without getting an error.
  $mdSidenav('left').toggle();
})

All 6 comments

BTW, the documentation is buggy as it should mention the boolean argument (true):
$mdSidenav(componentId, true).then(function(instance) {

The workaround I found to avoid the log.error is to use [the undocumented] $mdComponentRegistry as follows:

$mdComponentRegistry.when('left').then(function() {
  // Now you can use $mdSidenav('left') or $mdSidenav('left', true) without getting an error.
  $mdSidenav('left').toggle();
})

This happens because firing the $mdSidenav immediately in your controller is earlier than the directive controller which registers it with the $mdComponentRegistry. I'll try to see if there's a workaround on Material's side, but in your case wrapping it in a $timeout should do the trick.

I initially tried $timeout, but a low timeout (say even 200 milliseconds) wasn't enough to solve it --- maybe my macbook pro is slow :D

Then I found a better workaround using:
$mdComponentRegistry.when('left')

I believe this is what material should do:

return function(handle, enableWait) {
if ( angular.isUndefined(handle) ) return service;

if (enableWait) return waitForInstance(handle); // which calls $mdComponentRegistry.when(handle);

Simple :)

Yeah, passing in true does the same behind the scenes. We shouldn't throw errors in that case though.

The current code doesn't do the same because it first tries to find the instance (which logs an error).
Note that it doesn't throw an error (just console.error):
$log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );

Again, I believe the fix is a one line change:

Current code:
return function(handle, enableWait) {
if ( angular.isUndefined(handle) ) return service;

var instance = service.find(handle);
return  !instance && (enableWait === true) ? service.waitFor(handle) :
        !instance && angular.isUndefined(enableWait) ? addLegacyAPI(service, handle) : instance;

};

New code:

return function(handle, enableWait) {
if ( angular.isUndefined(handle) ) return service;
if (enableWait) return waitForInstance(handle); // NEW LINE
var instance = service.find(handle);
// I also simplified the line below
return !instance && angular.isUndefined(enableWait) ? addLegacyAPI(service, handle) : instance;
};

I have the same issue, with the workarround of @yoav-zibin the sidernav works well, but in my case if you navigate to another route and go back the sidernav stop working without any warning or error log, I fixed that destroying the sidernav on scope destroy event.

It's strange, but I really don't sure why this works.

var sidenavPromise = $mdComponentRegistry.when('sidenav');
var sidenavInstance;

sidenavPromise.then(function() {
      if(sidenavInstance){
        sidenavInstance.destroy();
      }
      sidenavInstance= $mdSidenav("sidenav");
      $scope.$on("$destroy", sidenavInstance.destroy);
     //Sidenav logic
});
Was this page helpful?
0 / 5 - 0 ratings