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];
};
}