Mam usługę AngularJS, którą chcę zainicjować za pomocą niektórych danych asynchronicznych. Coś takiego:
myModule.service('MyService', function($http) {
var myData = null;
$http.get('data.json').success(function (data) {
myData = data;
});
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Oczywiście to nie zadziała, ponieważ jeśli coś spróbuje zadzwonić doStuff()
przed myData
powrotem, otrzymam wyjątek wskaźnika zerowego. O ile mogę stwierdzić po przeczytaniu niektórych innych pytań zadanych tutaj i tutaj mam kilka opcji, ale żadne z nich nie wydaje się bardzo czyste (być może czegoś mi brakuje):
Usługa instalacyjna z „uruchom”
Podczas konfigurowania mojej aplikacji wykonaj następujące czynności:
myApp.run(function ($http, MyService) {
$http.get('data.json').success(function (data) {
MyService.setData(data);
});
});
Wtedy moja usługa wyglądałaby tak:
myModule.service('MyService', function() {
var myData = null;
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Działa to czasami, ale jeśli dane asynchroniczne zdarzają się dłużej niż potrzeba, aby wszystko zostało zainicjowane, otrzymuję wyjątek wskaźnika zerowego podczas wywoływania doStuff()
Użyj obiektów obietnicy
To prawdopodobnie zadziałałoby. Jedynym minusem jest to, że wszędzie, gdzie nazywam MyService, będę musiał wiedzieć, że doStuff () zwraca obietnicę i cały kod będzie musiał do nas wejść then
w interakcję z obietnicą. Wolę poczekać, aż myData wróci przed załadowaniem mojej aplikacji.
Ręczny pasek startowy
angular.element(document).ready(function() {
$.getJSON("data.json", function (data) {
// can't initialize the data here because the service doesn't exist yet
angular.bootstrap(document);
// too late to initialize here because something may have already
// tried to call doStuff() and would have got a null pointer exception
});
});
Global JavaScript Var Mógłbym wysłać mój JSON bezpośrednio do globalnej zmiennej JavaScript:
HTML:
<script type="text/javascript" src="data.js"></script>
data.js:
var dataForMyService = {
// myData here
};
Wtedy byłby dostępny przy inicjalizacji MyService
:
myModule.service('MyService', function() {
var myData = dataForMyService;
return {
doStuff: function () {
return myData.getSomeData();
}
};
});
To też by działało, ale mam globalną zmienną javascript, która źle pachnie.
Czy to moje jedyne opcje? Czy jedna z tych opcji jest lepsza od innych? Wiem, że to dość długie pytanie, ale chciałem pokazać, że próbowałem zbadać wszystkie moje opcje. Wszelkie wskazówki będą mile widziane.
źródło
$http
, a następnie zapisać dane w usłudze, a następnie uruchomić aplikację.Odpowiedzi:
Rzuciłeś okiem
$routeProvider.when('/path',{ resolve:{...}
? Może sprawić, że obietnica stanie się nieco czystsza:Ujawnij obietnicę w swojej usłudze:
Dodaj
resolve
do konfiguracji trasy:Twój kontroler nie zostanie utworzony, dopóki wszystkie zależności nie zostaną rozwiązane:
Zrobiłem przykład na plnkr: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
źródło
resolve
właściwość do kontrolera, który nie jest wymieniony w$routeProvider
. Na przykład<div ng-controller="IndexCtrl"></div>
. Tutaj kontroler jest wyraźnie wymieniony i nie jest ładowany przez routing. W takim przypadku, w jaki sposób opóźnić wystąpienie kontrolera?W oparciu o rozwiązanie Martina Atkinsa, oto kompletne, zwięzłe rozwiązanie czysto kątowe:
To rozwiązanie korzysta z samoczynnie wykonującej się funkcji anonimowej, aby uzyskać usługę $ http, zażądać konfiguracji i wstrzyknąć ją do stałej o nazwie CONFIG, gdy będzie dostępna.
Po zakończeniu czekamy, aż dokument będzie gotowy, a następnie uruchamiamy aplikację Angular.
Jest to niewielkie ulepszenie w stosunku do rozwiązania Martina, które odroczyło pobieranie konfiguracji do momentu, gdy dokument będzie gotowy. O ile mi wiadomo, nie ma powodu opóźniać połączenia $ http z tego powodu.
Testów jednostkowych
Uwaga: Odkryłem, że to rozwiązanie nie działa dobrze podczas testowania jednostkowego, gdy kod jest zawarty w twoim
app.js
pliku. Powodem tego jest to, że powyższy kod jest uruchamiany natychmiast po załadowaniu pliku JS. Oznacza to, że środowisko testowe (w moim przypadku Jasmine) nie ma szansy zapewnić fałszywej implementacji$http
.Moim rozwiązaniem, z którego nie jestem w pełni usatysfakcjonowany, było przeniesienie tego kodu do naszego
index.html
pliku, więc infrastruktura testów jednostkowych Grunt / Karma / Jasmine tego nie widzi.źródło
Użyłem podobnego podejścia do opisanego przez @XMLilley, ale chciałem mieć możliwość korzystania z usług AngularJS, takich jak
$http
ładowanie konfiguracji i przeprowadzanie dalszej inicjalizacji bez użycia interfejsów API niskiego poziomu lub jQuery.Używanie
resolve
na trasach również nie było opcją, ponieważ potrzebowałem, aby wartości były dostępne jako stałe, gdy moja aplikacja jest uruchomiona, nawet wmodule.config()
blokach.Stworzyłem małą aplikację AngularJS, która ładuje konfigurację, ustawia je jako stałe w rzeczywistej aplikacji i ładuje ją.
Zobacz to w akcji (używając
$timeout
zamiast$http
) tutaj: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=previewAKTUALIZACJA
Poleciłbym zastosować podejście opisane poniżej przez Martina Atkinsa i JBCP.
AKTUALIZACJA 2
Ponieważ potrzebowałem go w wielu projektach, właśnie wydałem moduł altany, który zajmuje się tym: https://github.com/philippd/angular-deferred-bootstrap
Przykład, który ładuje dane z zaplecza i ustawia stałą o nazwie APP_CONFIG w module AngularJS:
źródło
Przypadek „ręcznego ładowania” może uzyskać dostęp do usług Angular poprzez ręczne utworzenie wtryskiwacza przed rozpoczęciem ładowania. Ten początkowy wtryskiwacz będzie samodzielny (nie będzie dołączany do żadnych elementów) i będzie zawierał tylko podzbiór modułów, które są załadowane. Jeśli wszystko, czego potrzebujesz, to podstawowe usługi Angular, wystarczy po prostu załadować
ng
:Możesz na przykład użyć
module.constant
mechanizmu, aby udostępnić dane swojej aplikacji:To
myAppConfig
może teraz być wstrzykiwane tak jak wszystkie inne usługi, a w szczególności jest ona dostępna w fazie konfiguracji:lub, w przypadku mniejszej aplikacji, możesz po prostu wstrzyknąć globalną konfigurację bezpośrednio do swojej usługi, kosztem rozpowszechniania wiedzy o formacie konfiguracji w całej aplikacji.
Oczywiście, ponieważ operacje asynchroniczne blokują tutaj bootstrap aplikacji, a tym samym blokują kompilację / linkowanie szablonu, rozsądnie jest użyć
ng-cloak
dyrektywy, aby zapobiec wyświetlaniu niesparowanego szablonu podczas pracy. Możesz również podać jakieś wskazanie ładowania w DOM, dostarczając trochę HTML, który jest pokazywany tylko do momentu zainicjowania AngularJS:Stworzyłem kompletny, działający przykład tego podejścia na Plunkerze, ładując konfigurację ze statycznego pliku JSON jako przykład.
źródło
Miałem ten sam problem: uwielbiam ten
resolve
obiekt, ale działa to tylko w przypadku zawartości ng-view. Co się stanie, jeśli masz kontrolery (powiedzmy na przykład na najwyższym poziomie), które istnieją poza ng-view i które muszą zostać zainicjowane danymi, zanim jeszcze zacznie się routing? W jaki sposób unikamy chowania się po stronie serwera, aby to działało?Użyj ręcznego paska startowego i stałej kątowej . Naiwny XHR pobiera twoje dane, a Ty odruchowo uruchamiasz w swoim wywołaniu zwrotnym, który zajmuje się problemami z asynchronizacją. W poniższym przykładzie nie musisz nawet tworzyć zmiennej globalnej. Zwracane dane istnieją tylko w zakresie kątowym jako wstrzykiwalne i nie są nawet obecne w kontrolerach, usługach itp., Chyba że zostaną wstrzyknięte. (Tak, jakbyś wstrzyknął swoje wyjście
resolve
obiektu do kontrolera w celu uzyskania widoku trasy). Jeśli wolisz później interakcję z tymi danymi jako usługą, możesz utworzyć usługę, wstrzyknąć dane, a nikt nigdy nie będzie mądrzejszy .Przykład:
Teraz twoja
NavData
stała istnieje. Śmiało i wstrzyknij go do kontrolera lub usługi:Oczywiście użycie nagiego obiektu XHR usuwa wiele drobiazgów, o które
$http
zadbałby Tobie JQuery, ale ten przykład działa bez specjalnych zależności, przynajmniej dla prostejget
. Jeśli potrzebujesz trochę więcej mocy na żądanie, załaduj bibliotekę zewnętrzną, aby Ci pomóc. Ale nie sądzę, że można uzyskać dostęp do kątownika$http
w tym kontekście narzędzi lub innych narzędzi.( Post związany z SO )
źródło
To, co możesz zrobić, znajduje się w .config dla aplikacji, aby utworzyć obiekt rozstrzygania trasy i przekazać funkcję w $ q (obiekt przyrzeczenia) i nazwę usługi, od której zależysz, i rozwiązać obietnicę w funkcja oddzwaniania dla $ http w usłudze tak:
KONFIGURACJA TRASY
Angular nie wyświetli szablonu ani nie udostępni kontrolera, dopóki nie zostanie wywołana defer.resolve (). Możemy to zrobić w naszym serwisie:
USŁUGA
Teraz, gdy MyService ma przypisane dane do swojej właściwości data, a obietnica zawarta w obiekcie rozstrzygania trasy została rozwiązana, nasz kontroler trasy rozpoczyna się i możemy przypisać dane z usługi do obiektu kontrolera.
KONTROLER
Teraz wszystkie nasze powiązania w zakresie administratora będą mogły korzystać z danych pochodzących z MyService.
źródło
resolve
obiekt z właściwością będącą funkcją. więc skończyło się naresolve:{ dataFetch: function(){ // call function here } }
Więc znalazłem rozwiązanie. Stworzyłem usługę angularJS, nazwiemy ją MyDataRepository i stworzyłem dla niej moduł. Następnie podaję ten plik javascript z kontrolera po stronie serwera:
HTML:
Po stronie serwera:
Następnie mogę wstrzyknąć MyDataRepository tam, gdzie jest to potrzebne:
Działa to dla mnie świetnie, ale jestem otwarty na wszelkie opinie, jeśli ktoś je ma. }
źródło
Ponadto przed użyciem rzeczywistych kontrolerów można użyć następujących technik do globalnego świadczenia usług: https://stackoverflow.com/a/27050497/1056679 . Po prostu rozwiąż swoje dane globalnie, a następnie przekaż je
run
na przykład do usługi blokowo.źródło
Możesz użyć
JSONP
do asynchronicznego ładowania danych usługi. Żądanie JSONP zostanie wykonane podczas początkowego ładowania strony, a wyniki będą dostępne przed uruchomieniem aplikacji. W ten sposób nie będziesz musiał nadmuchać routingu za pomocą nadmiarowych rozwiązań.Twój HTML wyglądałby tak:
źródło
Najprostszym sposobem na pobranie dowolnego katalogu inicjalizacji jest użycie katalogu ng-init.
Wystarczy umieścić zakres div ng-init div w miejscu, w którym chcesz pobrać dane init
index.html
index.js
źródło