Angular Значение разрешения ui-router как строка

С ui-router я добавляю все логические решения в функцию состояния, например:

    //my-ctrl.js
    var MyCtrl = function($scope, customers) {
      $scope.customers = customers;
    }

    //routing.js
    $stateProvider.state('customers.show', {
      url: '/customers/:id',
      template: template,
      controller: 'MyCtrl',
      resolve: {   // <-- I feel this must define as like controller
        customers: function(Customer, $stateParams) {
          return Customer.get($stateParams.id);
        }
      }
    });

Однако объект IMO, resolve должен принадлежать контроллеру, и его легко читать и поддерживать, если он определен в файле контроллера.

    //my-ctrl.js
    var MyCtrl = function($scope, customers) {
      $scope.customers = customers;
    }
    MyCtrl.resolve = {
      customers: function(Customer, $stateParams) {
        return Customer.get($stateParams.id);
      };
    };

    //routing.js
    $stateProvider.state('customers.show', {
      url: '/customers/:id',
      template: template,
      controller: 'MyCtrl',
      resolve: 'MyCtrl.resolve'   //<--- Error: 'invocables' must be an object.
    });

Однако, когда я определяю его как MyCtrl.resolve, из-за IIFE, я получаю следующую ошибку.

Failed to instantiate module due to: ReferenceError: MyCtrl is not defined

Когда я определяю его как строку 'MyCtrl.resolve', я получаю этот

Error: 'invocables' must be an object.

Я вижу, что контроллер определен как строка, поэтому я думаю, что также можно указать значение как строку, используя декоратор или что-то в этом роде.

Кто-нибудь сделал такой подход? Так что я могу сохранить мои маршруты .js чистыми и помещать соответствующую информацию. в соответствующем файле?

Ответ 1

Это звучит как аккуратный способ создания решения, но я просто не думаю, что вы можете это сделать.

Помимо того факта, что "разрешение" требует объекта, оно определяется в фазе, где все, что у вас есть, есть поставщики. На данный момент контроллер еще не существует.

Однако еще хуже, что "разрешение" предназначено для определения входных данных для самого контроллера. Чтобы определить решение в контроллере, ожидайте, что он будет оценен до того, как будет создан контроллер, будет циклической зависимостью.

В прошлом я определил функции разрешения вне определения $stateProvider, по крайней мере, позволяя им повторно использовать. Я никогда не стремился к тому, чтобы стать более привлекательным.

var customerResolve = ['Customer', '$stateParams',
    function(Customer, $stateParams) {
        return Customer.get($stateParams.id);
    }
];

// ....

$stateProvider.state('customers.show', {
  url: '/customers/:id',
  template: template,
  controller: 'MyCtrl',
  resolve: {
    customers: customerResolve
  }
});

Ответ 2

Этот вопрос касается особенностей пакета ui-router. По умолчанию ui-router не поддерживает строки для параметра разрешения. Но если вы посмотрите на исходный код ui-router, вы увидите, что можно реализовать эту функциональность, не внося в нее прямых изменений.

Теперь я покажу логику предлагаемого метода и его реализацию

Анализ кода

Сначала рассмотрим функцию $state.transitionTo angular -ui-router/src/urlRouter.js. Внутри этой функции мы увидим этот код

  for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
    locals = toLocals[l] = inherit(locals);
    resolved = resolveState(state, toParams, state === to, resolved, locals, options);
  }

Очевидно, что здесь разрешены параметры разрешения для каждого родительского состояния. Затем рассмотрим функцию resolveState в том же файле. Мы найдем эту строку там:

dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
var promises = [dst.resolve.then(function (globals) {
    dst.globals = globals;
})];

Это специально, где promises для параметров решения извлекаются. Что хорошо для использования, функция, которая делает это, вынимается в отдельную службу. Это означает, что мы можем привязать и изменить поведение с помощью декоратора.

Для справки реализация $resolve находится в файле angular -ui-router/src/resolve.js

Реализация hook

Подпись для функции разрешения $resolve -

this.resolve = function (invocables, locals, parent, self) {

Где "invocables" - это объект из нашего декларирования состояния. Поэтому нам нужно проверить, является ли строка "invocables" строкой. И если это так, мы получим функцию контроллера по строке и вызовите функцию после ".". символ

//1.1 Main hook for $resolve
$provide.decorator('$resolve', ['$delegate', '$window', function ($delegate, $window){ 
  var service = $delegate; 



  var oldResolve = service.resolve;
  service.resolve = function(invocables, locals, parent, self){
     if (typeof(invocables) == 'string') {
       var resolveStrs = invocables.split('.');

       var controllerName = resolveStrs[0];
       var methodName = resolveStrs[1];

       //By default the $controller service saves controller functions on window objec
       var controllerFunc = $window[controllerName];
       var controllerResolveObj = controllerFunc[methodName]();

       return oldResolve.apply(this, [controllerResolveObj, locals, parent, self]);

     } else {
       return oldResolve.apply(this, [invocables, locals, parent, self]);
     }
  };

  return $delegate;
}]);

EDIT:

Вы также можете переопределить $controllerProvider с поставщиком следующим образом:

app.provider("$controller", function () {

}

Таким образом становится возможным добавить новую функцию getConstructor, которая вернет конструктор контроллера по имени. И поэтому вы избежите использования объекта $window в hook:

$provide.decorator('$resolve', ['$delegate', function ($delegate){ 
    var service = $delegate; 

    var oldResolve = service.resolve;
    service.resolve = function(invocables, locals, parent, self){
       if (typeof(invocables) == 'string') {
         var resolveStrs = invocables.split('.');

         var controllerName = resolveStrs[0];
         var methodName = resolveStrs[1];

         var controllerFunc = $controllerProvider.getConstructor(controllerName);
         var controllerResolveObj = controllerFunc[methodName]();

         return oldResolve.apply(this, [controllerResolveObj, locals, parent, self]);

       } else {
         return oldResolve.apply(this, [invocables, locals, parent, self]);
       }
    }; 

Полный код, демонстрирующий этот метод http://plnkr.co/edit/f3dCSLn14pkul7BzrMvH?p=preview

Ответ 3

Вам нужно убедиться, что контроллер находится в том же закрытии, что и конфигурация состояния. Это не означает, что они должны быть определены в одном файле.

Итак, вместо строки используйте статическое свойство контроллера:

resolve: MyCtrl.resolve,

Обновление

Затем для вашего файла контроллера:

var MyCtrl;
(function(MyCtrl, yourModule) {

    MyCtrl = function() { // your contructor function}
    MyCtrl.resolve = { // your resolve object }

    yourModule.controller('MyCtrl', MyCtrl);

})(MyCtrl, yourModule)

И затем, когда вы определяете свои состояния в другом файле, который включается или конкатенируется или требуется после файла контроллера:

(function(MyCtrl, yourModule) {

    configStates.$inject = ['$stateProvider'];
    function configStates($stateProvider) {

        // state config has access to MyCtrl.resolve
        $stateProvider.state('customers.show', {
            url: '/customers/:id',
            template: template,
            controller: 'MyCtrl',
            resolve: MyCtrl.resolve
        });
    }

    yourModule.config(configStates);

})(MyCtrl, yourModule);

Для производственного кода вы все равно захотите обернуть все эти IIFE в других IIFE. Gulp или Grunt может сделать это за вас.

Ответ 4

Если намерение состоит в том, чтобы разрешитель был в том же файле, что и контроллер, самым простым способом это сделать объявление в файле контроллера как функцию:

//my-ctrl.js
var MyCtrl = function($scope, customers) {
  $scope.customers = customers;
}
var resolverMyCtrl_customers = (['Customer','$stateParams', function(Customer, $stateParams) {
    return Customer.get($stateParams.id);
}]);

//routing.js
$stateProvider.state('customers.show', {
  url: '/customers/:id',
  template: template,
  controller: 'MyCtrl',
  resolve: resolverMyCtrl_customers
});

Ответ 5

Это должно сработать.

//my-ctrl.js
var MyCtrl = function($scope, customer) {
    $scope.customer = customer;
};

//routing.js
$stateProvider
    .state('customers.show', {
        url: '/customers/:id',
        template: template,
        resolve: { 
            customer: function(CustomerService, $stateParams){
                return CustomerService.get($stateParams.id)
            } 
        },
        controller: 'MyCtrl'
});


//service.js
function CustomerService() {
    var _customers = {};

    this.get = function (id) {
        return _customers[id];
    };
}