Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

Я вижу, как некоторые люди спрашивают, как это сделать, используя метод 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';
}]);

Так как этот код выведен из ответа на вопрос/самый популярный, он не проверен, но он должен отправить вас в правильном направлении, если вы уже понимаете, как сделать дружественный для пользователя код angular. Одна часть, которую не требовал мой собственный код, - это инъекция "телефона" в функцию разрешения для "телефонов", и я вообще не использовал какой-либо объект "delay".

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

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

FYI, я заранее использую общий контроллер, который помогает мне делать 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 ....
    ]

Ответ 4

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

}]);

Ответ 5

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

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

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

Вы можете увидеть это в полной славе в мой последний (предстоящий) эксперимент. Хотя этот метод отлично подходит для меня, я действительно удивляюсь, почему инжектор $не задерживает строительство чего-либо, когда он обнаруживает инъекцию всего, что является обещанием; это сделало бы вещи soooOOOOoooOOOOO намного проще.

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

Ответ 6

Отсрочка отображения маршрута обязательно приведет к асинхронному запуску... почему бы просто не отслеживать статус загрузки вашего основного объекта и использовать его в представлении. Например, в вашем контроллере вы можете использовать как успешные, так и обратные вызовы ошибок на 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>

Ответ 7

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

Ответ 8

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

Обновление функции "телефонов" в ответе Justen с помощью синтаксиса 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.

Ответ 9

Вы можете использовать свойство $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 - constant объекты, определенные в том же файле, что и контроллеры 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..
});

Ответ 10

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

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

Добавьте флаг "ready" в область $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

Ответ 11

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

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

Я использовал пользовательский поставщик, который использовал для возврата Promise, который был разрешен resolve of $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);
});

ИМО. Как правило, рекомендуется добавить код отслеживания событий в блок запуска приложения. Этот код запускается сразу после фазы конфигурации приложения.

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

Таким образом, мы можем справиться с обещанием сбой во время фазы конфигурации.

Ответ 12

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

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


    });
});

Ответ 13

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

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

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