如何避免使用ui-router“污染”解决方法

时间:2014-03-07 16:06:06

标签: angularjs angular-ui-router

我想尽可能限制AngularJS应用程序中的“闪烁”。我从路由器使用 resolve:(使用 ngRouter ui-router )来加载数据,以便显示我的页面所需的一切在改变路线之前可以使用。

常见例子:

.state('recipes.category', {
    url: '/:cat',
    templateUrl: '/partials/recipes.category.html',
    controller: 'RecipesCategoryCtrl as recipeList',
    resolve: {
         category: ['$http','$stateParams', function ($http, $stateParams) {
             return $http.get('/recipes/' + $stateParams.cat).then(function(data) { return data.data; });
         }]
     }
});

现在我的 RecipesCategoryCtrl 控制器可以加载类别,并且可以直接解析承诺。

有没有办法将加载代码直接嵌入我的控制器中?或者其他地方更“干净”?我不喜欢在路线定义中有太多的逻辑......

类似的东西:

.state('recipes.category', {
    url: '/:cat',
    templateUrl: '/partials/recipes.category.html',
    controller: 'RecipesCategoryCtrl as recipeList',
    resolve: 'recipeList.resolve()' // something related to RecipesCategoryCtrl and "idiomatic" AngularJS
});

4 个答案:

答案 0 :(得分:4)

也许这不是您想要的,但您可以使用以下方式将一些逻辑从路由器移动到控制器:

//prepare content
$scope.$on('$viewContentLoading', function(event) {

     //show a progress bar whatever

     //fetch/request your data asynchronously

     //when promise resolves, add your data to $scope

});


//remove spinning loader when view is ready
$scope.$on('$viewContentLoaded', function(event) {

    //remove progress bar

});

基本上,用户首先被发送到页面,然后加载动态内容,然后显示整页。

这可能完全偏离主题,但我正在使用这种方法并且工作正常。首先显示新视图然后获取数据也是一种很好的做法。有一个非常好的视频here解释了原因。该视频是关于带有Phonegap的SPA,但有很多关于SPA的提示。有趣的部分(针对这个具体案例) 是大约1小时1分钟。

修改:如果$viewContentLoading未被解雇,请查看here。您可能需要将所有逻辑放在$viewContentLoaded

这就是我目前正在做的事情:

 $scope.$on('$viewContentLoaded', function(event) {

            //show loader while we are preparing view 
            notifications.showProgressDialog();

            //get data
            getData().then(function(data) {

                //bind data to view
                $scope.data = data;

                //remove spinning loader as view is ready
                notifications.hideProgressDialog();
            });
});

我仍然对$scope.data = data;不满意,好像我的数据对象很大,我可能会在与视图绑定完成之前隐藏进度对话框,因此可能会发生一些闪烁。解决方案是使用处理scope.$last的自定义指令,请参阅this answer(即使绑定到$stateChangeSuccess就足够了,请查看here

这是ui-router在更改状态/视图时的当前工作方式:

  1. $viewContentLoading广播
  2. 解析部分下的依赖关系已解决
  3. 控制器实例化并解决注入的依赖关系。
  4. 发出
  5. $viewContentLoaded
  6. 控制器对$viewContentLoaded作出反应(当它被设置为当事人派遣那些事件时)。

答案 1 :(得分:3)

这是常见问题,通常发生在使用ngRoute或uiRouter的应用程序中。在这种情况下,我通常使用缓存。

例如,如果您使用Active Record like pattern与我们的业务层进行通信,则可以按以下步骤操作:

国家定义

//...
.state('users', {
  url: '/users',
  templateUrl: '/partials/users.html',
  controller: 'UsersCtrl',
  resolve: {
    users: ['Users', function (Users) {
      return Users.getList();
    }]
  }
})
.state('user', {
  url: '/users/:id',
  templateUrl: '/partials/user.html',
  controller: 'UserCtrl',
  resolve: {
    users: ['Users', '$stateParams', function (Users, $stateParams) {
      return Users.get($stateParams.id);
    }]
  }
});

服务定义

myModule.factory('User', function ($q, $http) {
  var cachedUsers = null;
  function User() {
  }

  User.getList = function () {
    if (cachedUsers) {
      return $q.when(cachedUsers);
    } else {
      return $http.get('/users')
          .then(function (resp) {
            cachedUsers = resp.data;
            return cachedUsers;
          });
    }
  };

  User.get = function (id) {
    if (cachedUsers && cachedUsers[id]) {
      return $q.when(cachedUsers[id]);
    } else {
      return $http.get('/users/' + id)
         .then(function (resp) {
           cachedUsers = cachedUsers || {};
           cachedUsers[id] = resp.data;
           return cachedUsers[id];
         });

    }
  };
  return User;
});

控制器定义

myModule.controller('UsersCtrl', function ($scope, users) {
  $scope.users = data;
});

myModule.controller('UserCtrl', function ($scope, user) {
  $scope.users = data;
});

这样,您的应用程序会缓存请求的结果,并且在每个后续路由更改中,它都会通过内存缓存获取请求的值。由于这是一个虚拟示例,我建议您使用内置的AngularJS缓存机制,因为它利用了不同的HTTP头。

答案 2 :(得分:2)

您可以在控制器中注入一个服务,该服务执行$ http.get来清理您的代码。

.state('recipes.category', {
  url: '/:cat',
  templateUrl: '/partials/recipes.category.html',
  controller: 'RecipesCategoryCtrl as recipeList',
  resolve: {
       category: ['recipeService','$stateParams', function (recipeService, $stateParams) {
           return recipeService.get($stateParams.cat)
       }]
  }
});

答案 3 :(得分:2)

我来到这里寻找有关ui-router处理resolve的方式的信息,以及它与ngRoute的方式有何不同。我没有找到答案,但最近我和你遇到的同样问题打了好几个小时,所以我认为这可能有用。需要注意的是,解决方案是针对ngRoute而不是ui-router,因此YMMV(我很乐意听到它是否/如何变化,实际上)。

我发现在resolve中封装代码的最佳方法是使用工厂(或服务)。当您将匿名函数写入resolve:时,如上所述,您实际上是在动态创建服务。因此,您可以参考现有的工厂(或服务) - 只要它使用promises(来自$ q)。然后,您告诉路由器等待该工厂的任何承诺解决。

就我而言,我使用的是在angular-pouch之上构建的自定义工厂 - 为了简单起见,我们只是说我通过http访问CouchDB。我的工厂使用了已解决的延迟对象。

我从这篇文章中学到了很多东西:http://www.undefinednull.com/2014/02/17/resolve-in-angularjs-routes-explained-as-story。但这是我的代码:

路由器

  .config(function ($routeProvider) {
     $routeProvider
     .when('/',{
        controller: 'TestController',
        templateUrl: 'templates/myview.html',
        resolve: {"pouchDbFactory":"pouchDbFactory"}
    })
    .otherwise({ redirectTo: '/' });
 })
 /*snip*/

工厂

.factory('pouchDbFactory', function (myPouch,$q) {
    var myDefer = $q.defer();
    myDefer.resolve({
        db: myPouch,
        all: function(){
            // another factory is doing the work behind the scenes
           // but here's where it returns a pouchDB promise
           return this.db.allDocs({include_docs: true});        
        },
        /* more functions, etc. */            
     });
     // Return the promise, along with its result
     return myDefer.promise;
    });

控制器(这里是解析逻辑被封装的地方)

.controller('TestController',function ($scope,pouchDbFactory){
    // this variable makes it to the template right away
    $scope.foo = "Hello Foo"; 

    pouchDbFactory.all().then(
        function(result){
          /*  this one gets added to the template once it's fetched
              AND the template isn't rendered until that's done -- 
              because it's pouchDbFactory, from "resolve:" property above
          */
          $scope.username = result.username;
        }
    );
 }) 
 /*snip*/

我有兴趣知道哪些方法适用于其他人。