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

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

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


当前回答

下面是一个适用于Angular 1.0.2的最小工作示例

模板:

<script type="text/ng-template" id="/editor-tpl.html">
    Editor Template {{datasets}}
</script>

<div ng-view>

</div>

JavaScript:

function MyCtrl($scope, datasets) {    
    $scope.datasets = datasets;
}

MyCtrl.resolve = {
    datasets : function($q, $http) {
        var deferred = $q.defer();

        $http({method: 'GET', url: '/someUrl'})
            .success(function(data) {
                deferred.resolve(data)
            })
            .error(function(data){
                //actually you'd want deffered.reject(data) here
                //but to show what would happen on success..
                deferred.resolve("error value");
            });

        return deferred.promise;
    }
};

var myApp = angular.module('myApp', [], function($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: '/editor-tpl.html',
        controller: MyCtrl,
        resolve: MyCtrl.resolve
    });
});​
​

http://jsfiddle.net/dTJ9N/3/

简化版:

因为$http()已经返回了一个承诺(又名deferred),我们实际上不需要创建自己的承诺。我们可以简化MyCtrl。解决:

MyCtrl.resolve = {
    datasets : function($http) {
        return $http({
            method: 'GET', 
            url: 'http://fiddle.jshell.net/'
        });
    }
};

$http()的结果包含数据、状态、报头和配置对象,因此我们需要将MyCtrl的主体更改为:

$scope.datasets = datasets.data;

http://jsfiddle.net/dTJ9N/5/

其他回答

延迟显示路由肯定会导致异步纠缠……为什么不简单地跟踪主实体的加载状态并在视图中使用它呢?例如,在你的控制器中,你可能同时使用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>

下面是一个适用于Angular 1.0.2的最小工作示例

模板:

<script type="text/ng-template" id="/editor-tpl.html">
    Editor Template {{datasets}}
</script>

<div ng-view>

</div>

JavaScript:

function MyCtrl($scope, datasets) {    
    $scope.datasets = datasets;
}

MyCtrl.resolve = {
    datasets : function($q, $http) {
        var deferred = $q.defer();

        $http({method: 'GET', url: '/someUrl'})
            .success(function(data) {
                deferred.resolve(data)
            })
            .error(function(data){
                //actually you'd want deffered.reject(data) here
                //but to show what would happen on success..
                deferred.resolve("error value");
            });

        return deferred.promise;
    }
};

var myApp = angular.module('myApp', [], function($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: '/editor-tpl.html',
        controller: MyCtrl,
        resolve: MyCtrl.resolve
    });
});​
​

http://jsfiddle.net/dTJ9N/3/

简化版:

因为$http()已经返回了一个承诺(又名deferred),我们实际上不需要创建自己的承诺。我们可以简化MyCtrl。解决:

MyCtrl.resolve = {
    datasets : function($http) {
        return $http({
            method: 'GET', 
            url: 'http://fiddle.jshell.net/'
        });
    }
};

$http()的结果包含数据、状态、报头和配置对象,因此我们需要将MyCtrl的主体更改为:

$scope.datasets = datasets.data;

http://jsfiddle.net/dTJ9N/5/

您可以使用$routeProvider解析属性来延迟路由更改,直到数据被加载。

angular.module('app', ['ngRoute']).
  config(['$routeProvider', function($routeProvider, EntitiesCtrlResolve, EntityCtrlResolve) {
    $routeProvider.
      when('/entities', {
        templateUrl: 'entities.html', 
        controller: 'EntitiesCtrl', 
        resolve: EntitiesCtrlResolve
      }).
      when('/entity/:entityId', {
        templateUrl: 'entity.html', 
        controller: 'EntityCtrl', 
        resolve: EntityCtrlResolve
      }).
      otherwise({redirectTo: '/entities'});
}]);

注意,resolve属性是在route上定义的。

EntitiesCtrlResolve和EntityCtrlResolve是与EntitiesCtrl和EntityCtrl控制器定义在同一文件中的常量对象。

// EntitiesCtrl.js

angular.module('app').constant('EntitiesCtrlResolve', {
  Entities: function(EntitiesService) {
    return EntitiesService.getAll();
  }
});

angular.module('app').controller('EntitiesCtrl', function(Entities) {
  $scope.entities = Entities;

  // some code..
});

// EntityCtrl.js

angular.module('app').constant('EntityCtrlResolve', {
  Entity: function($route, EntitiesService) {
    return EntitiesService.getById($route.current.params.projectId);
  }
});

angular.module('app').controller('EntityCtrl', function(Entity) {
  $scope.entity = Entity;

  // some code..
});

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

<div ng-cloak="">
  Value in  myModel is: {{myModel}}
</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。