Aktualisieren Sie den Bereichswert, wenn die Servicedaten geändert werden


76

Ich habe den folgenden Dienst in meiner App:

uaInProgressApp.factory('uaProgressService', 
    function(uaApiInterface, $timeout, $rootScope){
        var factory = {};
        factory.taskResource = uaApiInterface.taskResource()
        factory.taskList = [];
        factory.cron = undefined;
        factory.updateTaskList = function() {
            factory.taskResource.query(function(data){
                factory.taskList = data;
                $rootScope.$digest
                console.log(factory.taskList);
            });
            factory.cron = $timeout(factory.updateTaskList, 5000);
        }

        factory.startCron = function () {
            factory.cron = $timeout(factory.updateTaskList, 5000);
        }

        factory.stopCron = function (){
            $timeout.cancel(factory.cron);
        }
        return factory;
});

Dann benutze ich es in einem Controller wie diesem:

uaInProgressApp.controller('ua.InProgressController',
    function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
        uaContext.getSession().then(function(){
            uaContext.appName.set('Testing house');
            uaContext.subAppName.set('In progress');
            uaProgressService.startCron();

            $scope.taskList = uaProgressService.taskList;
        });
    }
);

Also im Grunde meine Service - Update factory.taskListalle 5 Sekunden und ich verknüpfen diese factory.taskListzu $scope.taskList. Ich habe dann verschiedene Methoden ausprobiert $apply, $digestaber Änderungen an factory.taskListwerden nicht in meinem Controller und meiner Ansicht wiedergegeben $scope.taskList.

Es bleibt in meiner Vorlage leer. Wissen Sie, wie ich diese Änderungen verbreiten kann?

Antworten:


79

Während dem Benutzen $watch das Problem lösen kann, ist es nicht die effizienteste Lösung. Möglicherweise möchten Sie die Art und Weise ändern, in der Sie die Daten im Dienst speichern.

Das Problem besteht darin, dass Sie den Speicherort, dem Sie taskListzugeordnet sind, jedes Mal ersetzen , wenn Sie ihm einen neuen Wert zuweisen, während der Bereich nicht mehr auf den alten Speicherort verweist. Sie können dies in diesem Plunk sehen .

Machen Sie mit Chrome beim ersten Laden des Plunks Heap-Snapshots. Nachdem Sie auf die Schaltfläche geklickt haben, werden Sie feststellen, dass der Speicherort, auf den der Bereich verweist, niemals aktualisiert wird, während die Liste auf einen anderen Speicherort verweist.

Sie können dies leicht beheben, indem Ihr Dienst ein Objekt enthält, das die Variable enthält, die sich möglicherweise ändert (so etwas wie data:{task:[], x:[], z:[]}). In diesem Fall sollten "Daten" niemals geändert werden, aber jedes seiner Mitglieder kann jederzeit geändert werden. Sie übergeben diese Datenvariable dann an den Gültigkeitsbereich und, solange Sie sie nicht überschreiben, indem Sie versuchen, "Daten" einem anderen zuzuweisen, wird der Gültigkeitsbereich bei jeder Änderung eines Felds in den Daten darüber informiert und korrekt aktualisiert.

Dieser Plunk zeigt dasselbe Beispiel, das mit dem oben vorgeschlagenen Fix ausgeführt wird. In dieser Situation müssen keine Beobachter verwendet werden. Wenn in der Ansicht jemals etwas nicht aktualisiert wird, wissen Sie, dass Sie lediglich einen Bereich ausführen müssen$apply zum Aktualisieren der Ansicht .

Auf diese Weise müssen Sie keine Beobachter mehr verwenden, die häufig Variablen auf Änderungen vergleichen, und das hässliche Setup ist erforderlich, wenn Sie viele Variablen überwachen müssen. Das einzige Problem bei diesem Ansatz ist, dass Sie in Ihrer Ansicht (HTML) "Daten" haben. Stellen Sie alles voran, wo Sie früher nur den Variablennamen hatten.


1
Was ist der Grund für das "neue" Leben bei der Rückgabe des Dienstes?
Schleife

1
In diesem Fall können Sie eine Klasse zurückgeben, wenn Sie die neue entfernen, wird der Code beschädigt. Ich bevorzuge diese Struktur gegenüber der Objektstruktur (z. B. "{attr: value, attr2: value2 ...}"), da ich sie flexibler finde. Hier ist der Plunker dafür, und hier sind ein paar Informationen darüber vom eckigen Team. Wie Sie sehen können, können Sie hierfür den Service anstelle des Werks verwenden, aber beide funktionieren einwandfrei.
Piacenti

4
hey @GabrielPiacenti - Sie haben hier eine wirklich gute Antwort, ich denke, es würde helfen, wenn Sie den Inhalt / die Formatierung ein wenig bereinigen ... machen Sie es lesbarer ...
sfletche

Gabriel, du solltest den größten Teil des Codes löschen und die Antwort nur auf das konzentrieren, was benötigt wird. Sie haben Recht, aber dieser Code sieht kompliziert aus. Schauen Sie sich die 2 Antworten unten an, viel sauberer (aber ihnen fehlen die Informationen, die Sie über den Speicherhaufen haben). So oder so schön gemacht!
Mark Pieszak - Trilon.io

Ich weiß, dass dies eine wirklich alte Antwort ist, aber ich habe dies eine Menge in meinen Apps getan und es funktioniert wie ein Traum. In Angular 1.5 wurde die bidirektionale Datenbindung (meistens) entfernt. Fällt diese Technik also in die gleichen Leistungsprobleme wie das Entfernen der bidirektionalen Datenbindung?
Tyler

71

Angular (im Gegensatz zu Ember und einigen anderen Frameworks) bietet keine speziell verpackten Objekte, die halbmagisch synchron bleiben. Die Objekte , die Sie manipulieren sind einfache JavaScript - Objekte und einfach wie zu sagen , var a = b;nicht verknüpfen die Variablen aund b, sagen $scope.taskList = uaProgressService.taskListverknüpfen nicht diese beiden Werte.

Für diese Art von link-ing angularsorgt $watchon$scope . Sie können den Wert von beobachten uaProgressService.taskListund den Wert aktualisieren, $scopewenn er sich ändert:

$scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) {
    if (typeof newVal !== 'undefined') {
        $scope.taskList = uaProgressService.taskList;
    }
});

Der erste an die $watchFunktion übergebene Ausdruck wird in jeder $digestSchleife ausgeführt, und das zweite Argument ist die Funktion, die mit dem neuen und dem alten Wert aufgerufen wird.


1
Ok, ich verstehe so. Ich habe Ihr Snippet ausprobiert und jetzt funktioniert meine App wie erwartet :) Beeinflusst viel Uhr die App-Leistung?
Alex Grs

1
Obwohl manchmal extreme Maßnahmen ergriffen werden mussten, um es zu optimieren , funktioniert es für die meisten Apps recht gut.
musikalisch_ut

1
Ich war so lange blind, danke. Ich dachte, weil es ein Singleton war, würden sich die Eigenschaften auf magische Weise verbreiten: S
Dvid Silva

4
Ich habe buchstäblich Tage gebraucht, um diese Antwort zu finden. Vielen Dank. Ich bin überrascht, dass dieser Anwendungsfall nicht besser dokumentiert ist, da dies der einzige Grund ist, warum ich einen Dienst (rpc / pubsub) verwende.
Senica Gonzalez

5
$ watch ist in diesem Fall ein Hack. Die richtige Technik wird durch die beiden anderen Antworten beschrieben, insbesondere @Gabriel Piacenti
Bernard

44

Ich bin nicht sicher, ob das hilft, aber ich binde die Funktion an $ scope.value. Zum Beispiel

angular
  .module("testApp", [])
  .service("myDataService", function(){
    this.dataContainer = {
      valA : "car",
      valB : "bike"
    }
  })
  .controller("testCtrl", [
    "$scope",
    "myDataService",
    function($scope, myDataService){
      $scope.data = function(){
        return myDataService.dataContainer;
      };
  }]);

Dann binde ich es einfach in DOM als

<li ng-repeat="(key,value) in data() "></li>

Auf diese Weise können Sie vermeiden, $ watch in Ihrem Code zu verwenden.


4
BESTE ANTWORT Ignorieren Sie die $ watch-Antworten. So sollte es gemacht werden. Das obige hat es auch richtig, ist aber äußerst verwirrend.
Mark Pieszak - Trilon.io

1
Es tut mir leid, eine alte Antwort vorzubringen, aber diese Lösung funktioniert perfekt für mich und ich frage mich, ob es irgendwelche Nachteile gibt, die ich beachten muss? Ich habe verschiedene Möglichkeiten zum Verknüpfen von Bereichen und Diensten ausprobiert, aber keine Lösung ist annähernd so elegant wie diese.
CT14.IT

4
Entschuldigung für die späte Antwort. Dies sollte nicht in Fällen verwendet werden, in denen Ihre Datenfunktion recht komplex ist. Jedes Mal, wenn der $ Digest ausgeführt wird, wird die Methode aufgerufen.
Eduard Jacko

Um dieses Problem zu beheben, verwenden Sie eine Factory, die den Wert zurückgibt und sich selbst aktualisiert, wenn die Daten geändert werden.
Tiago Sousa

Ich wünschte, ich könnte dir mehr danken als nur Danke zu sagen .. !! Ich konnte keine Lösung finden, sogar die Uhr funktionierte aus irgendeinem seltsamen Grund nicht für mich, aber diese macht ihren Job! DANKESCHÖN!
Kuss Koppány

5

Nein $watchoder etc. ist erforderlich. Sie können einfach Folgendes definieren

uaInProgressApp.controller('ua.InProgressController',
  function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
    uaContext.getSession().then(function(){
        uaContext.appName.set('Testing house');
        uaContext.subAppName.set('In progress');
        uaProgressService.startCron();
    });

    $scope.getTaskList = function() {
      return uaProgressService.taskList;
    };
  });

Da die Funktion getTaskListzu $scopeihrem Rückgabewert gehört, wird sie bei jeder Änderung von ausgewertet (und aktualisiert)uaProgressService.taskList


3

Eine einfache Alternative besteht darin, dass Sie während der Controller-Initialisierung ein im Service eingerichtetes Benachrichtigungsmuster abonnieren.

Etwas wie:

app.controller('YourCtrl'['yourSvc', function(yourSvc){
    yourSvc.awaitUpdate('YourCtrl',function(){
        $scope.someValue = yourSvc.someValue;
    });
}]);

Und der Service hat so etwas wie:

app.service('yourSvc', ['$http',function($http){
    var self = this;
    self.notificationSubscribers={};
    self.awaitUpdate=function(key,callback){
        self.notificationSubscribers[key]=callback;
    };
    self.notifySubscribers=function(){
        angular.forEach(self.notificationSubscribers,
            function(callback,key){
                callback();
            });
    };
    $http.get('someUrl').then(
        function(response){
            self.importantData=response.data;
            self.notifySubscribers();
        }
    );
}]);

Auf diese Weise können Sie eine genauere Feinabstimmung vornehmen, wenn Ihre Controller von einem Dienst aktualisiert werden.


Als Hinweis habe ich seitdem eine viel effektivere Lösung dafür gefunden. Verwenden Sie verschachtelte Routen mit dem Angular UI-Router und platzieren Sie alle langlebigen Ereignisbenachrichtigungen im Root-Controller.
Bon

1
Ich wollte in der Lage sein, eine Liste von Daten überall zu platzieren, und wurde von Ihrer Antwort inspiriert, eine Direktive zu schreiben, die einen Dienst nutzt, der auf Ihrem Abonnementprinzip basiert. Ich denke, dass bei Direktive eine viel einfachere Lösung ist als verschachtelte Routen. Aber ich denke, es hängt davon ab ... trotzdem, danke, dass Sie einen sehr schönen und nützlichen Code
Jette

2

Wie Gabriel Piacenti sagte, werden keine Uhren benötigt, wenn Sie die sich ändernden Daten in ein Objekt einwickeln.

ABER um die geänderten Servicedaten im Bereich korrekt zu aktualisieren, ist es wichtig, dass der Bereichswert des Controllers, der die Servicedaten verwendet, nicht direkt auf die sich ändernden Daten (Feld) verweist. Stattdessen muss der Bereichswert auf das Objekt verweisen, das die sich ändernden Daten umschließt.

Der folgende Code sollte dies klarer erklären. In meinem Beispiel verwende ich einen NLS-Dienst zum Übersetzen. Die NLS-Token werden über http aktualisiert.

Der Service:

app.factory('nlsService', ['$http', function($http) {
  var data = {
    get: {
      ressources        : "gdc.ressources",
      maintenance       : "gdc.mm.maintenance",
      prewarning        : "gdc.mobMaint.prewarning",
    }
  };
// ... asynchron change the data.get = ajaxResult.data... 
  return data;
}]);

Controller- und Scope-Ausdruck

app.controller('MenuCtrl', function($scope, nlsService)
  {
    $scope.NLS = nlsService;
  }
);

<div ng-controller="MenuCtrl">
  <span class="navPanelLiItemText">{{NLS.get.maintenance}}</span>
</div>

Der obige Code funktioniert, aber zuerst wollte ich direkt auf meine NLS-Token zugreifen (siehe folgendes Snippet) und hier wurden die Werte nicht aktualisiert.

app.controller('MenuCtrl', function($scope, nlsService)
  {
    $scope.NLS = nlsService.get;
  }
);

<div ng-controller="MenuCtrl">
  <span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.