service = function (options) {
        if (!isStringValid(options.key)) {
          throw new TypeError('Unable to load data, a key is not a non-empty string.');
        }

        if (!isStringValid(options.urlTemplate) && !angular.isFunction(options.urlTemplate)) {
          throw new TypeError('Unable to load data, a urlTemplate is not a non-empty string or not a function.');
        }

        var errorHandler = options.loadFailureHandler;
        if (errorHandler !== undefined) {
          if (!angular.isString(errorHandler)) {
            throw new Error('Unable to load data, a loadFailureHandler is not a string.');
          } else {
            errorHandler = $injector.get(errorHandler);
          }
        }

        var loaders = [],
          prioritizedParts = getPrioritizedParts();

        angular.forEach(prioritizedParts, function (part) {
          loaders.push(
            part.getTable(options.key, $q, $http, options.$http, options.urlTemplate, errorHandler)
          );
          part.urlTemplate = part.urlTemplate || options.urlTemplate;
        });

        // workaround for #1781
        var structureHasBeenChangedWhileLoading = false;
        var dirtyCheckEventCloser = $rootScope.$on('$translatePartialLoaderStructureChanged', function () {
          structureHasBeenChangedWhileLoading = true;
        });

        return $q.all(loaders)
          .then(function () {
            dirtyCheckEventCloser();
            if (structureHasBeenChangedWhileLoading) {
              if (!options.__retries) {
                // the part structure has been changed while loading (the origin ones)
                // this can happen if an addPart/removePart has been invoked right after a $translate.use(lang)
                // TODO maybe we can optimize this with the actual list of missing parts
                options.__retries = (options.__retries || 0) + 1;
                return service(options);
              } else {
                // the part structure has been changed again while loading (retried one)
                // because this could an infinite loop, this will not load another one again
                $log.warn('The partial loader has detected a multiple structure change (with addPort/removePart) ' +
                  'while loading translations. You should consider using promises of $translate.use(lang) and ' +
                  '$translate.refresh(). Also parts should be added/removed right before an explicit refresh ' +
                  'if possible.');
              }
            }
            var table = {};
            prioritizedParts = getPrioritizedParts();
            angular.forEach(prioritizedParts, function (part) {
              deepExtend(table, part.tables[options.key]);
            });
            return table;
          }, function () {
            dirtyCheckEventCloser();
            return $q.reject(options.key);
          });
      }
@ngdoc event
@name pascalprecht.translate.$translatePartialLoader#$translatePartialLoaderStructureChanged
@eventOf pascalprecht.translate.$translatePartialLoader
@eventType broadcast on root scope

@description
A $translatePartialLoaderStructureChanged event is called when a state of the loader was
changed somehow. It could mean either some part is added or some part is deleted. Anyway when
you get this event the translation table is not longer current and has to be updated.

@param {string} name A name of the part which is a reason why the event was fired