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

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

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


当前回答

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

我遇到过类似的情况,我在解析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);
   });
}]);

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

其他回答

使用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。

我有一个复杂的多层次滑动面板界面,与禁用的屏幕层。在禁用屏幕层上创建指令,该指令将创建点击事件来执行状态

$state.go('account.stream.social.view');

产生了弹跳的效果。history.back()而不是它工作得很好,但它并不总是回到历史在我的情况下。所以我发现,如果我只是在我的禁用屏幕上创建属性href而不是状态。去吧,效果很好。

<a class="disable-screen" back></a>

指令“回来”

app.directive('back', [ '$rootScope', function($rootScope) {

    return {
        restrict : 'A',
        link : function(scope, element, attrs) {
            element.attr('href', $rootScope.previousState.replace(/\./gi, '/'));
        }
    };

} ]);

我只是保存了之前的状态

app.run(function($rootScope, $state) {      

    $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {         

        $rootScope.previousState = fromState.name;
        $rootScope.currentState = toState.name;


    });
});

下面是一个适用于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/

我看到一些人问如何使用angular来做到这一点。带有简化友好依赖项注入的控制器方法。因为我刚开始工作,我觉得有必要回来帮忙。以下是我的解决方案(采用了最初的问题和Misko的答案):

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: { 
            phones: ["Phone", "$q", function(Phone, $q) {
                var deferred = $q.defer();
                Phone.query(function(successData) {
                  deferred.resolve(successData); 
                }, function(errorData) {
                  deferred.reject(); // you could optionally pass error data here
                });
                return deferred.promise;
             ]
            },
            delay: ["$q","$defer", function($q, $defer) {
               var delay = $q.defer();
               $defer(delay.resolve, 1000);
               return delay.promise;
              }
            ]
        },

        }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}]);

由于这段代码来源于问题/最流行的答案,所以它没有经过测试,但如果你已经了解如何编写友好的简化角代码,它应该会给你带来正确的方向。我自己的代码不需要的一部分是将“Phone”注入到“phones”的resolve函数中,我也没有使用任何“delay”对象。

我也推荐这个youtube视频http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp,它对我帮助很大

如果你感兴趣,我也决定粘贴我自己的代码(写在coffescript),所以你可以看到我是如何得到它的工作。

仅供参考,在此之前,我使用了一个通用控制器,帮助我在几个模型上做CRUD:

appModule.config ['$routeProvider', ($routeProvider) ->
  genericControllers = ["boards","teachers","classrooms","students"]
  for controllerName in genericControllers
    $routeProvider
      .when "/#{controllerName}/",
        action: 'confirmLogin'
        controller: 'GenericController'
        controllerName: controllerName
        templateUrl: "/static/templates/#{controllerName}.html"
        resolve:
          items : ["$q", "$route", "$http", ($q, $route, $http) ->
             deferred = $q.defer()
             controllerName = $route.current.controllerName
             $http(
               method: "GET"
               url: "/api/#{controllerName}/"
             )
             .success (response) ->
               deferred.resolve(response.payload)
             .error (response) ->
               deferred.reject(response.message)

             return deferred.promise
          ]

  $routeProvider
    .otherwise
      redirectTo: '/'
      action: 'checkStatus'
]

appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->

  $scope.items = items
      #etc ....
    ]

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

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

我觉得这个最省力。