我想知道是否有一种方法(类似于Gmail)让AngularJS延迟显示一个新的路由,直到每个模型和它的数据已经使用各自的服务获取。

例如,如果有一个ProjectsController列出了所有的项目,而project_index.html是显示这些项目的模板,那么Project.query()将在显示新页面之前被完全获取。

在此之前,旧页面仍将继续显示(例如,如果我正在浏览另一个页面,然后决定查看这个Project索引)。


当前回答

一个可能的解决方案可能是在我们使用模型的元素中使用ng-cloak指令。

<div ng-cloak="">
  Value in  myModel is: {{myModel}}
</div>

我觉得这个最省力。

其他回答

我喜欢上面的答案,并从中学到很多东西,但上面的大多数答案都缺少一些东西。

我遇到过类似的情况,我在解析url时使用了从服务器的第一个请求中获取的一些数据。我面临的问题是,如果承诺被拒绝了怎么办。

我正在使用一个自定义提供程序,它曾经返回一个承诺,在配置阶段由$routeProvider的解析解决。

我在这里想强调的是当它这样做时的概念。

它看到url在url栏,然后分别当块在调用控制器和视图被引用到目前为止,很好。

假设我有以下配置阶段的代码。

App.when('/', {
   templateUrl: '/assets/campaigns/index.html',
   controller: 'CampaignListCtr',
   resolve : {
      Auth : function(){
         return AuthServiceProvider.auth('campaign');
      }
   }
})
// Default route
.otherwise({
   redirectTo: '/segments'
});

在根url在浏览器的第一个块运行得到调用,否则否则被调用。

让我们想象一个场景,我在地址栏中点击rootUrl authserviceprivid .auth()函数被调用。

假设返回的Promise处于拒绝状态,然后呢?

什么都没有渲染。

否则,block将不会被执行,因为它对于任何没有在配置块中定义的url和angularJs的配置阶段未知的url都是如此。

当这个承诺没有得到解决时,我们将不得不处理被解雇的事件。失败时$ routechangeerror在$rootScope上被触发。

它可以被捕获,如下面的代码所示。

$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
    // Use params in redirection logic.
    // event is the routeChangeEvent
    // current is the current url
    // previous is the previous url
    $location.path($rootScope.rootPath);
});

在应用程序的运行块中放置事件跟踪代码通常是一个好主意。这段代码只在应用程序的配置阶段之后运行。

App.run(['$routeParams', '$rootScope', '$location', function($routeParams, $rootScope, $location){
   $rootScope.rootPath = "my custom path";
   // Event to listen to all the routeChangeErrors raised
   // by the resolve in config part of application
   $rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
       // I am redirecting to rootPath I have set above.
       $location.path($rootScope.rootPath);
   });
}]);

这样我们就可以在配置阶段处理承诺失败。

我从Misko的代码上面工作,这是我所做的。这是一个更当前的解决方案,因为$defer已经更改为$timeout。然而,替换$timeout将等待超时时间(在Misko的代码中,1秒),然后返回数据,希望它能及时解决。用这种方法,它会尽快返回。

function PhoneListCtrl($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}

PhoneListCtrl.resolve = {

  phones: function($q, Phone) {
    var deferred = $q.defer();

    Phone.query(function(phones) {
        deferred.resolve(phones);
    });

    return deferred.promise;
  }
}

延迟显示路由肯定会导致异步纠缠……为什么不简单地跟踪主实体的加载状态并在视图中使用它呢?例如,在你的控制器中,你可能同时使用ngResource上的success和error回调:

$scope.httpStatus = 0; // in progress
$scope.projects = $resource.query('/projects', function() {
    $scope.httpStatus = 200;
  }, function(response) {
    $scope.httpStatus = response.status;
  });

然后在视图中你可以做任何事情:

<div ng-show="httpStatus == 0">
    Loading
</div>
<div ng-show="httpStatus == 200">
    Real stuff
    <div ng-repeat="project in projects">
         ...
    </div>
</div>
<div ng-show="httpStatus >= 400">
    Error, not found, etc. Could distinguish 4xx not found from 
    5xx server error even.
</div>

使用AngularJS 1.1.5

使用AngularJS 1.1.5语法更新justin回答中的“phones”函数。

原:

phones: function($q, Phone) {
    var deferred = $q.defer();

    Phone.query(function(phones) {
        deferred.resolve(phones);
    });

    return deferred.promise;
}

更新:

phones: function(Phone) {
    return Phone.query().$promise;
}

多亏了Angular团队和贡献者,这个版本变得更短了。:)

这也是马克西米利安·霍夫曼的答案。显然,这个commit被放到了1.1.5。

这个代码片段是依赖注入友好的(我甚至将它与ngmin和uglify结合使用),它是一个更优雅的基于领域驱动的解决方案。

下面的例子注册了一个Phone资源和一个常量phoneRoutes,其中包含了该(Phone)域的所有路由信息。在提供的答案中,我不喜欢解析逻辑的位置——主模块不应该知道任何事情,也不应该被提供给控制器的资源参数的方式所困扰。这样逻辑就保持在同一个域中。

注意:如果你正在使用ngmin(如果你没有:你应该),你只需要按照DI数组约定编写解析函数。

angular.module('myApp').factory('Phone',function ($resource) {
  return $resource('/api/phone/:id', {id: '@id'});
}).constant('phoneRoutes', {
    '/phone': {
      templateUrl: 'app/phone/index.tmpl.html',
      controller: 'PhoneIndexController'
    },
    '/phone/create': {
      templateUrl: 'app/phone/edit.tmpl.html',
      controller: 'PhoneEditController',
      resolve: {
        phone: ['$route', 'Phone', function ($route, Phone) {
          return new Phone();
        }]
      }
    },
    '/phone/edit/:id': {
      templateUrl: 'app/phone/edit.tmpl.html',
      controller: 'PhoneEditController',
      resolve: {
        form: ['$route', 'Phone', function ($route, Phone) {
          return Phone.get({ id: $route.current.params.id }).$promise;
        }]
      }
    }
  });

下一部分是在模块处于configure状态时注入路由数据,并将其应用到$routeProvider。

angular.module('myApp').config(function ($routeProvider, 
                                         phoneRoutes, 
                                         /* ... otherRoutes ... */) {

  $routeProvider.when('/', { templateUrl: 'app/main/index.tmpl.html' });

  // Loop through all paths provided by the injected route data.

  angular.forEach(phoneRoutes, function(routeData, path) {
    $routeProvider.when(path, routeData);
  });

  $routeProvider.otherwise({ redirectTo: '/' });

});

用这个设置测试路由配置也很简单:

describe('phoneRoutes', function() {

  it('should match route configuration', function() {

    module('myApp');

    // Mock the Phone resource
    function PhoneMock() {}
    PhoneMock.get = function() { return {}; };

    module(function($provide) {
      $provide.value('Phone', FormMock);
    });

    inject(function($route, $location, $rootScope, phoneRoutes) {
      angular.forEach(phoneRoutes, function (routeData, path) {

        $location.path(path);
        $rootScope.$digest();

        expect($route.current.templateUrl).toBe(routeData.templateUrl);
        expect($route.current.controller).toBe(routeData.controller);
      });
    });
  });
});

你可以在我最新的(即将到来的)实验中看到它的全部荣耀。 虽然这个方法对我来说工作得很好,但我真的想知道为什么$injector在检测到任何是promise对象的注入时不延迟任何东西的构造;这会让事情变得非常非常非常简单。

编辑:使用Angular v1.2(rc2)