角ui-路由器解析值为字符串

时间:2022-02-04 10:31:09

With ui-router, I add all resolve logic in state function like this;

使用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);
        }
      }
    });

However IMO, resolve object must belong to a controller, and it's easy to read and maintain if it is defined within a controller file.

但是,IMO,解析对象必须属于一个控制器,如果它是在一个控制器文件中定义的,那么它很容易读取和维护。

    //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.
    });

However, When I define it as MyCtrl.resolve, because of IIFE, I get the following error.

但是,当我将它定义为MyCtrl。解决,因为生活,我得到以下错误。

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

When I define that one as string 'MyCtrl.resolve', I get this

我把它定义为字符串MyCtrl。解决的,我得到这个

Error: 'invocables' must be an object.

I see that controller is defined as string, so I think it's also possible to provide the value as string by using a decorator or something.

我看到控制器被定义为string,所以我认为也可以通过使用decorator或其他东西来提供字符串的值。

Has anyone done this approach? So that I can keep my routings.js clean and putting relevant info. in a relevant file?

有人这样做过吗?这样我才能遵守我的路线。js清理并放置相关信息。在一个相关的文件?

5 个解决方案

#1


8  

It sounds like a neat way to build the resolve, but I just don't think you can do it.

这听起来像是一种构建解决方案的简洁方式,但我认为您做不到。

Aside from the fact that "resolve" requires an object, it is defined in a phase where all you have available are providers. At this time, the controller doesn't even exist yet.

除了“解析”需要对象的事实之外,它还定义在一个阶段,即所有可用的都是提供者。此时,控制器甚至还不存在。

Even worse, though, the "resolve" is meant to define inputs to the controller, itself. To define the resolve in the controller, then expect it to be evaluated before the controller is created is a circular dependency.

更糟糕的是,“resolve”是用来定义控制器本身的输入。要在控制器中定义解析,那么期望在控制器被创建为循环依赖之前对其进行计算。

In the past, I have defined resolve functions outside of the $stateProvider definition, at least allowing them to be reused. I never tried to get any fancier than that.

在过去,我定义了$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


4  

This question is about features of ui-router package. By default ui-router doesn't support strings for resolve parameter. But if you look at the source code of ui-router you will see, that it's possible to implement this functionality without making direct changes to it's code.

这个问题是关于ui-router包的特性的。默认情况下,ui-router不支持解析参数的字符串。但是,如果您查看ui-router的源代码,您会发现,在不直接修改ui-router的代码的情况下实现这个功能是可能的。

Now, I will show the logic behind suggested method and it's implementation

现在,我将展示建议方法及其实现背后的逻辑

Analyzing the code

First let's take a look at $state.transitionTo function angular-ui-router/src/urlRouter.js. Inside that function we will see this code

首先让我们看看$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);
  }

Obviously this is where "resolve" parameters are resolved for every parent state. Next, let's take a look at resolveState function at the same file. We will find this line there:

显然,这就是为每个父状态解析“resolve”参数的地方。接下来,让我们看看同一个文件中的resolveState函数。我们会在这里找到这条线:

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

This is specifically where promises for resolve parameters are retrieved. What's good for use, the function that does this is taken out to a separate service. This means we can hook and alter it's behavior with decorator.

这是获取解析参数承诺的特定位置。什么是有用的,它的功能是把它带到一个单独的服务。这意味着我们可以用decorator关联并改变它的行为。

For reference the implementation of $resolve is in angular-ui-router/src/resolve.js file

作为参考,$resolve的实现是在angular-ui-router/src/resolve中实现的。js文件

Implementing the hook

The signature for resolve function of $resolve is

$resolve函数的签名是

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

Where "invocables" is the object from our declaration of state. So we need to check if "invocables" is string. And if it is we will get a controller function by string and invoke function after "." character

“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:

You can also override $controllerProvider with provider like this:

您还可以用提供程序覆盖$controllerProvider,如下所示:

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

}

This way it becomes possible to add a new function getConstructor, that will return controller constructor by name. And so you will avoid using $window object in the hook:

这样就可以添加一个新的函数getConstructor,它将按名称返回controller构造函数。因此,您将避免在钩子中使用$window对象:

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

Full code demonstrating this method http://plnkr.co/edit/f3dCSLn14pkul7BzrMvH?p=preview

演示此方法的完整代码http://plnkr.co/edit/f3dcsln14pkul7bzrmvh?

#3


2  

You need to make sure the controller is within the same closure as the state config. This doesn't mean they need to be defined in the same file.

您需要确保控制器在与状态配置相同的闭包内。这并不意味着它们需要在同一个文件中定义。

So instead of a string, use a the static property of the controller:

因此,使用控制器的静态属性而不是字符串:

resolve: MyCtrl.resolve,

Update

更新

Then for your Controller file:

然后为您的控制器文件:

var MyCtrl;
(function(MyCtrl, yourModule) {

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

    yourModule.controller('MyCtrl', MyCtrl);

})(MyCtrl, yourModule)

And then when you define your states in another file, that is included or concatenated or required after the controller file:

然后,当您在另一个文件中定义您的状态时,该状态在控制器文件之后被包含或连接或需要:

(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);

For production code you will still want to wrap all these IIFEs within another IIFEs. Gulp or Grunt can do this for you.

对于生产代码,您仍然需要将所有这些IIFEs打包到另一个IIFEs中。大口喝或咕哝都能帮到你。

#4


0  

If the intention is to have the resolver in the same file as the controller, the simplest way to do so is to declare the resolver at the controller file as a function:

如果打算将解析器与控制器放在同一个文件中,最简单的方法是将控制器文件中的解析器声明为函数:

//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


0  

This should work.

这应该工作。

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

#1


8  

It sounds like a neat way to build the resolve, but I just don't think you can do it.

这听起来像是一种构建解决方案的简洁方式,但我认为您做不到。

Aside from the fact that "resolve" requires an object, it is defined in a phase where all you have available are providers. At this time, the controller doesn't even exist yet.

除了“解析”需要对象的事实之外,它还定义在一个阶段,即所有可用的都是提供者。此时,控制器甚至还不存在。

Even worse, though, the "resolve" is meant to define inputs to the controller, itself. To define the resolve in the controller, then expect it to be evaluated before the controller is created is a circular dependency.

更糟糕的是,“resolve”是用来定义控制器本身的输入。要在控制器中定义解析,那么期望在控制器被创建为循环依赖之前对其进行计算。

In the past, I have defined resolve functions outside of the $stateProvider definition, at least allowing them to be reused. I never tried to get any fancier than that.

在过去,我定义了$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


4  

This question is about features of ui-router package. By default ui-router doesn't support strings for resolve parameter. But if you look at the source code of ui-router you will see, that it's possible to implement this functionality without making direct changes to it's code.

这个问题是关于ui-router包的特性的。默认情况下,ui-router不支持解析参数的字符串。但是,如果您查看ui-router的源代码,您会发现,在不直接修改ui-router的代码的情况下实现这个功能是可能的。

Now, I will show the logic behind suggested method and it's implementation

现在,我将展示建议方法及其实现背后的逻辑

Analyzing the code

First let's take a look at $state.transitionTo function angular-ui-router/src/urlRouter.js. Inside that function we will see this code

首先让我们看看$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);
  }

Obviously this is where "resolve" parameters are resolved for every parent state. Next, let's take a look at resolveState function at the same file. We will find this line there:

显然,这就是为每个父状态解析“resolve”参数的地方。接下来,让我们看看同一个文件中的resolveState函数。我们会在这里找到这条线:

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

This is specifically where promises for resolve parameters are retrieved. What's good for use, the function that does this is taken out to a separate service. This means we can hook and alter it's behavior with decorator.

这是获取解析参数承诺的特定位置。什么是有用的,它的功能是把它带到一个单独的服务。这意味着我们可以用decorator关联并改变它的行为。

For reference the implementation of $resolve is in angular-ui-router/src/resolve.js file

作为参考,$resolve的实现是在angular-ui-router/src/resolve中实现的。js文件

Implementing the hook

The signature for resolve function of $resolve is

$resolve函数的签名是

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

Where "invocables" is the object from our declaration of state. So we need to check if "invocables" is string. And if it is we will get a controller function by string and invoke function after "." character

“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:

You can also override $controllerProvider with provider like this:

您还可以用提供程序覆盖$controllerProvider,如下所示:

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

}

This way it becomes possible to add a new function getConstructor, that will return controller constructor by name. And so you will avoid using $window object in the hook:

这样就可以添加一个新的函数getConstructor,它将按名称返回controller构造函数。因此,您将避免在钩子中使用$window对象:

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

Full code demonstrating this method http://plnkr.co/edit/f3dCSLn14pkul7BzrMvH?p=preview

演示此方法的完整代码http://plnkr.co/edit/f3dcsln14pkul7bzrmvh?

#3


2  

You need to make sure the controller is within the same closure as the state config. This doesn't mean they need to be defined in the same file.

您需要确保控制器在与状态配置相同的闭包内。这并不意味着它们需要在同一个文件中定义。

So instead of a string, use a the static property of the controller:

因此,使用控制器的静态属性而不是字符串:

resolve: MyCtrl.resolve,

Update

更新

Then for your Controller file:

然后为您的控制器文件:

var MyCtrl;
(function(MyCtrl, yourModule) {

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

    yourModule.controller('MyCtrl', MyCtrl);

})(MyCtrl, yourModule)

And then when you define your states in another file, that is included or concatenated or required after the controller file:

然后,当您在另一个文件中定义您的状态时,该状态在控制器文件之后被包含或连接或需要:

(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);

For production code you will still want to wrap all these IIFEs within another IIFEs. Gulp or Grunt can do this for you.

对于生产代码,您仍然需要将所有这些IIFEs打包到另一个IIFEs中。大口喝或咕哝都能帮到你。

#4


0  

If the intention is to have the resolver in the same file as the controller, the simplest way to do so is to declare the resolver at the controller file as a function:

如果打算将解析器与控制器放在同一个文件中,最简单的方法是将控制器文件中的解析器声明为函数:

//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


0  

This should work.

这应该工作。

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