Задержка изменения маршрута AngularJS до загрузки модели для предотвращения мерцания

Мне интересно, есть ли способ (аналогичный Gmail) для AngularJS задерживать отображение нового маршрута до тех пор, пока каждая модель и ее данные не будут получены с использованием соответствующих служб.

Например, если есть ProjectsController, в котором перечислены все проекты, и project_index.html, который является шаблоном, показывающим эти проекты, Project.query() будет полностью извлечен перед отображением новой страницы.

До этого момента старая страница все равно продолжала бы отображаться (например, если бы я просматривал другую страницу, а затем решил просмотреть этот индекс проекта).


person Misko Hevery    schedule 15.08.2012    source источник


Ответы (13)


$ 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 определено на маршруте.

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 отвечает за возврат обещания. Собираются все обещания, и изменение маршрута откладывается до тех пор, пока не будут выполнены все обещания.

Рабочая демонстрация: http://mhevery.github.com/angular-phonecat/app/#/phones Источник: https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831

person Misko Hevery    schedule 15.08.2012
comment
В примере вы используете $ defer. $ Defer был заменен на $ timeout? Насколько я понимаю из примера, он задерживает смену маршрута не до загрузки данных, а на 1 сек. И остается только надеяться, что данные загрузятся за эту секунду ... - person Artem Andreev; 16.08.2012
comment
отсрочка / задержка используется только в качестве примера. Он задерживается, по крайней мере, на секунду или до загрузки данных. - person Misko Hevery; 22.08.2012
comment
думать о том, что все обещания должны быть выполнены до того, как страница продолжится. - person Misko Hevery; 22.08.2012
comment
Миско, как я могу создать обещание, ожидающее, пока переменная в $ scope не изменится на то, что меня волнует? Я пробовал, но не похоже, что у обещаний есть доступ к переменной $ scope? - person dougvk; 23.08.2012
comment
@MiskoHevery Спасибо, но я не могу полностью понять, почему resolve.phones возвращает обещание. Насколько я знаю из документации Phone.query () возвращает пустой массив который будет заполнен фактическими данными, когда данные будут возвращены с сервера. - person Artem Andreev; 23.08.2012
comment
@MiskoHevery - что, если ваши контроллеры находятся внутри модуля и определены как строка, а не функция. Как вы могли настроить атрибут разрешения, как вы? - person aar0n; 04.10.2012
comment
Как это используется в определениях контроллера типа angular.controller()? Что касается $routeProvider, я думал, что вам нужно использовать строковые имена контроллеров. - person Ben Lesh; 09.11.2012
comment
@MiskoHevery Как передать значение, если контроллер использует `$ inject 'для внедрения служб в контроллер? - person rxgx; 17.01.2013
comment
Пытался использовать это - и обещания от $ resource НЕ разрешаются при запуске контроллера; обещания, выполненные с помощью $ q, тем не менее выполняются. - person ondra; 27.01.2013
comment
Прочтите здесь о $ resource и promises - и о том, почему вышеперечисленное не работает должным образом: groups.google.com/forum/?fromgroups = #! topic / angular / DGf7yyD4Oc4 - person georgiosd; 28.01.2013
comment
Любой пример с использованием angular.controller () и последней версии AngularJS? - person Laurent; 07.03.2013
comment
Для тех, кто заинтересован в удобном для минификации стиле angular.controller (), я просто написал новый ответ ниже. Надеюсь, это поможет. Ваше здоровье. - person bitwit; 14.03.2013
comment
@blesh, когда вы используете angular.controller(), вы можете присвоить результат этой функции переменной (var MyCtrl = angular.controller(...)), а затем работать с ней дальше (MyCtrl.loadData = function(){..}). Посмотрите видео Egghead, там сразу же показан код: egghead.io/video/0uvAseNXDr0 - person petrkotek; 21.04.2013
comment
@beret хороший момент, я думаю, из-за подъема и того факта, что контроллер будет ссылочным типом, это все, что вам нужно сделать. Это настолько очевидно, что я чувствую себя глупо, задав этот вопрос. +1 вам за указание на очевидное. ха-ха. - person Ben Lesh; 22.04.2013
comment
$ defer был переименован в $ timeout, см. groups.google.com/d/ msg / angular / IxYes7wyYQA / M3PpOMyKxZYJ - person Michael; 21.08.2013
comment
У меня проблемы с переключением между страницами, и мне все еще не удалось решить проблему. Это возможно? - person jbenowitz; 23.08.2013
comment
Я все еще хотел бы иметь хороший способ обойтись без размещения вашего контроллера в global. Я не хочу, чтобы повсюду валялись глобалы. Вы можете сделать это с помощью константы, но было бы неплохо иметь возможность поместить функцию разрешения в контроллер, а не где-нибудь еще. - person Erik Honn; 13.09.2013
comment
@ErikHonn Оберните ваши исходные файлы Angular в самоисполняющуюся функцию, чтобы вам не приходилось беспокоиться о том, чтобы испортить вашу глобальную область видимости. - person Roy Daniels; 19.10.2013
comment
Я не использую другие js, кроме Angular, так что это все равно сделало бы его глобальным для всего моего проекта: P - person Erik Honn; 21.10.2013
comment
Задержка не требуется, правда? И можно ли это обновить с помощью нового метода $ timeout? - person CMCDragonkai; 02.11.2013
comment
Присваивать его результату app.controller неверно, так как результат возвращает тот же объект. a = angular.module('a', []); a.controller(function () {}) == a.controller(function (){}), поэтому он не будет работать с несколькими контроллерами. - person elado; 21.02.2014
comment
Как я могу отменить навигацию по маршруту, если мне отказали в обещании? - person Gangadhar JANNU; 01.02.2016
comment
@GangadharJannu Слушай $routeChangeError - person tsuz; 28.08.2016

Вот минимальный рабочий пример, который работает для 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 () уже возвращает обещание (также известное как отложенное), нам на самом деле не нужно создавать свое собственное. Таким образом, мы можем упростить MyCtrl. решить:

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

Результат $ http () содержит объекты data, status, headers и config, поэтому нам нужно изменить тело MyCtrl в:

$scope.datasets = datasets.data;

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

person Community    schedule 10.09.2012
comment
Я пытаюсь сделать что-то подобное, но у меня проблемы с введением «наборов данных», поскольку он не определен. Есть предположения? - person Rob Bygrave; 08.01.2013
comment
Привет, mb21, я думаю, вы могли бы помочь мне с этим вопросом: stackoverflow.com/questions/14271713/ - person winduptoy; 11.01.2013
comment
Может ли кто-нибудь помочь мне преобразовать этот ответ в формат app.controller ('MyCtrl')? jsfiddle.net/5usya/1 у меня не работал. - person user1071182; 15.04.2013
comment
я получаю сообщение об ошибке: Unknown provider: datasetsProvider <- datasets - person chovy; 25.11.2013
comment
Вы можете упростить свой ответ, заменив наборы данных следующим образом: function($http) { return $http({method: 'GET', url: '/someUrl'}) .then( function(data){ return data;}, function(reason){return 'error value';} ); } - person Morteza Tourani; 08.05.2016

Я вижу, как некоторые люди спрашивают, как это сделать, используя метод angular.controller с инъекцией зависимостей, дружественной к минимизации. Поскольку я только что получил эту работу, я почувствовал себя обязанным вернуться и помочь. Вот мое решение (взято из исходного вопроса и ответа Миско):

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» в функцию разрешения для «телефонов», и при этом я вообще не использовал какой-либо объект «задержки».

Я также рекомендую это видео на YouTube http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp, что мне немного помогло

Если это вас заинтересует, я решил также вставить свой собственный код (написанный на coffeescript), чтобы вы могли увидеть, как он у меня работает.

К вашему сведению, заранее я использую общий контроллер, который помогает мне выполнять 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 ....
    ]
person bitwit    schedule 14.03.2013
comment
Правильно ли я делаю вывод из вашего примера и моих неудачных попыток, что теперь невозможно ссылаться на resolve функцию в контроллере в последних версиях Angular? Значит, это должно быть объявлено прямо в конфиге, как здесь? - person XML; 07.08.2013
comment
@XMLilley Я почти уверен, что это так. Думаю, этот пример был из версии 1.1.2, когда я его писал. Я не видел документации о разрешении внутри контроллера. - person bitwit; 07.08.2013
comment
Хорошо, спасибо. Есть много примеров того, как это делается на SO (например, два верхних здесь), но все они относятся к 2012 году и началу 2013 года. Это элегантный подход, но, похоже, он устарел. Самой чистой альтернативой сейчас кажется написание отдельных сервисов, которые являются объектами обещаний. - person XML; 07.08.2013
comment
Спасибо, это сработало для меня. Для всех, кто получает ошибки о неопределенной службе $defer, обратите внимание, что в версии 1.5.7 AngularJS вы хотите использовать вместо этого $timeout. - person racl101; 22.07.2016

Эта фиксация, которая является частью версии 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;

}]);
person Maximilian Hoffmann    schedule 26.05.2013
comment
Какие версии включают этот коммит, пожалуйста? - person XML; 07.08.2013
comment
Последняя нестабильная версия (1.1.5) включает этот коммит. ajax.googleapis.com/ajax/libs/angularjs/ 1.1.5 / angular.min.js - person Maximilian Hoffmann; 11.08.2013
comment
Мне нравится этот менее подробный подход. Было бы неплохо создать обещание из фактического объекта данных и передать его напрямую, но это так мало кода, что он отлично работает. - person Sam Barnum; 23.08.2013
comment
Как ресурс получит доступ к $ routeParams? Например: в GET '/api/1/apps/:appId' - ›App.get({id: $routeParams.appId}).$promise(); я не могу так использовать - person zeronone; 13.02.2014
comment
@zeronone вы вводите $route и используете $route.current.params. Будьте осторожны, $routeParams все еще указывает на старый маршрут. - person Brice Stacey; 26.04.2014

Этот фрагмент кода удобен для внедрения зависимостей (я даже использую его в сочетании с ngmin и uglify), и это более элегантный домен, управляемый < / em> решение на основе.

В приведенном ниже примере регистрируются Phone resource и константа phoneRoutes, которая содержит всю вашу маршрутную информацию для этого (phone ) домен. Что мне не понравилось в предоставленном ответе, так это расположение логики resolve - модуль main не должен знать что-либо или беспокоиться о том, как аргументы ресурса предоставляются контроллеру. Таким образом, логика остается в том же домене.

Примечание. Если вы используете 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;
        }]
      }
    }
  });

Следующим этапом является внедрение данных маршрутизации, когда модуль находится в состоянии настройки, и их применение к $ 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 не задерживает создание ничего, когда он обнаруживает инъекцию чего-нибудь, которое является обещанием < / strong> объект; это сделало бы все оооооооооооооооооо намного проще.

Изменить: используется Angular v1.2 (rc2)

person null    schedule 06.10.2013
comment
Этот отличный ответ кажется более соответствующим философии Angular (инкапсуляция и т. Д.). Мы все должны прилагать сознательные усилия, чтобы не дать логике расползаться по всей кодовой базе, как кудзу. - person zakdances; 23.10.2013
comment
I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise object Я предполагаю, что они упустили эту функцию, потому что она может поощрять шаблоны проектирования, которые негативно влияют на скорость отклика приложений. По их мнению, идеальное приложение - это действительно асинхронное приложение, поэтому решение должно быть крайним случаем. - person zakdances; 23.10.2013

Задержка показа маршрута обязательно приведет к асинхронному запутыванию ... почему бы просто не отслеживать статус загрузки вашего основного объекта и не использовать его в представлении. Например, в вашем контроллере вы можете использовать обратные вызовы успеха и ошибки на ngResource:

$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>
person jpsimons    schedule 03.09.2012
comment
Возможно, показывать статус HTTP представлению неправильно, так же как иметь дело с классами CSS и элементами DOM, принадлежащими контроллеру. Я бы, вероятно, использовал ту же идею, но с абстрактным статусом в isValid () и isLoaded (). - person jpsimons; 08.09.2012
comment
Это действительно не лучшее разделение проблем, плюс то, что он выйдет из строя, если у вас есть вложенные контроллеры, зависящие от конкретного объекта. - person null; 07.10.2013
comment
Это довольно умно ... что касается отображения кодов состояния в представлении, вы можете просто вставить логику http в свойствах области в контроллере, а затем привязать к ним. Кроме того, если вы делаете несколько вызовов ajax, которые происходят в фоновом режиме, вы все равно захотите это сделать. - person KingOfHypocrites; 21.07.2015
comment
Это было бы хорошо, если бы проблема заключалась в простой задержке просмотра. Но разрешение лучше всего использовать, если вам нужно отложить создание экземпляра контроллера, а не только представление. (Пример: если вам нужно быть уверенным, что ваш JSON загружен, потому что ваш контроллер передает его директиве до того, как он будет подключен.) Из документации: маршрутизатор будет ждать, пока все они будут разрешены или один будет отклонен до Контроллер создан. - person Dan; 18.05.2017

Я работал с кодом 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;
  }
}
person Justen    schedule 07.09.2012

Использование AngularJS 1.1.5

Обновление функции «телефоны» в ответе Джастена с использованием синтаксиса AngularJS 1.1.5.

Оригинал:

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 и участникам. :)

Это тоже ответ Максимилиана Гофмана. По-видимому, эта фиксация попала в 1.1.5.

person OJ Raqueño    schedule 28.07.2013
comment
Кажется, я не могу найти ничего о $promise в документации. Возможно, его убрали из версии 2.0+. - person zakdances; 23.10.2013
comment
Он доступен только в версии 1.2. - person Thomas; 06.11.2013

Вы можете использовать свойство $ routeProvider resolve, чтобы отложить изменение маршрута до тех пор, пока данные не будут загружен.

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 определено на маршруте.

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..
});
person Bohdan Lyzanets    schedule 14.11.2014

Мне нравится идея darkporter, потому что команде разработчиков, плохо знакомой с AngularJS, будет легко понять ее и сразу же приступить к работе.

Я создал эту адаптацию, в которой используются 2 блока: один для панели загрузчика, а другой - для фактического содержимого, отображаемого после загрузки данных. Обработка ошибок будет выполняться в другом месте.

Добавьте флаг готовности к $ scope:

$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

person reggoodwin    schedule 27.02.2013
comment
Немного разваливается, если вы загружаете несколько фрагментов данных. Как узнать, все ли загружено? - person toxaq; 14.10.2013
comment
С тех пор, как я добавил этот ответ в феврале, все изменилось, и на этой странице гораздо больше активности. Похоже, что в Angular есть лучшая поддержка для решения этой проблемы, чем то, что предлагается здесь. Ваше здоровье, - person reggoodwin; 16.10.2013
comment
Я приезжаю немного поздно, но работа с несколькими частями данных не имеет большого значения. Вам просто нужно использовать отдельные переменные (логические: isReadyData1, isReadyData2 и т. Д.) Для каждого запроса и установить $ scope.ready = isReadyData1 && isReadyData2 ...; у меня хорошо работает. - person GuillaumeA; 17.11.2013

Мне понравились приведенные выше ответы, и я многому от них научился, но в большинстве из приведенных выше ответов чего-то не хватает.

Я застрял в аналогичном сценарии, когда я разрешал URL-адрес с некоторыми данными, которые были получены в первом запросе с сервера. Проблема, с которой я столкнулся, заключалась в том, что если обещание равно rejected.

Я использовал настраиваемый поставщик, который раньше возвращал Promise, который был разрешен resolve из $routeProvider во время фазы конфигурации.

Я хочу подчеркнуть здесь концепцию when, которая делает что-то вроде этого.

Он видит URL-адрес в строке URL-адресов, а затем соответствующий блок when в вызываемом контроллере, и представление пока упоминается как хорошее.

Допустим, у меня есть следующий код фазы конфигурации.

App.when('/', {
   templateUrl: '/assets/campaigns/index.html',
   controller: 'CampaignListCtr',
   resolve : {
      Auth : function(){
         return AuthServiceProvider.auth('campaign');
      }
   }
})
// Default route
.otherwise({
   redirectTo: '/segments'
});

По корневому URL-адресу в браузере вызывается первый блок выполнения, в противном случае вызывается otherwise.

Давайте представим сценарий, когда я нажимаю rootUrl в адресной строке, вызывается функция AuthServicePrivider.auth().

Допустим, возвращенное обещание находится в состоянии отклонить что тогда ???

Вообще ничего не рендерится.

Блок Otherwise не будет выполняться, как для любого URL-адреса, который не определен в блоке конфигурации и неизвестен фазе конфигурации angularJs.

Нам нужно будет обработать событие, которое запускается, когда это обещание не выполняется. В случае ошибки $routeChangeErorr увольняется $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);
});

IMO Обычно рекомендуется помещать код отслеживания событий в блок выполнения приложения. Этот код запускается сразу после фазы настройки приложения.

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

Таким образом, мы можем справиться с ошибкой обещания на этапе настройки.

person Ashish Singh    schedule 12.01.2015

У меня был сложный многоуровневый интерфейс скользящей панели с отключенным слоем экрана. Создание директивы по отключению слоя экрана, которая создаст событие щелчка для выполнения состояния, например

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

производили эффект мерцания. history.back () вместо этого работал нормально, однако в моем случае он не всегда возвращался в историю. Итак, я обнаружил, что если я просто создаю атрибут href на моем экране отключения вместо state.go, это сработает как шарм.

<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.js я просто сохраняю предыдущее состояние

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

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

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


    });
});
person Dima    schedule 08.01.2016

Одним из возможных решений может быть использование директивы ng-cloak с элементом, в котором мы используем модели, например.

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

Думаю, это требует наименьших усилий.

person Devendra Singh    schedule 03.10.2014