延迟 AngularJS 路由更改直到模型加载以防止闪烁

IT技术 javascript angularjs angularjs-routing
2021-02-04 17:03:10

我想知道 AngularJS 是否有一种方法(类似于 Gmail)可以延迟显示新路由,直到使用其各自的服务获取每个模型及其数据之后

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

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

6个回答

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

首先定义一个具有这样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属性是在路由上定义的。

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

请注意,控制器定义包含一个解析对象,该对象声明了控制器构造函数应该可用的内容。这里phones注入到控制器中,并在resolve属性中定义

resolve.phones函数负责返回一个Promise。所有的Promise都被收集起来,并且路由更改被延迟到所有的Promise都被解决之后。

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

任何使用 angular.controller() 和最新版本的 AngularJS 的例子?
2021-03-13 17:03:10
@blesh,当您使用时angular.controller(),您可以将此函数的结果分配给变量 ( var MyCtrl = angular.controller(...)),然后进一步处理 ( MyCtrl.loadData = function(){..})。查看egghead的视频,代码直接显示在那里:egghead.io/video/0uvAseNXDr0
2021-03-20 17:03:10
这是如何在angular.controller()类型控制器定义中使用的?在这些$routeProvider东西中,我认为您必须使用控制器的字符串名称。
2021-03-30 17:03:10
@MiskoHevery - 如果您的控制器在module内并且被定义为字符串而不是函数怎么办。你怎么能像你一样设置解析属性?
2021-04-09 17:03:10
我仍然想要一个很好的方法来处理而无需将控制器放在全局中。我不想到处乱扔全局变量。您可以使用常量来实现,但是能够将解析函数放在控制器上/放在控制器中而不是其他地方会很好。
2021-04-09 17:03:10

这是一个适用于 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() 已经返回了一个Promise(又名延迟),我们实际上不需要创建我们自己的。所以我们可以简化MyCtrl。决心:

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

$http() 的结果包含datastatusheadersconfig对象,因此我们需要将 MyCtrl 的主体更改为:

$scope.datasets = datasets.data;

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

有人可以帮我将此答案转换为 app.controller('MyCtrl') 格式吗? jsfiddle.net/5usya/1对我不起作用。
2021-03-10 17:03:10
我正在尝试做这样的事情,但在注入“数据集”时遇到了麻烦,因为它没有定义。有什么想法吗?
2021-03-18 17:03:10
嘿 mb21,我想你可以帮我解决这个问题:stackoverflow.com/questions/14271713/...
2021-03-21 17:03:10
您可以通过用以下内容替换数据集来使您的答案更简单: function($http) { return $http({method: 'GET', url: '/someUrl'}) .then( function(data){ return data;}, function(reason){return 'error value';} ); }
2021-03-23 17:03:10
我收到一个错误: Unknown provider: datasetsProvider <- datasets
2021-04-08 17:03:10

我看到有些人问如何使用 angular.controller 方法和缩小友好的依赖注入来做到这一点。因为我刚刚开始工作,所以我觉得有必要回来帮忙。这是我的解决方案(采用原始问题和 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';
}]);

由于此代码源自问题/最受欢迎的答案,因此未经测试,但如果您已经了解如何制作对缩小友好的角度代码,它应该会向您发送正确的方向。我自己的代码不需要的一个部分是将“电话”注入到“电话”的解析函数中,我也根本没有使用任何“延迟”对象。

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

如果您感兴趣,我决定也粘贴我自己的代码(用咖啡脚本编写),这样您就可以看到我是如何让它工作的。

仅供参考,我事先使用了一个通用控制器来帮助我在几个模型上进行 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 ....
    ]
酷,谢谢。在 SO 上有很多这样做的例子(比如这里的前两个),但它们都是 2012 年和 2013 年初的。这是一种优雅的方法,但似乎已被弃用。现在最干净的选择似乎是编写作为Promise对象的单个服务。
2021-03-13 17:03:10
我是否从您的示例和我失败的尝试中正确推断,现在无法resolve在 Angular 的最新版本中引用控制器中函数?所以它必须在配置中正确声明,因为它在这里?
2021-03-22 17:03:10
@XMLilley 我很确定是这样。这个例子是我写它时的 1.1.2,我相信。我没有看到任何关于将解析放入控制器的文档
2021-04-02 17:03:10
谢谢这对我有用。对于任何其他关于未定义$defer服务的错误,请注意在 AngularJS 的 1.5.7 版中,您想$timeout改用它。
2021-04-07 17:03:10

此提交,这是1.1.5版的一部分以上,暴露$promise的物体$resource包含此提交的 ngResource 版本允许解析如下资源:

$routeProvider

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

控制器

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

    $scope.data = data;

}]);
请问哪些版本包含该提交?
2021-03-10 17:03:10
最新的不稳定版本 (1.1.5) 包含此提交。 ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js
2021-03-14 17:03:10
资源将如何访问 $routeParams?例如:在GET '/api/1/apps/:appId'-->App.get({id: $routeParams.appId}).$promise();我不能这样使用
2021-03-14 17:03:10
我喜欢这种不那么冗长的方法。从实际数据对象创建一个Promise并直接传递它会很好,但这是非常少的代码,它可以很好地工作。
2021-03-18 17:03:10
@zeronone 你注入$route你的决心并使用$route.current.params. 小心点,$routeParams还是指着老路。
2021-03-29 17:03:10

这个片段是依赖注入友好(我甚至用它在组合ngmin丑化),这是一个更优雅的领域驱动基础的解决方案。

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

注意:如果你正在使用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;
        }]
      }
    }
  });

下一部分是在module处于配置状态时注入路由数据并将其应用于$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);
      });
    });
  });
});

你可以在我最新的(即将进行的)实验中看到它的盛况尽管这种方法能正常工作对我来说,我真的不知道为什么$喷油器不误事的建设什么当它检测到的注射什么这是一个Promise的对象; 它会让事情变得更容易。

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

这个出色的答案似乎更符合“Angular”哲学(封装等)。我们都应该有意识地努力阻止逻辑像 kudzu 一样在代码库中蔓延。
2021-04-03 17:03:10
I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise object我猜他们省略了这个功能,因为它可能会鼓励对应用程序的响应能力产生负面影响的设计模式。他们心目中的理想应用程序是真正异步的应用程序,因此解析应该是一种边缘情况。
2021-04-04 17:03:10