code: function (element, opts) {
        var TIMEOUT = 3000; // fallback is 3 secs
  
        return $q(function(resolve, reject){
          opts = opts || { };
  
          // If there is no transition is found, resolve immediately
          //
          // NOTE: using $mdUtil.nextTick() causes delays/issues
          if (noTransitionFound(opts.cachedTransitionStyles)) {
            TIMEOUT = 0;
          }
  
          var timer = $timeout(finished, opts.timeout || TIMEOUT);
          element.on($mdConstant.CSS.TRANSITIONEND, finished);
  
          /**
           * Upon timeout or transitionEnd, reject or resolve (respectively) this promise.
           * NOTE: Make sure this transitionEnd didn't bubble up from a child
           */
          function finished(ev) {
            if (ev && ev.target !== element[0]) return;
  
            if (ev) $timeout.cancel(timer);
            element.off($mdConstant.CSS.TRANSITIONEND, finished);
  
            // Never reject since ngAnimate may cause timeouts due missed transitionEnd events
            resolve();
  
          }
  
          /**
           * Checks whether or not there is a transition.
           *
           * @param styles The cached styles to use for the calculation. If null, getComputedStyle()
           * will be used.
           *
           * @returns {boolean} True if there is no transition/duration; false otherwise.
           */
          function noTransitionFound(styles) {
            styles = styles || window.getComputedStyle(element[0]);
  
            return styles.transitionDuration == '0s' || (!styles.transition && !styles.transitionProperty);
          }
  
        });
      }
docs: Listen for transitionEnd event (with optional timeout)
  Announce completion or failure via promise handlers