AngularJS: Dienst mit asynchronen Daten initialisieren

475

Ich habe einen AngularJS-Dienst, den ich mit asynchronen Daten initialisieren möchte. Etwas wie das:

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

Offensichtlich funktioniert dies nicht, denn wenn etwas versucht aufzurufen, doStuff()bevor es myDatazurückkommt, erhalte ich eine Nullzeigerausnahme. Soweit ich aus den anderen hier und hier gestellten Fragen ersehen kann , habe ich einige Optionen, aber keine davon scheint sehr sauber zu sein (vielleicht fehlt mir etwas):

Setup Service mit "run"

Gehen Sie beim Einrichten meiner App folgendermaßen vor:

myApp.run(function ($http, MyService) {
    $http.get('data.json').success(function (data) {
        MyService.setData(data);
    });
});

Dann würde mein Service so aussehen:

myModule.service('MyService', function() {
    var myData = null;
    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

Dies funktioniert manchmal, aber wenn die asynchronen Daten länger dauern als es dauert, bis alles initialisiert ist, erhalte ich beim Aufruf eine Nullzeigerausnahme doStuff()

Verwenden Sie Versprechen Objekte

Das würde wahrscheinlich funktionieren. Der einzige Nachteil überall, wo ich MyService anrufe, ist, dass doStuff () ein Versprechen zurückgibt und der gesamte Code für die thenInteraktion mit dem Versprechen erforderlich ist . Ich würde lieber warten, bis myData zurück ist, bevor ich meine Anwendung lade.

Manueller Bootstrap

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 Ich könnte meinen JSON direkt an eine globale Javascript-Variable senden:

HTML:

<script type="text/javascript" src="data.js"></script>

data.js:

var dataForMyService = { 
// myData here
};

Dann wäre es beim Initialisieren verfügbar MyService:

myModule.service('MyService', function() {
    var myData = dataForMyService;
    return {
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

Das würde auch funktionieren, aber dann habe ich eine globale Javascript-Variable, die schlecht riecht.

Sind das meine einzigen Möglichkeiten? Ist eine dieser Optionen besser als die anderen? Ich weiß, dass dies eine ziemlich lange Frage ist, aber ich wollte zeigen, dass ich versucht habe, alle meine Optionen zu erkunden. Jede Anleitung wäre sehr dankbar.

Testen123
quelle
Angular - Bootstrap durchläuft asynchron den Code, um Daten von einem Server mit $httpabzurufen, Daten in einem Dienst zu speichern und dann eine App zu booten.
Steven Wexler

Antworten:

327

Hast du dir das angeschaut $routeProvider.when('/path',{ resolve:{...}? Es kann den Versprechensansatz ein bisschen sauberer machen:

Stellen Sie ein Versprechen in Ihren Dienst:

app.service('MyService', function($http) {
    var myData = null;

    var promise = $http.get('data.json').success(function (data) {
      myData = data;
    });

    return {
      promise:promise,
      setData: function (data) {
          myData = data;
      },
      doStuff: function () {
          return myData;//.getSomeData();
      }
    };
});

Fügen Sie resolveIhrer Routenkonfiguration Folgendes hinzu:

app.config(function($routeProvider){
  $routeProvider
    .when('/',{controller:'MainCtrl',
    template:'<div>From MyService:<pre>{{data | json}}</pre></div>',
    resolve:{
      'MyServiceData':function(MyService){
        // MyServiceData will also be injectable in your controller, if you don't want this you could create a new promise with the $q service
        return MyService.promise;
      }
    }})
  }):

Ihr Controller wird erst instanziiert, wenn alle Abhängigkeiten aufgelöst sind:

app.controller('MainCtrl', function($scope,MyService) {
  console.log('Promise is now resolved: '+MyService.doStuff().data)
  $scope.data = MyService.doStuff();
});

Ich habe ein Beispiel bei plnkr gemacht: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview

Joakimbl
quelle
1
Vielen Dank für Ihre Antwort! Es würde für mich funktionieren, wenn ich nicht bereits einen Dienst in der Auflösungszuordnung hätte, der MyService verwendet. Ich habe Ihren Plunker mit meiner Situation aktualisiert: plnkr.co/edit/465Cupaf5mtxljCl5NuF?p=preview . Gibt es eine Möglichkeit, MyOtherService auf die Initialisierung von MyService warten zu lassen?
Testen123
2
Ich denke, ich würde die Versprechen in MyOtherService verketten - ich habe den Plunker mit Verkettung und einigen Kommentaren aktualisiert - wie sieht das aus? plnkr.co/edit/Z7dWVNA9P44Q72sLiPjW?p=preview
Joakimbl
3
Ich habe dies ausprobiert und bin immer noch auf einige Probleme gestoßen, weil ich Direktiven und andere Controller habe (der Controller, den ich mit $ routeProvider verwende, behandelt das primäre, sekundäre Navigationsmaterial ... das ist 'MyOtherService'), die bis 'MyService' warten müssen ' ist gelöst. Ich werde weiterhin versuchen, dies mit meinem Erfolg zu aktualisieren. Ich wünschte nur, es gäbe einen Haken im Winkel, an dem ich auf die Rückkehr der Daten warten könnte, bevor ich meine Controller und Direktiven initialisiere. Danke nochmal für deine Hilfe. Wenn ich einen Hauptcontroller hätte, der alles einwickelt, hätte das funktioniert.
Testen123
43
Eine Frage hier - wie werden Sie die resolveEigenschaft einem Controller zuweisen, der in nicht erwähnt wird $routeProvider. Zum Beispiel <div ng-controller="IndexCtrl"></div>. Hier wird der Controller explizit erwähnt und nicht über das Routing geladen. Wie würde man dann in einem solchen Fall die Instanziierung des Controllers verzögern?
Callmekatootie
19
Ummm was ist, wenn Sie kein Routing verwenden? Dies ist fast so, als würde man sagen, dass man eine Winkel-App mit asynchronen Daten nur schreiben kann, wenn man Routing verwendet. Der empfohlene Weg, um Daten in eine App zu bekommen, besteht darin, sie asynchron zu laden. Sobald Sie jedoch mehr als einen Controller haben und Dienste einbinden, ist BOOM unmöglich.
Wired_in
88

Basierend auf der Lösung von Martin Atkins finden Sie hier eine vollständige, präzise Lösung für den reinen Winkel:

(function() {
  var initInjector = angular.injector(['ng']);
  var $http = initInjector.get('$http');
  $http.get('/config.json').then(
    function (response) {
      angular.module('config', []).constant('CONFIG', response.data);

      angular.element(document).ready(function() {
          angular.bootstrap(document, ['myApp']);
        });
    }
  );
})();

Diese Lösung verwendet eine selbstausführende anonyme Funktion, um den $ http-Dienst abzurufen, die Konfiguration anzufordern und sie in eine Konstante namens CONFIG einzufügen, sobald sie verfügbar ist.

Sobald dies vollständig ist, warten wir, bis das Dokument fertig ist, und booten dann die Angular-App.

Dies ist eine leichte Verbesserung gegenüber Martins Lösung, bei der das Abrufen der Konfiguration erst nach Fertigstellung des Dokuments verschoben wurde. Soweit ich weiß, gibt es keinen Grund, den $ http-Aufruf dafür zu verzögern.

Unit Testing

Hinweis: Ich habe festgestellt, dass diese Lösung beim Unit-Test nicht gut funktioniert, wenn der Code in Ihrer app.jsDatei enthalten ist. Der Grund dafür ist, dass der obige Code sofort ausgeführt wird, wenn die JS-Datei geladen wird. Dies bedeutet, dass das Test-Framework (in meinem Fall Jasmine) keine Chance hat, eine Scheinimplementierung von bereitzustellen $http.

Meine Lösung, mit der ich nicht ganz zufrieden bin, bestand darin, diesen Code in unsere index.htmlDatei zu verschieben, sodass die Grunt / Karma / Jasmine-Unit-Test-Infrastruktur ihn nicht sieht.

JBCP
quelle
1
Regeln wie "Den globalen Geltungsbereich nicht verschmutzen" sollten nur in dem Maße befolgt werden, in dem sie unseren Code verbessern (weniger komplex, wartbarer, sicherer usw.). Ich kann nicht sehen, wie diese Lösung besser ist, als einfach die Daten in eine einzelne globale Variable zu laden. Was vermisse ich?
David004
4
Sie können das Abhängigkeitsinjektionssystem von Angular verwenden, um in Modulen, die es benötigen, auf die Konstante 'CONFIG' zuzugreifen. Sie riskieren jedoch nicht, andere Module zu blockieren, die dies nicht tun. Wenn Sie beispielsweise eine globale Konfigurationsvariable verwendet haben, besteht die Möglichkeit, dass auch anderer Code von Drittanbietern nach derselben Variablen sucht.
JBCP
1
Ich bin ein eckiger Neuling. Hier sind einige Hinweise, wie ich die Abhängigkeit vom Konfigurationsmodul in meiner App gelöst habe: gist.github.com/dsulli99/0be3e80db9b21ce7b989 ref: tutorials.jenkov.com/angularjs/… Vielen Dank für diese Lösung.
dps
7
Es wird in einem Kommentar in einer der anderen manuellen Bootstrap-Lösungen unten erwähnt, aber als eckiger Neuling, der es nicht erkannt hat, kann ich nur darauf hinweisen, dass Sie Ihre ng-app-Direktive in Ihrem HTML-Code löschen müssen, damit dies ordnungsgemäß funktioniert - Es ersetzt den automatischen Bootstrap (über ng-app) durch diese manuelle Methode. Wenn Sie die ng-App nicht herausnehmen, funktioniert die Anwendung möglicherweise tatsächlich, es werden jedoch verschiedene Fehler unbekannter Anbieter in der Konsole angezeigt.
IrishDubGuy
49

Ich habe einen ähnlichen Ansatz wie den von @XMLilley beschriebenen verwendet, wollte aber die Möglichkeit haben, AngularJS-Dienste $httpzu verwenden, um die Konfiguration zu laden und eine weitere Initialisierung ohne Verwendung von APIs auf niedriger Ebene oder jQuery durchzuführen.

Die Verwendung resolveauf Routen war ebenfalls keine Option, da die Werte beim Starten meiner App auch in module.config()Blöcken als Konstanten verfügbar sein mussten .

Ich habe eine kleine AngularJS-App erstellt, die die Konfiguration lädt, sie als Konstanten für die eigentliche App festlegt und sie bootet.

// define the module of your app
angular.module('MyApp', []);

// define the module of the bootstrap app
var bootstrapModule = angular.module('bootstrapModule', []);

// the bootstrapper service loads the config and bootstraps the specified app
bootstrapModule.factory('bootstrapper', function ($http, $log, $q) {
  return {
    bootstrap: function (appName) {
      var deferred = $q.defer();

      $http.get('/some/url')
        .success(function (config) {
          // set all returned values as constants on the app...
          var myApp = angular.module(appName);
          angular.forEach(config, function(value, key){
            myApp.constant(key, value);
          });
          // ...and bootstrap the actual app.
          angular.bootstrap(document, [appName]);
          deferred.resolve();
        })
        .error(function () {
          $log.warn('Could not initialize application, configuration could not be loaded.');
          deferred.reject();
        });

      return deferred.promise;
    }
  };
});

// create a div which is used as the root of the bootstrap app
var appContainer = document.createElement('div');

// in run() function you can now use the bootstrapper service and shutdown the bootstrapping app after initialization of your actual app
bootstrapModule.run(function (bootstrapper) {

  bootstrapper.bootstrap('MyApp').then(function () {
    // removing the container will destroy the bootstrap app
    appContainer.remove();
  });

});

// make sure the DOM is fully loaded before bootstrapping.
angular.element(document).ready(function() {
  angular.bootstrap(appContainer, ['bootstrapModule']);
});

Sehen Sie es in Aktion ( $timeoutanstelle von $http) hier: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=preview

AKTUALISIEREN

Ich würde empfehlen, den unten von Martin Atkins und JBCP beschriebenen Ansatz zu verwenden.

UPDATE 2

Da ich es in mehreren Projekten brauchte, habe ich gerade ein Bower-Modul veröffentlicht, das sich darum kümmert: https://github.com/philippd/angular-deferred-bootstrap

Beispiel, das Daten aus dem Back-End lädt und eine Konstante namens APP_CONFIG im AngularJS-Modul festlegt:

deferredBootstrapper.bootstrap({
  element: document.body,
  module: 'MyApp',
  resolve: {
    APP_CONFIG: function ($http) {
      return $http.get('/api/demo-config');
    }
  }
});
philippd
quelle
11
deferredBootstrapper ist der
richtige
44

Der Fall "Manueller Bootstrap" kann Zugriff auf Angular-Dienste erhalten, indem vor dem Bootstrap manuell ein Injektor erstellt wird. Dieser anfängliche Injektor ist eigenständig (nicht an Elemente angeschlossen) und enthält nur eine Teilmenge der geladenen Module. Wenn Sie nur Angular-Kerndienste benötigen, können Sie ngdiese wie folgt laden :

angular.element(document).ready(
    function() {
        var initInjector = angular.injector(['ng']);
        var $http = initInjector.get('$http');
        $http.get('/config.json').then(
            function (response) {
               var config = response.data;
               // Add additional services/constants/variables to your app,
               // and then finally bootstrap it:
               angular.bootstrap(document, ['myApp']);
            }
        );
    }
);

Sie können beispielsweise den module.constantMechanismus verwenden, um Daten für Ihre App verfügbar zu machen:

myApp.constant('myAppConfig', data);

Dies myAppConfigkann jetzt wie jeder andere Dienst injiziert werden und ist insbesondere während der Konfigurationsphase verfügbar:

myApp.config(
    function (myAppConfig, someService) {
        someService.config(myAppConfig.someServiceConfig);
    }
);

Bei einer kleineren App können Sie die globale Konfiguration auch direkt in Ihren Dienst einfügen, um das Wissen über das Konfigurationsformat in der gesamten Anwendung zu verbreiten.

Da die asynchronen Vorgänge hier den Bootstrap der Anwendung und damit das Kompilieren / Verknüpfen der Vorlage blockieren, ist es ng-cloaknatürlich ratsam, die Anweisung zu verwenden, um zu verhindern, dass die nicht analysierte Vorlage während der Arbeit angezeigt wird. Sie können auch eine Art Ladeanzeige im DOM bereitstellen, indem Sie HTML-Code bereitstellen, der nur angezeigt wird, bis AngularJS initialisiert wird:

<div ng-if="initialLoad">
    <!-- initialLoad never gets set, so this div vanishes as soon as Angular is done compiling -->
    <p>Loading the app.....</p>
</div>
<div ng-cloak>
    <!-- ng-cloak attribute is removed once the app is done bootstrapping -->
    <p>Done loading the app!</p>
</div>

Ich habe ein vollständiges, funktionierendes Beispiel für diesen Ansatz in Plunker erstellt und die Konfiguration als Beispiel aus einer statischen JSON-Datei geladen.

Martin Atkins
quelle
Ich glaube nicht, dass Sie $ http.get () verschieben müssen, bis das Dokument fertig ist.
JBCP
@JBCP Ja, Sie haben Recht, dass es genauso gut funktioniert, wenn Sie die Ereignisse austauschen, damit wir nicht warten, bis das Dokument fertig ist, bis die HTTP-Antwort zurückgegeben wird, mit dem Vorteil, dass Sie möglicherweise mit dem HTTP beginnen können Anfrage schneller. Nur der Bootstrap-Aufruf muss warten, bis das DOM bereit ist.
Martin Atkins
2
Ich habe ein
Laubenmodul
@MartinAtkins, ich habe gerade festgestellt, dass Ihr großartiger Ansatz mit Angular v1.1 + nicht funktioniert. Es sieht so aus, als ob frühe Versionen von Angular "dann" erst verstehen, wenn die Anwendung gebootet wird. Um es in Ihrem Plunk zu sehen, ersetzen Sie Angular URL durch code.angularjs.org/1.1.5/angular.min.js
vkelman
16

Ich hatte das gleiche Problem: Ich liebe das resolveObjekt, aber das funktioniert nur für den Inhalt von ng-view. Was ist, wenn Sie Controller (z. B. für die Navigation auf oberster Ebene) haben, die außerhalb von ng-view existieren und mit Daten initialisiert werden müssen, bevor das Routing überhaupt beginnt? Wie vermeiden wir es, auf der Serverseite herumzuspielen, damit das funktioniert?

Verwenden Sie einen manuellen Bootstrap und eine Winkelkonstante . Ein naiive XHR liefert Ihnen Ihre Daten und Sie booten in seinem Rückruf eckig, der sich mit Ihren asynchronen Problemen befasst. Im folgenden Beispiel müssen Sie nicht einmal eine globale Variable erstellen. Die zurückgegebenen Daten existieren nur im Winkelbereich als injizierbare Daten und sind nicht einmal in Controllern, Diensten usw. vorhanden, es sei denn, Sie injizieren sie. (Ähnlich wie Sie die Ausgabe Ihres resolveObjekts für eine geroutete Ansicht in den Controller einspeisen würden.) Wenn Sie danach lieber mit diesen Daten als Dienst interagieren möchten, können Sie einen Dienst erstellen, die Daten einfügen, und niemand wird jemals klüger sein .

Beispiel:

//First, we have to create the angular module, because all the other JS files are going to load while we're getting data and bootstrapping, and they need to be able to attach to it.
var MyApp = angular.module('MyApp', ['dependency1', 'dependency2']);

// Use angular's version of document.ready() just to make extra-sure DOM is fully 
// loaded before you bootstrap. This is probably optional, given that the async 
// data call will probably take significantly longer than DOM load. YMMV.
// Has the added virtue of keeping your XHR junk out of global scope. 
angular.element(document).ready(function() {

    //first, we create the callback that will fire after the data is down
    function xhrCallback() {
        var myData = this.responseText; // the XHR output

        // here's where we attach a constant containing the API data to our app 
        // module. Don't forget to parse JSON, which `$http` normally does for you.
        MyApp.constant('NavData', JSON.parse(myData));

        // now, perform any other final configuration of your angular module.
        MyApp.config(['$routeProvider', function ($routeProvider) {
            $routeProvider
              .when('/someroute', {configs})
              .otherwise({redirectTo: '/someroute'});
          }]);

        // And last, bootstrap the app. Be sure to remove `ng-app` from your index.html.
        angular.bootstrap(document, ['NYSP']);
    };

    //here, the basic mechanics of the XHR, which you can customize.
    var oReq = new XMLHttpRequest();
    oReq.onload = xhrCallback;
    oReq.open("get", "/api/overview", true); // your specific API URL
    oReq.send();
})

Jetzt existiert deine NavDataKonstante. Fahren Sie fort und injizieren Sie es in eine Steuerung oder einen Dienst:

angular.module('MyApp')
    .controller('NavCtrl', ['NavData', function (NavData) {
        $scope.localObject = NavData; //now it's addressable in your templates 
}]);

Wenn Sie ein nacktes XHR-Objekt verwenden, werden natürlich einige der Feinheiten entfernt, die $httpJQuery für Sie erledigen würde, aber dieses Beispiel funktioniert ohne besondere Abhängigkeiten, zumindest für einfache get. Wenn Sie etwas mehr Leistung für Ihre Anfrage benötigen, laden Sie eine externe Bibliothek, um Ihnen zu helfen. Aber ich denke nicht, dass es $httpin diesem Zusammenhang möglich ist, auf Winkel oder andere Werkzeuge zuzugreifen .

(SO verwandter Beitrag )

XML
quelle
8

In Ihrer .config für die App können Sie das Auflösungsobjekt für die Route erstellen und in der Funktion $ q (Versprechenobjekt) und den Namen des Dienstes übergeben, von dem Sie abhängig sind, und das Versprechen in der Rückruffunktion für das $ http im Dienst wie folgt:

ROUTENKONFIG

app.config(function($routeProvider){
    $routeProvider
     .when('/',{
          templateUrl: 'home.html',
          controller: 'homeCtrl',
          resolve:function($q,MyService) {
                //create the defer variable and pass it to our service
                var defer = $q.defer();
                MyService.fetchData(defer);
                //this will only return when the promise
                //has been resolved. MyService is going to
                //do that for us
                return defer.promise;
          }
      })
}

Angular rendert die Vorlage nicht und stellt den Controller erst zur Verfügung, wenn defer.resolve () aufgerufen wurde. Das können wir in unserem Service tun:

BEDIENUNG

app.service('MyService',function($http){
       var MyService = {};
       //our service accepts a promise object which 
       //it will resolve on behalf of the calling function
       MyService.fetchData = function(q) {
             $http({method:'GET',url:'data.php'}).success(function(data){
                 MyService.data = data;
                 //when the following is called it will
                 //release the calling function. in this
                 //case it's the resolve function in our
                 //route config
                 q.resolve();
             }
       }

       return MyService;
});

Nachdem MyService die Daten seiner Dateneigenschaft zugewiesen hat und das Versprechen im Routenauflösungsobjekt aufgelöst wurde, wird unser Controller für die Route aktiviert und wir können die Daten aus dem Service unserem Controllerobjekt zuweisen.

REGLER

  app.controller('homeCtrl',function($scope,MyService){
       $scope.servicedata = MyService.data;
  });

Jetzt können alle unsere Bindungen im Rahmen des Controllers die Daten verwenden, die von MyService stammen.

tau
quelle
Ich werde es versuchen, wenn ich mehr Zeit habe. Dies ähnelt dem, was andere in ngModules versucht haben.
Testen123
1
Ich mag diesen Ansatz und habe ihn bereits verwendet, aber ich versuche derzeit herauszufinden, wie dies auf saubere Weise geschehen kann, wenn ich mehrere Routen habe, von denen jede von vorab abgerufenen Daten abhängen kann oder nicht. Irgendwelche Gedanken dazu?
ivarni
Übrigens neige ich dazu, dass jeder Dienst, für den vorab abgerufene Daten erforderlich sind, die Anforderung bei der Initialisierung stellt, ein Versprechen zurückgibt und dann Auflösungsobjekte mit den Diensten einrichtet, die für die verschiedenen Routen erforderlich sind. Ich wünschte nur, es gäbe einen weniger ausführlichen Weg.
ivarni
1
@dewd Das ist das, was ich angestrebt habe, aber ich würde es sehr bevorzugen, wenn es eine Möglichkeit gäbe, einfach zu sagen: "Hol all diese Sachen zuerst, unabhängig davon, welche Route geladen ist", ohne meine Auflösungsblöcke wiederholen zu müssen. Sie alle haben etwas, von dem sie abhängen. Aber es ist keine große Sache, es würde sich nur ein bisschen
trockener
2
Dies ist der Weg, den ich letztendlich eingeschlagen habe, außer dass ich resolveein Objekt mit einer Eigenschaft als Funktion erstellen musste. so endete esresolve:{ dataFetch: function(){ // call function here } }
aron.duby
5

Also habe ich eine Lösung gefunden. Ich habe einen angleJS-Dienst erstellt, wir nennen ihn MyDataRepository, und ich habe ein Modul dafür erstellt. Ich stelle dann diese Javascript-Datei von meinem serverseitigen Controller bereit:

HTML:

<script src="path/myData.js"></script>

Serverseitig:

@RequestMapping(value="path/myData.js", method=RequestMethod.GET)
public ResponseEntity<String> getMyDataRepositoryJS()
{
    // Populate data that I need into a Map
    Map<String, String> myData = new HashMap<String,String>();
    ...
    // Use Jackson to convert it to JSON
    ObjectMapper mapper = new ObjectMapper();
    String myDataStr = mapper.writeValueAsString(myData);

    // Then create a String that is my javascript file
    String myJS = "'use strict';" +
    "(function() {" +
    "var myDataModule = angular.module('myApp.myData', []);" +
    "myDataModule.service('MyDataRepository', function() {" +
        "var myData = "+myDataStr+";" +
        "return {" +
            "getData: function () {" +
                "return myData;" +
            "}" +
        "}" +
    "});" +
    "})();"

    // Now send it to the client:
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/javascript");
    return new ResponseEntity<String>(myJS , responseHeaders, HttpStatus.OK);
}

Ich kann MyDataRepository dann injizieren, wo immer ich es brauche:

someOtherModule.service('MyOtherService', function(MyDataRepository) {
    var myData = MyDataRepository.getData();
    // Do what you have to do...
}

Das hat bei mir sehr gut funktioniert, aber ich bin offen für Rückmeldungen, wenn jemand welche hat. }}

Testen123
quelle
Ich mag Ihren modularen Ansatz. Ich habe festgestellt, dass $ routeScope für den Dienst verfügbar ist, der Daten anfordert, und Sie können ihm im Rückruf $ http.success Daten zuweisen. Die Verwendung von $ routeScope für nicht globale Elemente erzeugt jedoch einen Geruch, und Daten sollten wirklich dem Controller-Bereich $ zugewiesen werden. Leider denke ich, dass Ihr Ansatz zwar innovativ, aber nicht ideal ist (Respekt, wenn Sie etwas finden, das für Sie funktioniert). Ich bin mir nur sicher, dass es eine clientseitige Antwort geben muss, die irgendwie auf die Daten wartet und die Zuordnung zum Bereich ermöglicht. Die Suche geht weiter!
Tau
Für den Fall, dass es für jemanden nützlich ist, habe ich kürzlich verschiedene Ansätze gesehen, die sich mit Modulen befassen, die andere Leute geschrieben und der ngModules-Website hinzugefügt haben. Wenn ich mehr Zeit habe, muss ich entweder eines davon verwenden oder herausfinden, was sie getan haben, und es meinen Sachen hinzufügen.
Testen123
2

Sie können die folgenden Techniken auch verwenden, um Ihren Dienst global bereitzustellen, bevor die tatsächlichen Controller ausgeführt werden: https://stackoverflow.com/a/27050497/1056679 . Lösen Sie Ihre Daten einfach global auf und übergeben Sie sie runbeispielsweise blockweise an Ihren Dienst .

Slava Fomin II
quelle
1

Sie können JSONPdamit Dienstdaten asynchron laden. Die JSONP-Anforderung wird beim ersten Laden der Seite gestellt und die Ergebnisse sind verfügbar, bevor Ihre Anwendung gestartet wird. Auf diese Weise müssen Sie Ihr Routing nicht mit redundanten Auflösungen aufblähen.

Ihr HTML würde so aussehen:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>

function MyService {
  this.getData = function(){
    return   MyService.data;
  }
}
MyService.setData = function(data) {
  MyService.data = data;
}

angular.module('main')
.service('MyService', MyService)

</script>
<script src="/some_data.php?jsonp=MyService.setData"></script>
Hegemon
quelle
-1

Der einfachste Weg, eine Initialisierung abzurufen, ist das ng-init-Verzeichnis.

Platzieren Sie einfach den Bereich ng-init div dort, wo Sie Init-Daten abrufen möchten

index.html

<div class="frame" ng-init="init()">
    <div class="bit-1">
      <div class="field p-r">
        <label ng-show="regi_step2.address" class="show-hide c-t-1 ng-hide" style="">Country</label>
        <select class="form-control w-100" ng-model="country" name="country" id="country" ng-options="item.name for item in countries" ng-change="stateChanged()" >
        </select>
        <textarea class="form-control w-100" ng-model="regi_step2.address" placeholder="Address" name="address" id="address" ng-required="true" style=""></textarea>
      </div>
    </div>
  </div>

index.js

$scope.init=function(){
    $http({method:'GET',url:'/countries/countries.json'}).success(function(data){
      alert();
           $scope.countries = data;
    });
  };

HINWEIS: Sie können diese Methode verwenden, wenn Sie nicht mehr als einen Ort denselben Code haben.

Roni
quelle
Es wird nicht empfohlen, ngInit gemäß den folgenden Dokumenten zu verwenden: docs.angularjs.org/api/ng/directive/ngInit
fodma1