Opóźnianie zmiany trasy AngularJS do momentu załadowania modelu, aby zapobiec migotaniu

321

Zastanawiam się, czy istnieje sposób (podobny do Gmaila), aby AngularJS opóźniał wyświetlanie nowej trasy, aż do momentu, gdy każdy model i jego dane zostaną pobrane za pomocą odpowiednich usług.

Na przykład, gdyby istniała ProjectsControllerlista, która zawierała wszystkie projekty i project_index.htmlktóra była szablonem pokazującym te projekty, Project.query()zostałaby całkowicie pobrana przed wyświetleniem nowej strony.

Do tego czasu stara strona nadal będzie się wyświetlać (na przykład gdybym przeglądał inną stronę, a następnie zdecydowałbym zobaczyć ten indeks projektu).

Misko Hevery
źródło

Odpowiedzi:

374

$ routeProvider resolve właściwość umożliwia opóźnienie zmiany trasy, aż dane zostaną załadowane.

Najpierw zdefiniuj trasę z resolvetakim atrybutem.

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

zauważ, że resolvewłaściwość jest zdefiniowana na trasie.

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

Zauważ, że definicja kontrolera zawiera obiekt rozstrzygający, który deklaruje rzeczy, które powinny być dostępne dla konstruktora kontrolera. Tutaj phonesjest wstrzykiwany do kontrolera i jest zdefiniowany we resolvewłaściwości.

resolve.phonesFunkcja jest odpowiedzialna za zwrot obietnicę. Wszystkie obietnice są zbierane, a zmiana trasy jest opóźniona do momentu, aż wszystkie obietnice zostaną rozwiązane.

Działające demo: http://mhevery.github.com/angular-phonecat/app/#/phones Źródło: https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831

Misko Hevery
źródło
10
@MiskoHevery - co jeśli kontrolery znajdują się w module i są zdefiniowane jako ciąg, a nie funkcja. Jak możesz ustawić atrybut resolver tak jak Ty?
aar0n
53
Jak to jest używane w angular.controller()definicjach kontrolera typu? W $routeProviderrzeczy, pomyślałem, trzeba było używać nazw ciągów kontrolerów.
Ben Lesh,
6
Dowolny przykład z wykorzystaniem angular.controller () i najnowszej wersji AngularJS?
Laurent
22
@ rozwiązywanie problemów, gdy używasz angular.controller(), możesz przypisać wynik działania tej funkcji do zmiennej ( var MyCtrl = angular.controller(...)), a następnie pracować z nią dalej ( MyCtrl.loadData = function(){..}). Sprawdź wideo egghead
petrkotek 21.04.2013
17
Nadal chciałbym mieć dobry sposób na zrobienie tego bez umieszczania kontrolera w globalnym. Nie chcę zaśmiecać globaliami wszędzie. Możesz to zrobić ze stałą, ale fajnie byłoby umieścić funkcję resolvu na / w kontrolerze, a nie gdzie indziej.
Erik Honn,
51

Oto minimalny działający przykład, który działa w Angular 1.0.2

Szablon:

<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/

Usprawniona wersja:

Ponieważ $ http () już zwraca obietnicę (inaczej odroczoną), tak naprawdę nie musimy tworzyć własnej. Możemy więc uprościć MyCtrl. zdecydować się na:

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

Wynik $ http () zawiera dane , status , nagłówki i obiekty konfiguracji , dlatego musimy zmienić treść MyCtrl na:

$scope.datasets = datasets.data;

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

MB21
źródło
Próbuję zrobić coś takiego, ale mam problem z wprowadzaniem „zestawów danych”, ponieważ nie są one zdefiniowane. jakieś pomysły?
Rob Bygrave,
Hej mb21, myślę, że możesz mi pomóc z tym pytaniem: stackoverflow.com/questions/14271713/...
winduptoy
Czy ktoś mógłby mi pomóc przekonwertować tę odpowiedź na format app.controller („MyCtrl”)? jsfiddle.net/5usya/1 nie działało dla mnie.
user1071182 15.04.13
Unknown provider: datasetsProvider <- datasets
pojawia
Możesz uprościć swoją odpowiedź, zastępując zestawy danych tym:function($http) { return $http({method: 'GET', url: '/someUrl'}) .then( function(data){ return data;}, function(reason){return 'error value';} ); }
Morteza Tourani
32

Widzę, że niektórzy ludzie pytają, jak to zrobić za pomocą metody angular.controller z przyjaznym dla minimalizacji zastrzykiem zależności. Odkąd właśnie to uruchomiłem, czułem się zobowiązany do powrotu i pomocy. Oto moje rozwiązanie (zaczerpnięte z pierwotnego pytania i odpowiedzi 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';
}]);

Ponieważ ten kod pochodzi z pytania / najpopularniejszej odpowiedzi, nie został przetestowany, ale powinien wysłać cię we właściwym kierunku, jeśli już wiesz, jak zrobić kod kątowy przyjazny dla minimalizacji. Jedyną częścią, której mój własny kod nie wymagał, było wstrzyknięcie „Telefonu” do funkcji rozstrzygania dla „telefonów”, a ja w ogóle nie użyłem żadnego obiektu „opóźnienia”.

Polecam również ten film na youtube http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp , co bardzo mi pomogło

Jeśli cię to zainteresuje, postanowiłem wkleić własny kod (napisany w coffeescript), abyś mógł zobaczyć, jak to działa.

Do twojej wiadomości, z góry używam ogólnego kontrolera, który pomaga mi zrobić CRUD na kilku modelach:

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 ....
    ]
bitwit
źródło
Czy wnioskuję poprawnie z twojego przykładu i moich nieudanych prób, że nie można teraz odwoływać się do resolvefunkcji w kontrolerze, w najnowszych wersjach Angulara? Czyli trzeba to zadeklarować bezpośrednio w konfiguracji, tak jak jest tutaj?
XML,
@XMLilley Jestem pewien, że tak jest. Ten przykład pochodzi z 1.1.2, kiedy go napisałem. Nie widziałem żadnej dokumentacji dotyczącej umieszczania
rozdzielczości
2
Fajne dzięki. Istnieje wiele przykładów takich działań na SO (takich jak dwie najlepsze tutaj), ale wszystkie pochodzą z 2012 roku i początku 2013 roku. To eleganckie podejście, ale wydaje się być przestarzałe. Najczystszą alternatywą wydaje się teraz pisanie indywidualnych usług, które są przedmiotami obiecanymi.
XML,
Dzięki, że to działało dla mnie. Dla każdego, kto dostaje błędy dotyczące niezdefiniowanej $deferusługi, zwróć uwagę, że w wersji 1.5.7 AngularJS, $timeoutzamiast tego chcesz użyć .
racl101
18

To zatwierdzenie , które jest częścią wersji 1.1.5 i nowszych, udostępnia $promiseobiekt $resource. Wersje ngResource, w tym ten zatwierdzenie, pozwalają na rozwiązywanie takich zasobów:

$ routeProvider

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

kontroler

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

    $scope.data = data;

}]);
Maksymilian Hoffmann
źródło
Które wersje zawierają to zatwierdzenie, proszę?
XML,
Najnowsza niestabilna wersja (1.1.5) zawiera to zatwierdzenie. ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js
Maximilian Hoffmann
Podoba mi się to mniej szczegółowe podejście. Byłoby miło stworzyć obietnicę z rzeczywistego obiektu danych i przekazać ją bezpośrednio, ale jest to tak mało kodu, że działa dobrze.
Sam Barnum,
1
W jaki sposób zasób uzyska dostęp do $ routeParams? Na przykład: in GET '/api/1/apps/:appId'-> App.get({id: $routeParams.appId}).$promise();Nie mogę użyć w ten sposób
zeronone
2
@zeronone wstrzykujesz $routedo swojego rozwiązania i używasz $route.current.params. Uważaj, $routeParamswciąż wskazuje starą trasę.
Brice Stacey
16

Ten fragment kodu jest przyjazny dla wstrzykiwania zależności (używam go nawet w połączeniu ngmin i uglify ) i jest bardziej eleganckim rozwiązaniem opartym na domenie .

Poniższy przykład rejestruje zasób telefonu i stały phoneRoutes , który zawiera wszystkie informacje o routingu dla tej domeny (telefonu). W podanej odpowiedzi nie podobało mi się położenie logiki rozwiązywania - moduł główny nie powinien nic wiedzieć ani przejmować się sposobem dostarczania argumentów zasobów do kontrolera. W ten sposób logika pozostaje w tej samej domenie.

Uwaga: jeśli używasz ngmin (a jeśli nie: powinieneś), musisz tylko napisać funkcje rozpoznawania z konwencją tablic 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;
        }]
      }
    }
  });

Następnym elementem jest wstrzyknięcie danych routingu, gdy moduł jest w stanie konfiguracji i zastosowanie go do $ 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: '/' });

});

Testowanie konfiguracji trasy za pomocą tej konfiguracji jest również dość łatwe:

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

Możesz to zobaczyć w pełnej krasie w moim ostatnim (nadchodzącym) eksperymencie . Chociaż ta metoda działa dla mnie dobrze, naprawdę zastanawiam się, dlaczego $ injector nie opóźnia budowy czegokolwiek, gdy wykrywa zastrzyk czegokolwiek , co jest przedmiotem obietnicy ; znacznie ułatwiłoby to tak bardzoOOOOOOOooOOOOO.

Edycja: używane Angular v1.2 (rc2)

zero
źródło
2
Ta doskonała odpowiedź wydaje się bardziej zgodna z filozofią „Angular” (enkapsulacja itp.). Wszyscy powinniśmy podejmować świadomy wysiłek, aby powstrzymać logikę przed pełzaniem po całej bazie kodu jak kudzu.
zakdances
I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise objectDomyślam się, że pominęli tę funkcjonalność, ponieważ może zachęcać do wzorców projektowych, które negatywnie wpływają na czas reakcji aplikacji. Idealna aplikacja w ich opinii jest naprawdę asynchroniczna, więc rozwiązywanie powinno być przypadkowe.
zakdances
11

Opóźnianie pokazania trasy z pewnością doprowadzi do asynchronicznej plątaniny ... dlaczego nie po prostu śledzić status ładowania głównego obiektu i używać go w widoku. Na przykład w kontrolerze możesz użyć zarówno wywołań powodzenia, jak i błędów w ngResource:

$scope.httpStatus = 0; // in progress
$scope.projects = $resource.query('/projects', function() {
    $scope.httpStatus = 200;
  }, function(response) {
    $scope.httpStatus = response.status;
  });

Następnie w widoku możesz zrobić cokolwiek:

<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>
jpsimons
źródło
6
Być może ujawnianie statusu HTTP w widoku nie jest już właściwe, niż zajmowanie się klasami CSS, a elementy DOM należą do kontrolera. Prawdopodobnie skorzystałbym z tego samego pomysłu, ale status abstrakcyjny jest wyłączony w isValid () i isLoaded ().
jpsimons
1
To naprawdę nie jest najlepszy rozdział problemów, a dodatkowo zawiesi się, jeśli zagnieżdżone są kontrolery zależne od konkretnego obiektu.
null
Jest to dość sprytne .. jeśli chodzi o wyświetlanie kodów stanu w widoku, możesz po prostu przykleić logikę http do właściwości zakresu w kontrolerze, a następnie powiązać je z nimi. Również jeśli wykonujesz wiele wywołań ajax, które mają miejsce w tle, i tak będziesz chciał to zrobić.
KingOfHypocrites
Byłoby dobrze, gdyby kwestia polegała na opóźnieniu widoku. Ale rozwiązanie najlepiej jest zastosować, jeśli chcesz opóźnić tworzenie instancji kontrolera - nie tylko widok. (Np .: Jeśli musisz się upewnić, że JSON jest załadowany, ponieważ kontroler przekazuje go do dyrektywy, zanim zostanie podłączony). Z dokumentów: „router będzie czekał na ich rozstrzygnięcie lub odrzucenie jednego zanim kontroler zostanie utworzony ”.
Dan
8

Pracowałem z powyższego kodu Misko i właśnie to zrobiłem. Jest to bardziej aktualne rozwiązanie, ponieważ $deferzostało zmienione na $timeout. Zastąpienie $timeoutspowoduje jednak, że upłynie limit czasu (w kodzie Misko: 1 sekunda), a następnie zwróci dane z nadzieją, że czas zostanie rozwiązany. W ten sposób zwraca jak najszybciej.

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;
  }
}
Justen
źródło
7

Korzystanie z AngularJS 1.1.5

Aktualizacja funkcji „telefonów” w odpowiedzi Justena przy użyciu składni AngularJS 1.1.5 .

Oryginalny:

phones: function($q, Phone) {
    var deferred = $q.defer();

    Phone.query(function(phones) {
        deferred.resolve(phones);
    });

    return deferred.promise;
}

Zaktualizowano:

phones: function(Phone) {
    return Phone.query().$promise;
}

Znacznie krócej dzięki zespołowi Angular i współpracownikom. :)

To także odpowiedź Maximiliana Hoffmanna. Najwyraźniej zatwierdzenie przeszło do wersji 1.1.5.

Dz.U. Raqueño
źródło
1
Nie mogę znaleźć niczego $promisew dokumentach . Być może został wyjęty od wersji 2.0 +.
zakdances
Jest dostępny tylko w wersji 1.2
Thomas
5

Możesz użyć właściwości $ routeProvider resolver do opóźnienia zmiany trasy do momentu załadowania danych.

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

Zauważ, że resolvewłaściwość jest zdefiniowana na trasie.

EntitiesCtrlResolvei EntityCtrlResolvejest stałymi obiektami zdefiniowanymi w tym samym pliku co EntitiesCtrli EntityCtrlkontrolery.

// 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..
});
Bohdan Lyzanets
źródło
3

Podoba mi się pomysł darkportera, ponieważ zespołowi deweloperów AngularJS będzie łatwo zrozumieć i od razu pracować.

Stworzyłem tę adaptację, która wykorzystuje 2 elementy div, jedną dla paska ładowania, a drugą dla rzeczywistej zawartości wyświetlanej po załadowaniu danych. Obsługa błędów byłaby wykonywana gdzie indziej.

Dodaj flagę „ready” do $ scope:

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

W widoku 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>

Zobacz także: Dokumenty paska postępu Boostrap

reggoodwin
źródło
Trochę się rozpada, jeśli ładujesz wiele elementów danych. Skąd wiesz, czy wszystko załadowane?
toxaq
Sprawy potoczyły się od czasu, gdy dodałem tę odpowiedź w lutym, z większą aktywnością na tej stronie. Wygląda na to, że w Angular jest lepsze wsparcie w rozwiązywaniu tego problemu, niż jest to sugerowane tutaj. Pozdrawiam,
reggoodwin
Spóźniam się trochę, ale zajmowanie się wieloma fragmentami danych nie jest wielkim problemem. Musisz po prostu użyć osobnych zmiennych (booleans: isReadyData1, isReadyData2 itd.) Dla każdego żądania i ustawić $ scope.ready = isReadyData1 && isReadyData2 ...; działa dobrze dla mnie.
GuillaumeA
1

Podobały mi się powyższe odpowiedzi i wiele się od nich nauczyłem, ale w większości powyższych odpowiedzi brakuje czegoś.

Utknąłem w podobnym scenariuszu, w którym rozwiązywałem adres URL z danymi, które są pobierane w pierwszym żądaniu z serwera. Problem, z którym się spotkałem, polegał na tym, że jeśli taka jest obietnica rejected.

I był przy użyciu niestandardowego dostawcy, który używany do zwróci Promisektóry został rozwiązany przez resolvez $routeProviderw czasie fazy config.

Chciałbym tutaj podkreślić, że koncepcja whentego robi coś takiego.

Widzi adres URL w pasku adresu URL, a następnie odpowiedni whenblok w nazwie kontrolera, a widok jest do tej pory odsyłany tak dobrze.

Powiedzmy, że mam następujący kod fazy konfiguracji.

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

Na adresie URL root w przeglądarce wywoływany otherwisejest pierwszy blok uruchamiania, w przeciwnym razie wywoływany.

Wyobraźmy sobie scenariusz, w którym uderzyłem rootUrl w AuthServicePrivider.auth()funkcji paska adresu .

Powiedzmy, że Obietnica zwrócona jest w stanie odrzucenia, co wtedy ???

Nic się nie renderuje.

Otherwise blok nie zostanie wykonany tak, jak w przypadku dowolnego adresu URL, który nie jest zdefiniowany w bloku konfiguracji i nie jest znany fazie konfiguracji angularJs.

Będziemy musieli poradzić sobie ze zdarzeniem, które zostanie zwolnione, gdy ta obietnica nie zostanie rozwiązana. W przypadku awarii $routeChangeErorrzostaje zwolniony $rootScope.

Można go przechwycić, jak pokazano w kodzie poniżej.

$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 Generalnie dobrym pomysłem jest umieszczenie kodu śledzenia zdarzeń w bloku uruchamiania aplikacji. Ten kod jest uruchamiany tuż po fazie konfiguracji aplikacji.

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

W ten sposób poradzimy sobie z niepowodzeniem obietnicy w fazie konfiguracji.

Ashish Singh
źródło
0

Miałem złożony wielopoziomowy interfejs panelu przesuwnego z wyłączoną warstwą ekranu. Tworzenie dyrektywy dotyczącej wyłączania warstwy ekranu, która tworzyłaby zdarzenie click, aby wykonać stan podobny do

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

wywoływały efekt trzepotania. history.back () zamiast tego działało dobrze, jednak w moim przypadku nie zawsze wraca do historii. Przekonałem się więc, że jeśli po prostu utworzę atrybut href na moim ekranie wyłączania zamiast state.go, działałoby to jak urok.

<a class="disable-screen" back></a>

Dyrektywa „wstecz”

app.directive('back', [ '$rootScope', function($rootScope) {

    return {
        restrict : 'A',
        link : function(scope, element, attrs) {
            element.attr('href', $rootScope.previousState.replace(/\./gi, '/'));
        }
    };

} ]);

app.js Po prostu zapisuję poprzedni stan

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

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

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


    });
});
Dima
źródło
-2

Jednym z możliwych rozwiązań może być użycie dyrektywy ng-cloak z elementem, w którym używamy modeli np

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

Myślę, że ten wymaga najmniejszego wysiłku.

Devendra Singh
źródło