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

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

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


当前回答

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

其他回答

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

我从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;
  }
}

我喜欢darkporter的想法,因为对于刚接触AngularJS的开发团队来说,理解和工作起来很容易。

我创建了这个改编,它使用了2个div,一个用于加载器栏,另一个用于数据加载后显示的实际内容。错误处理将在其他地方完成。

在$scope中添加一个ready标志:

$http({method: 'GET', url: '...'}).
    success(function(data, status, headers, config) {
        $scope.dataForView = data;      
        $scope.ready = true;  // <-- set true after loaded
    })
});

在html视图中:

<div ng-show="!ready">

    <!-- Show loading graphic, e.g. Twitter Boostrap progress bar -->
    <div class="progress progress-striped active">
        <div class="bar" style="width: 100%;"></div>
    </div>

</div>

<div ng-show="ready">

    <!-- Real content goes here and will appear after loading -->

</div>

参见:Boostrap进度条文档

这个提交是版本1.1.5及更高版本的一部分,它公开了$resource的$promise对象。包含此提交的ngResource版本允许像这样解析资源:

routeProvider美元

resolve: {
    data: function(Resource) {
        return Resource.get().$promise;
    }
}

控制器

app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {

    $scope.data = data;

}]);

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

$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;


    });
});