function MdIconService(config, $templateRequest, $q, $log, $mdUtil, $sce) {
  var iconCache = {};
  var svgCache = {};
  var urlRegex = /[-\w@:%+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%+.~#?&//=]*)?/i;
  var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-=]*?(base64)?,(.*)$/i;

  Icon.prototype = {clone: cloneSVG, prepare: prepareAndStyle};
  getIcon.fontSet = findRegisteredFontSet;

  // Publish service...
  return getIcon;

  /**
   * Actual $mdIcon service is essentially a lookup function
   * @param {*} id $sce trust wrapper over a URL string, URL, icon registry id, or icon set id
   * @returns {angular.$q.Promise}
   */
  function getIcon(id) {
    id = id || '';

    // If the "id" provided is not a string, the only other valid value is a $sce trust wrapper
    // over a URL string. If the value is not trusted, this will intentionally throw an error
    // because the user is attempted to use an unsafe URL, potentially opening themselves up
    // to an XSS attack.
    if (!angular.isString(id)) {
      id = $sce.getTrustedUrl(id);
    }

    // If already loaded and cached, use a clone of the cached icon.
    // Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.

    if (iconCache[id]) {
      return $q.when(transformClone(iconCache[id]));
    }

    if (urlRegex.test(id) || dataUrlRegex.test(id)) {
      return loadByURL(id).then(cacheIcon(id));
    }

    if (id.indexOf(':') === -1) {
      id = '$default:' + id;
    }

    var load = config[id] ? loadByID : loadFromIconSet;
    return load(id)
      .then(cacheIcon(id));
  }

  /**
   * Lookup a registered fontSet style using its alias.
   * @param {string} alias used to lookup the alias in the array of fontSets
   * @returns {*} matching fontSet or the defaultFontSet if that alias does not match
   */
  function findRegisteredFontSet(alias) {
    var useDefault = angular.isUndefined(alias) || !(alias && alias.length);
    if (useDefault) {
      return config.defaultFontSet;
    }

    var result = alias;
    angular.forEach(config.fontSets, function(fontSet) {
      if (fontSet.alias === alias) {
        result = fontSet.fontSet || result;
      }
    });

    return result;
  }

  /**
   * @param {!Icon} cacheElement cached icon from the iconCache
   * @returns {Icon} cloned Icon element with unique ids
   */
  function transformClone(cacheElement) {
    var clone = cacheElement.clone();
    var newUid = $mdUtil.nextUid();
    var cacheSuffix, svgUrlQuerySelector, i, xlinkHrefValue;
    // These are SVG attributes that can reference element ids.
    var svgUrlAttributes = [
      'clip-path', 'color-profile', 'cursor', 'fill', 'filter', 'href', 'marker-start',
      'marker-mid', 'marker-end', 'mask', 'stroke', 'style', 'vector-effect'
    ];
    var isIeSvg = clone.innerHTML === undefined;

    // Verify that the newUid only contains a number and not some XSS content.
    if (!isFinite(Number(newUid))) {
      throw new Error('Unsafe and unexpected non-number result from $mdUtil.nextUid().');
    }
    cacheSuffix = '_cache' + newUid;

    // For each cached icon, we need to modify the id attributes and references.
    // This is needed because SVG ids are treated as normal DOM ids and should not be duplicated on
    // the page.
    if (clone.id) {
      clone.id += cacheSuffix;
    }

    // Do as much as possible with querySelectorAll as it provides much greater performance
    // than RegEx against serialized DOM.
    angular.forEach(clone.querySelectorAll('[id]'), function(descendantElem) {
      svgUrlQuerySelector = '';
      for (i = 0; i < svgUrlAttributes.length; i++) {
        svgUrlQuerySelector += '[' + svgUrlAttributes[i] + '="url(#' + descendantElem.id + ')"]';
        if (i + 1 < svgUrlAttributes.length) {
          svgUrlQuerySelector += ', ';
        }
      }
      // Append the cacheSuffix to references of the element's id within url(#id) calls.
      angular.forEach(clone.querySelectorAll(svgUrlQuerySelector), function(refItem) {
        updateSvgIdReferences(descendantElem, refItem, isIeSvg, newUid);
      });
      // Handle usages of url(#id) in the SVG's stylesheets
      angular.forEach(clone.querySelectorAll('style'), function(refItem) {
        updateSvgIdReferences(descendantElem, refItem, isIeSvg, newUid);
      });

      // Update ids referenced by the deprecated (in SVG v2) xlink:href XML attribute. The now
      // preferred href attribute is handled above. However, this non-standard XML namespaced
      // attribute cannot be handled in the same way. Explanation of this query selector here:
      // https://stackoverflow.com/q/23034283/633107.
      angular.forEach(clone.querySelectorAll('[*|href]:not([href])'), function(refItem) {
        xlinkHrefValue = refItem.getAttribute('xlink:href');
        if (xlinkHrefValue) {
          xlinkHrefValue = xlinkHrefValue.replace("#" + descendantElem.id, "#" + descendantElem.id + cacheSuffix);
          refItem.setAttribute('xlink:href', xlinkHrefValue);
        }
      });

      descendantElem.id += cacheSuffix;
    });

    return clone;
  }

  /**
   * @param {Element} referencedElement element w/ id that needs to be updated
   * @param {Element} referencingElement element that references the original id
   * @param {boolean} isIeSvg true if we're dealing with an SVG in IE11, false otherwise
   * @param {string} newUid the cache id to add as part of the cache suffix
   */
  function updateSvgIdReferences(referencedElement, referencingElement, isIeSvg, newUid) {
    var svgElement, cacheSuffix;

    // Verify that the newUid only contains a number and not some XSS content.
    if (!isFinite(Number(newUid))) {
      throw new Error('Unsafe and unexpected non-number result for newUid.');
    }
    cacheSuffix = '_cache' + newUid;

    // outerHTML of SVG elements is not supported by IE11
    if (isIeSvg) {
      svgElement = $mdUtil.getOuterHTML(referencingElement);
      svgElement = svgElement.replace("url(#" + referencedElement.id + ")",
        "url(#" + referencedElement.id + cacheSuffix + ")");
      referencingElement.textContent = angular.element(svgElement)[0].innerHTML;
    } else {
      // This use of outerHTML should be safe from XSS attack since we are only injecting the
      // cacheSuffix with content from $mdUtil.nextUid which we verify is a finite number above.
      referencingElement.outerHTML = referencingElement.outerHTML.replace(
        "url(#" + referencedElement.id + ")",
        "url(#" + referencedElement.id + cacheSuffix + ")");
    }
  }

  /**
   * Prepare and cache the loaded icon for the specified `id`.
   * @param {string} id icon cache id
   * @returns {function(*=): *}
   */
  function cacheIcon(id) {

    return function updateCache(icon) {
      iconCache[id] = isIcon(icon) ? icon : new Icon(icon, config[id]);

      return transformClone(iconCache[id]);
    };
  }

  /**
   * Lookup the configuration in the registry, if !registered throw an error
   * otherwise load the icon [on-demand] using the registered URL.
   * @param {string} id icon registry id
   * @returns {angular.$q.Promise}
   */
  function loadByID(id) {
    var iconConfig = config[id];
    return loadByURL(iconConfig.url).then(function(icon) {
      return new Icon(icon, iconConfig);
    });
  }

  /**
   * Loads the file as XML and uses querySelector( <id> ) to find the desired node...
   * @param {string} id icon id in icon set
   * @returns {angular.$q.Promise}
   */
  function loadFromIconSet(id) {
    var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
    var iconSetConfig = config[setName];

    return !iconSetConfig ? announceIdNotFound(id) : loadByURL(iconSetConfig.url).then(extractFromSet);

    function extractFromSet(set) {
      var iconName = id.slice(id.lastIndexOf(':') + 1);
      var icon = set.querySelector('#' + iconName);
      return icon ? new Icon(icon, iconSetConfig) : announceIdNotFound(id);
    }

    function announceIdNotFound(id) {
      var msg = 'icon ' + id + ' not found';
      $log.warn(msg);

      return $q.reject(msg || id);
    }
  }

  /**
   * Load the icon by URL (may use the $templateCache).
   * Extract the data for later conversion to Icon
   * @param {string} url icon URL
   * @returns {angular.$q.Promise}
   */
  function loadByURL(url) {
    /* Load the icon from embedded data URL. */
    function loadByDataUrl(url) {
      var results = dataUrlRegex.exec(url);
      var isBase64 = /base64/i.test(url);
      var data = isBase64 ? window.atob(results[2]) : results[2];

      return $q.when(angular.element(data)[0]);
    }

    /* Load the icon by URL using HTTP. */
    function loadByHttpUrl(url) {
      return $q(function(resolve, reject) {
        // Catch HTTP or generic errors not related to incorrect icon IDs.
        var announceAndReject = function(err) {
            var msg = angular.isString(err) ? err : (err.message || err.data || err.statusText);
            $log.warn(msg);
            reject(err);
          },
          extractSvg = function(response) {
            if (!svgCache[url]) {
              svgCache[url] = angular.element('<div>').append(response)[0].querySelector('svg');
            }
            resolve(svgCache[url]);
          };

        $templateRequest(url, true).then(extractSvg, announceAndReject);
      });
    }

    return dataUrlRegex.test(url)
      ? loadByDataUrl(url)
      : loadByHttpUrl(url);
  }

  /**
   * Check target signature to see if it is an Icon instance.
   * @param {Icon|Element} target
   * @returns {boolean} true if the specified target is an Icon object, false otherwise.
   */
  function isIcon(target) {
    return angular.isDefined(target.element) && angular.isDefined(target.config);
  }

  /**
   * Define the Icon class
   * @param {Element} el
   * @param {=ConfigurationItem} config
   * @constructor
   */
  function Icon(el, config) {
    // If the node is a <symbol>, it won't be rendered so we have to convert it into <svg>.
    if (el && el.tagName.toLowerCase() === 'symbol') {
      var viewbox = el.getAttribute('viewBox');
      // // Check if innerHTML is supported as IE11 does not support innerHTML on SVG elements.
      if (el.innerHTML) {
        el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">')
          .html(el.innerHTML)[0];
      } else {
        el = angular.element('<svg xmlns="http://www.w3.org/2000/svg">')
          .append($mdUtil.getInnerHTML(el))[0];
      }
      if (viewbox) el.setAttribute('viewBox', viewbox);
    }

    if (el && el.tagName.toLowerCase() !== 'svg') {
      el = angular.element(
        '<svg xmlns="http://www.w3.org/2000/svg">').append(el.cloneNode(true))[0];
    }

    // Inject the namespace if not available...
    if (!el.getAttribute('xmlns')) {
      el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
    }

    this.element = el;
    this.config = config;
    this.prepare();
  }

  /**
   *  Prepare the DOM element that will be cached in the
   *  loaded iconCache store.
   */
  function prepareAndStyle() {
    var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
    angular.forEach({
      'fit': '',
      'height': '100%',
      'width': '100%',
      'preserveAspectRatio': 'xMidYMid meet',
      'viewBox': this.element.getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize),
      'focusable': false // Disable IE11s default behavior to make SVGs focusable
    }, function(val, attr) {
      this.element.setAttribute(attr, val);
    }, this);
  }

  /**
   * Clone the Icon DOM element.
   */
  function cloneSVG() {
    // If the element or any of its children have a style attribute, then a CSP policy without
    // 'unsafe-inline' in the style-src directive, will result in a violation.
    return this.element.cloneNode(true);
  }

}
@ngdoc service
@name $mdIcon
@module material.components.icon

@description
The `$mdIcon` service is a function used to lookup SVG icons.

@param {string} id Query value for a unique Id or URL. If the argument is a URL, then the service will retrieve the icon element
from its internal cache or load the icon and cache it first. If the value is not a URL-type string, then an ID lookup is
performed. The Id may be a unique icon ID or may include an iconSet ID prefix.

For the **id** query to work properly, this means that all id-to-URL mappings must have been previously configured
using the `$mdIconProvider`.

@returns {angular.$q.Promise} A promise that gets resolved to a clone of the initial SVG DOM element; which was
created from the SVG markup in the SVG data file. If an error occurs (e.g. the icon cannot be found) the promise
will get rejected.

@usage
<hljs lang="js">
function SomeDirective($mdIcon) {

  // See if the icon has already been loaded, if not then lookup the icon from the
  // registry cache, load and cache it for future requests.
  // NOTE: Non-URL queries require configuration with $mdIconProvider.
  $mdIcon('android').then(function(iconEl)    { element.append(iconEl); });
  $mdIcon('work:chair').then(function(iconEl) { element.append(iconEl); });

  // Load and cache the external SVG using a URL.
  $mdIcon('img/icons/android.svg').then(function(iconEl) {
    element.append(iconEl);
  });
};
</hljs>

> <b>Note:</b> The `<md-icon>` directive internally uses the `$mdIcon` service to query, load,
  and instantiate SVG DOM elements.