我想知道是否有一种方法(类似于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。

其他回答

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

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


    });
});

您可以使用$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..
});

$routeProvider resolve属性允许延迟路由更改,直到数据被加载。

首先像这样定义一个具有resolve属性的路由。

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: PhoneListCtrl.resolve}).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

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

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

PhoneListCtrl.resolve = {
  phones: function(Phone, $q) {
    // see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
    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: function($q, $defer) {
    var delay = $q.defer();
    $defer(delay.resolve, 1000);
    return delay.promise;
  }
}

注意,控制器定义包含一个resolve对象,它声明了控制器构造函数应该可用的东西。在这里,手机被注入到控制器中,并在resolve属性中定义。

的决心。Phones函数负责返回一个promise。收集所有的承诺,并延迟路由更改,直到所有的承诺都被解决。

工作演示:http://mhevery.github.com/angular-phonecat/app/#/phones 来源:https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831

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