AngularJS: Die richtige Art der Bindung an Serviceeigenschaften

162

Ich suche nach der besten Methode zum Binden an eine Service-Eigenschaft in AngularJS.

Ich habe mehrere Beispiele durchgearbeitet, um zu verstehen, wie man an Eigenschaften in einem Dienst bindet, der mit AngularJS erstellt wurde.

Im Folgenden habe ich zwei Beispiele für das Binden an Eigenschaften in einem Dienst. Sie arbeiten beide. Im ersten Beispiel werden grundlegende Bindungen verwendet, und im zweiten Beispiel wurde $ scope. $ Watch verwendet, um an die Serviceeigenschaften zu binden

Werden diese Beispiele bevorzugt, wenn Sie an Eigenschaften eines Dienstes binden, oder gibt es eine andere Option, von der ich nicht weiß, dass sie empfohlen wird?

Die Voraussetzung dieser Beispiele ist, dass der Dienst seine Eigenschaften "lastUpdated" und "calls" alle 5 Sekunden aktualisiert. Sobald die Serviceeigenschaften aktualisiert wurden, sollte die Ansicht diese Änderungen widerspiegeln. Beide Beispiele funktionieren erfolgreich. Ich frage mich, ob es einen besseren Weg gibt.

Grundbindung

Der folgende Code kann hier angezeigt und ausgeführt werden: http://plnkr.co/edit/d3c16z

<html>
<body ng-app="ServiceNotification" >

    <div ng-controller="TimerCtrl1" style="border-style:dotted"> 
        TimerCtrl1 <br/>
        Last Updated: {{timerData.lastUpdated}}<br/>
        Last Updated: {{timerData.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.timerData = Timer.data;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

Die andere Möglichkeit, die Bindung an Serviceeigenschaften zu lösen, besteht darin, $ scope. $ Watch im Controller zu verwenden.

$ scope. $ watch

Der folgende Code kann hier angezeigt und ausgeführt werden: http://plnkr.co/edit/dSBlC9

<html>
<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.$watch(function () { return Timer.data.lastUpdated; },
                function (value) {
                    console.log("In $watch - lastUpdated:" + value);
                    $scope.lastUpdated = value;
                }
            );

            $scope.$watch(function () { return Timer.data.calls; },
                function (value) {
                    console.log("In $watch - calls:" + value);
                    $scope.calls = value;
                }
            );
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

Ich bin mir bewusst, dass ich $ rootcope. $ Broadcast im Dienst und $ root. $ On im Controller verwenden kann, aber in anderen Beispielen, die ich erstellt habe und die $ Broadcast / $ bei der ersten Sendung verwenden, wird vom nicht erfasst Controller, aber zusätzliche Anrufe, die gesendet werden, werden im Controller ausgelöst. Wenn Sie einen Weg kennen, um das Problem mit $ rootcope. $ Broadcast zu lösen, geben Sie bitte eine Antwort.

Um jedoch noch einmal zu erwähnen, was ich bereits erwähnt habe, möchte ich die bewährte Methode zum Binden an Serviceeigenschaften kennen.


Aktualisieren

Diese Frage wurde ursprünglich im April 2013 gestellt und beantwortet. Im Mai 2014 gab Gil Birman eine neue Antwort, die ich als richtige Antwort geändert habe. Da die Antwort von Gil Birman nur sehr wenige Stimmen hat, befürchte ich, dass die Leute, die diese Frage lesen, seine Antwort zugunsten anderer Antworten mit viel mehr Stimmen ignorieren. Bevor Sie sich für die beste Antwort entscheiden, empfehle ich die Antwort von Gil Birman.

Mike Barlow - BarDev
quelle
Ich denke, Josh David Millers Antwort ist besser als die von Gil Birman. Und mit $ watch, $ watchGroup und $ watchCollection kann es sogar noch besser werden. Die Trennung von Bedenken ist bei mittelgroßen bis großen Apps sehr wichtig.
Jonathan
@bardev Ich denke, beide Antworten waren nicht nützlich und neue Entwickler würden sie völlig falsch verstehen.
Zalaboza
Das Problem, nach dem Sie fragen,
betrifft das

Antworten:

100

Betrachten Sie einige Vor- und Nachteile des zweiten Ansatzes :

  • 0 {{lastUpdated}} statt {{timerData.lastUpdated}}, was genauso gut sein könnte {{timer.lastUpdated}}, was ich argumentieren könnte, ist besser lesbar (aber lassen Sie uns nicht streiten ... Ich gebe diesem Punkt eine neutrale Bewertung, damit Sie selbst entscheiden)

  • +1 Es kann zweckmäßig sein, dass der Controller als eine Art API für das Markup fungiert, sodass Sie (theoretisch) die API-Zuordnungen des Controllers aktualisieren können, wenn sich die Struktur des Datenmodells ändert, ohne den HTML-Teil zu berühren.

  • -1 Allerdings ist Theorie nicht immer üben und ich mich in der Regel finden Markup zu modifizieren und Controller - Logik , wenn Änderungen gefordert sind, sowieso . Der zusätzliche Aufwand beim Schreiben der API macht den Vorteil zunichte.

  • -1 Außerdem ist dieser Ansatz nicht sehr trocken.

  • -1 Wenn Sie die Daten an ng-modelIhren Code binden möchten, werden Sie noch weniger trocken, da Sie die Daten $scope.scalar_valuesim Controller neu verpacken müssen , um einen neuen REST-Aufruf durchzuführen.

  • -0.1 Es gibt einen winzigen Performance-Hit, der zusätzliche Beobachter schafft. Wenn dem Modell Dateneigenschaften zugeordnet sind, die in einem bestimmten Controller nicht überwacht werden müssen, entsteht ein zusätzlicher Overhead für die Deep Watcher.

  • -1 Was ist, wenn mehrere Controller dieselben Datenmodelle benötigen? Das bedeutet, dass Sie bei jeder Modelländerung mehrere APIs aktualisieren müssen.

$scope.timerData = Timer.data;fängt gerade an, mächtig verlockend zu klingen ... Lassen Sie uns etwas tiefer in diesen letzten Punkt eintauchen ... Über welche Art von Modelländerungen haben wir gesprochen? Ein Modell im Backend (Server)? Oder ein Modell, das nur im Frontend erstellt wird und lebt? In beiden Fällen gehört die Datenzuordnungs-API im Wesentlichen zur Front-End-Serviceschicht (einer Winkelfabrik oder einem Service). (Beachten Sie, dass Ihr erstes Beispiel - meine Präferenz - keine solche API in der Service-Schicht hat , was in Ordnung ist, weil es einfach genug ist, dass es sie nicht benötigt.)

Abschließend muss nicht alles entkoppelt werden. Und soweit das Markup vollständig vom Datenmodell entkoppelt ist, überwiegen die Nachteile die Vorteile.


Controller sollten im Allgemeinen nicht mit $scope = injectable.data.scalar's übersät sein . Sie sollten vielmehr mit $scope = injectable.data's, promise.then(..)' s und $scope.complexClickAction = function() {..}'s bestreut werden

Als alternativer Ansatz zur Erzielung einer Datenentkopplung und damit zur Ansichtskapselung ist es nur mit einer Direktive sinnvoll, die Ansicht vom Modell zu entkoppeln . Aber auch dort keine skalaren Werte in den oder Funktionen. Das spart keine Zeit und macht den Code weder wartbar noch lesbar. Dies erleichtert das Testen nicht einmal, da robuste Tests im Winkel normalerweise das resultierende DOM ohnehin testen . Fordern Sie in einer Direktive Ihre Daten-API in Objektform und bevorzugen Sie die Verwendung nur der von er erstellten ers .$watchcontrollerlink$watchng-bind


Beispiel http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio

<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Bad:<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
        Good:<br/>
        Last Updated: {{data.lastUpdated}}<br/>
        Last Updated: {{data.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.data = Timer.data;
            $scope.lastUpdated = Timer.data.lastUpdated;
            $scope.calls = Timer.data.calls;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 500);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>

UPDATE : Ich bin endlich auf diese Frage zurückgekommen, um hinzuzufügen, dass ich nicht denke, dass einer der beiden Ansätze "falsch" ist. Ursprünglich hatte ich geschrieben, dass die Antwort von Josh David Miller falsch war, aber im Nachhinein sind seine Punkte völlig gültig, insbesondere sein Punkt über die Trennung von Bedenken.

Abgesehen von der Trennung von Bedenken (aber tangential) gibt es einen weiteren Grund für defensives Kopieren , den ich nicht berücksichtigt habe. Diese Frage befasst sich hauptsächlich mit dem Lesen von Daten direkt von einem Dienst. Was aber, wenn ein Entwickler in Ihrem Team entscheidet, dass der Controller die Daten auf triviale Weise transformieren muss, bevor die Ansicht sie anzeigt? (Ob Controller Daten überhaupt transformieren sollen, ist eine andere Diskussion.) Wenn sie nicht zuerst eine Kopie des Objekts erstellt, kann sie unabsichtlich Regressionen in einer anderen Ansichtskomponente verursachen, die dieselben Daten verwendet.

Was diese Frage wirklich hervorhebt, sind architektonische Mängel der typischen Winkelanwendung (und wirklich jeder JavaScript-Anwendung): enge Kopplung von Bedenken und Objektveränderlichkeit. Ich habe mich kürzlich in die Architektur von Anwendungen mit React und unveränderlichen Datenstrukturen verliebt . Dies löst die folgenden zwei Probleme auf wunderbare Weise:

  1. Trennung von Bedenken : Eine Komponente verbraucht alle von über Requisiten es die Daten und hat wenig bis keine Abhängigkeit von globalen Singletons (wie Angular - Dienste), und weiß nichts über das, was passiert ist oben in der Ansicht Hierarchie.

  2. Mutabilität : Alle Requisiten sind unveränderlich, wodurch das Risiko einer unwissentlichen Datenmutation ausgeschlossen wird.

Angular 2.0 ist jetzt auf dem richtigen Weg, sich stark von React zu leihen, um die beiden oben genannten Punkte zu erreichen.

Gil Birman
quelle
1
Je mehr ich mit AngularJS arbeite, desto mehr verstehe ich. Ich glaube, dass AngularJS-Controller so einfach und dünn wie möglich sein sollten. Durch das Hinzufügen von $ watchs im Controller wird die Logik zu kompliziert. Wenn Sie nur auf den Wert im Service verweisen, ist dies viel einfacher und scheint eher AngularJS zu sein. -
Mike Barlow - BarDev
3
Die "zehn Gebote" von AngularJS. Es ist aus einem bestimmten Grund deklarativ.
Pilau
Josh David Millers Antwort lautet nicht "Falsch". Es gibt eine Zeit und einen Ort für alles.
Joshua David
Ich denke du hast recht, @FireCoding. Ich plane, die Antwort zu aktualisieren.
Gil Birman
@ GilBirman ausgezeichnete Antwort. Würde es Ihnen etwas ausmachen, für die Richtlinie zu schreiben und ein Beispiel zu geben? Um zu veranschaulichen, was Sie gesagt haben: "Der einzige Ort, an dem es wirklich sinnvoll ist, die Ansicht vom Modell zu entkoppeln, ist eine Direktive. [...] Fordern Sie in einer Direktive Ihre Daten-API in Objektform und bevorzugen Sie die Verwendung nur des $ Beobachter, die von ng-bind erstellt wurden. "
Klode
78

Aus meiner Sicht $watchwäre dies der beste Weg.

Sie können Ihr Beispiel tatsächlich ein wenig vereinfachen:

function TimerCtrl1($scope, Timer) {
  $scope.$watch( function () { return Timer.data; }, function (data) {
    $scope.lastUpdated = data.lastUpdated;
    $scope.calls = data.calls;
  }, true);
}

Das ist alles was du brauchst.

Da die Eigenschaften gleichzeitig aktualisiert werden, benötigen Sie nur eine Uhr. Da sie von einem einzelnen, eher kleinen Objekt stammen, habe ich es geändert, um nur die Timer.dataEigenschaft zu beobachten. Der letzte übergebene Parameter $watchweist ihn an, auf tiefe Gleichheit zu prüfen, anstatt nur sicherzustellen, dass die Referenz dieselbe ist.


Um einen kleinen Kontext zu bieten, würde ich diese Methode der direkten Platzierung des Servicewerts auf dem Geltungsbereich vorziehen, um eine ordnungsgemäße Trennung der Bedenken sicherzustellen. Ihre Ansicht sollte nichts über Ihre Dienste wissen müssen, um zu funktionieren. Die Aufgabe des Controllers ist es, alles zusammenzukleben; Seine Aufgabe ist es, die Daten von Ihren Diensten abzurufen und sie auf die erforderliche Weise zu verarbeiten und dann Ihrer Ansicht die erforderlichen Details zu liefern. Aber ich glaube nicht, dass es seine Aufgabe ist, den Service direkt an die Ansicht weiterzugeben. Was macht der Controller sonst überhaupt dort? Die AngularJS-Entwickler folgten denselben Überlegungen, als sie beschlossen, keine "Logik" in die Vorlagen aufzunehmen (z if. B. Anweisungen).

Um fair zu sein, gibt es hier wahrscheinlich mehrere Perspektiven und ich freue mich auf andere Antworten.

Josh David Miller
quelle
3
Können Sie etwas näher darauf eingehen? Bevorzugen Sie die $ -Uhren, weil die Ansicht weniger an den Service gekoppelt ist? Dh {{lastUpdated}}vs.{{timerData.lastUpdated}}
Mark Rajcok
2
@BarDev muss für die Verwendung Timer.datain einer $ watch Timerim $ scope definiert werden, da der Zeichenfolgenausdruck, den Sie an $ watch übergeben, anhand des Bereichs ausgewertet wird. Hier ist ein Plunker , der zeigt, wie das funktioniert. Der Parameter objectEquality ist hier dokumentiert - 3. Parameter - aber nicht wirklich gut erklärt.
Mark Rajcok
2
Leistungstechnisch $watchist es ziemlich ineffizient. Siehe Antworten unter stackoverflow.com/a/17558885/932632 und stackoverflow.com/questions/12576798/…
Krym
11
@Kyrm Das stimmt wahrscheinlich unter bestimmten Umständen, aber wenn wir uns mit Leistung befassen, müssen wir nach den "klinisch signifikanten" Leistungsverbesserungen suchen und nicht nur nach den statistisch signifikanten, verallgemeinerten. Wenn in einer vorhandenen Anwendung ein Leistungsproblem vorliegt, sollte dieses behoben werden. Andernfalls handelt es sich nur um eine vorzeitige Optimierung, die zu schwer lesbarem, fehleranfälligem Code führt, der nicht den Best Practices entspricht und keinen nachgewiesenen Nutzen hat.
Josh David Miller
1
Angenommen, der Beobachter verwendet eine Funktion, um den Getter aufzurufen, dann ja. Es funktioniert gut. Ich bin auch ein Befürworter von Diensten, die Instanzen in einer Sammlung zurückgeben, in denen Getter- und Setter-Funktionen von es5 stattdessen ziemlich gut funktionieren.
Josh David Miller
19

Spät zur Party, aber für zukünftige Googler - verwenden Sie nicht die bereitgestellte Antwort.

JavaScript hat einen Mechanismus zum Übergeben von Objekten als Referenz, während es nur eine flache Kopie für die Werte "Zahlen, Zeichenfolgen usw." übergibt.

Warum setzen wir den Dienst im obigen Beispiel nicht dem Gültigkeitsbereich aus , anstatt die Attribute eines Dienstes zu binden ?

$scope.hello = HelloService;

Dieser einfache Ansatz ermöglicht es Angular, bidirektionale Bindungen und alle magischen Dinge auszuführen, die Sie benötigen. Hacken Sie Ihren Controller nicht mit Beobachtern oder nicht benötigten Markups.

Wenn Sie sich Sorgen machen, dass Ihre Ansicht Ihre Serviceattribute versehentlich überschreibt, verwenden definePropertySie diese Option, um sie lesbar, aufzählbar, konfigurierbar zu machen oder Getter und Setter zu definieren. Sie können viel Kontrolle erlangen, indem Sie Ihren Service solider gestalten.

Letzter Tipp: Wenn Sie mehr Zeit mit der Arbeit an Ihrem Controller als mit Ihren Diensten verbringen, machen Sie es falsch :(.

In diesem speziellen Demo-Code, den Sie angegeben haben, würde ich Ihnen Folgendes empfehlen:

 function TimerCtrl1($scope, Timer) {
   $scope.timer = Timer;
 }
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...

Bearbeiten:

Wie oben erwähnt, können Sie das Verhalten Ihrer Serviceattribute mithilfe von steuern defineProperty

Beispiel:

// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
  get: function() { return self.data.variable; },
  set: function(newValue) { 
         self.data.variable = newValue; 
         // let's update the database too to reflect changes in data-model !
         self.updateDatabaseWithNewData(data);
       },
  enumerable: true,
  configurable: true
});

Jetzt in unserem Controller, wenn wir das tun

$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';

Unser Service wird den Wert von ändern propertyWithSetterund den neuen Wert auch irgendwie in der Datenbank veröffentlichen!

Oder wir können jeden Ansatz wählen, den wir wollen.

Wenden Sie sich an die MDN - Dokumentation für defineProperty.

Zalaboza
quelle
Ich bin mir ziemlich sicher, dass ich das oben beschrieben habe, $scope.model = {timerData: Timer.data};indem ich es einfach an das Modell angehängt habe, anstatt direkt auf Scope.
Scott Silvi
1
AFAIK, die js-Objektreferenz funktioniert für alles im Service. Codierung wie folgt: $ scope.controllerVar = ServiceVar, alles, was Sie in $ scope.controllerVar tun, wird auch in ServiceVar ausgeführt.
Kai Wang
@KaiWang stimmt zu, wenn Sie sich nicht für DefineAttribute entschieden haben, können Sie Ihren Diensten eine Setter-Funktion zuweisen, um zu verhindern, dass Controller versehentlich Ihre Dienstdaten mutieren.
Zalaboza
12

Ich denke, diese Frage hat eine kontextbezogene Komponente.

Wenn Sie einfach Daten von einem Dienst abrufen und diese Informationen in seine Ansicht übertragen, ist es meiner Meinung nach in Ordnung, direkt an die Serviceeigenschaft zu binden. Ich möchte nicht viel Boilerplate-Code schreiben , um Serviceeigenschaften einfach Modelleigenschaften zuzuordnen, die in meiner Ansicht verwendet werden sollen.

Darüber hinaus basiert die Leistung im Winkel auf zwei Dingen. Die erste ist, wie viele Bindungen sich auf einer Seite befinden. Das zweite ist, wie teuer Getter-Funktionen sind. Misko spricht hier darüber

Wenn Sie eine instanzspezifische Logik für die Servicedaten ausführen müssen (im Gegensatz zu Datenmassagen, die innerhalb des Service selbst angewendet werden) und das Ergebnis dies Auswirkungen auf das Datenmodell hat, das der Ansicht ausgesetzt ist, würde ich sagen, dass ein $ watcher angemessen ist solange die Funktion nicht besonders teuer ist. Im Falle einer teuren Funktion würde ich vorschlagen, die Ergebnisse in einer lokalen Variablen (an den Controller) zwischenzuspeichern, Ihre komplexen Operationen außerhalb der $ watcher-Funktion auszuführen und dann Ihren Bereich an das Ergebnis zu binden.

Als Einschränkung sollten Sie keine Eigenschaften direkt an Ihrem $ scope hängen . Die $scopeVariable ist NICHT Ihr Modell. Es enthält Verweise auf Ihr Modell.

In meinen Augen "Best Practice" für die einfache Übertragung von Informationen vom Service bis zur Ansicht:

function TimerCtrl1($scope, Timer) {
  $scope.model = {timerData: Timer.data};
};

Und dann würde Ihre Ansicht enthalten {{model.timerData.lastupdated}}.

Scott Silvi
quelle
Mit diesem Vorschlag vorsichtig, könnte jemand, der kein Experte für Javascript ist, versuchen, dies mit einer Eigenschaft zu tun, die eine Zeichenfolge ist. In diesem Fall verweist Javascript nicht auf das Objekt, sondern kopiert die Zeichenfolge roh. (und das ist schlecht, weil es nicht aktualisiert wird, wenn es sich ändert)
Valerio
7
Habe ich das nicht mit meiner 'Einschränkung' abgedeckt, dass Sie immer den Punkt verwenden sollten (was bedeutet, dass Sie ihn nicht an $ scope, sondern an $ scope.model hängen). Wenn Sie $ scope.model.someStringProperty haben und in Ihrer Ansicht auf model.someStringProperty verweisen, wird es aktualisiert, da sich die internen Beobachter auf dem Objekt befinden und nicht auf der Requisite.
Scott Silvi
6

Aufbauend auf den obigen Beispielen dachte ich, ich würde eine Möglichkeit einführen, eine Controller-Variable transparent an eine Service-Variable zu binden.

Im folgenden Beispiel werden Änderungen an der Controller- $scope.countVariablen automatisch in der Service- countVariablen übernommen.

In der Produktion verwenden wir diese Bindung tatsächlich, um eine ID für einen Dienst zu aktualisieren, der dann asynchron Daten abruft und seine Dienstvariablen aktualisiert. Weitere Bindung bedeutet, dass Controller automatisch aktualisiert werden, wenn der Dienst selbst aktualisiert wird.

Der folgende Code funktioniert unter http://jsfiddle.net/xuUHS/163/.

Aussicht:

<div ng-controller="ServiceCtrl">
    <p> This is my countService variable : {{count}}</p>
    <input type="number" ng-model="count">
    <p> This is my updated after click variable : {{countS}}</p>

    <button ng-click="clickC()" >Controller ++ </button>
    <button ng-click="chkC()" >Check Controller Count</button>
    </br>

    <button ng-click="clickS()" >Service ++ </button>
    <button ng-click="chkS()" >Check Service Count</button>
</div>

Service / Controller:

var app = angular.module('myApp', []);

app.service('testService', function(){
    var count = 10;

    function incrementCount() {
      count++;
      return count;
    };

    function getCount() { return count; }

    return {
        get count() { return count },
        set count(val) {
            count = val;
        },
        getCount: getCount,
        incrementCount: incrementCount
    }

});

function ServiceCtrl($scope, testService)
{

    Object.defineProperty($scope, 'count', {
        get: function() { return testService.count; },
        set: function(val) { testService.count = val; },
    });

    $scope.clickC = function () {
       $scope.count++;
    };
    $scope.chkC = function () {
        alert($scope.count);
    };

    $scope.clickS = function () {
       ++testService.count;
    };
    $scope.chkS = function () {
        alert(testService.count);
    };

}
TomDotTom
quelle
Dies ist eine großartige Lösung, danke, das hat mir sehr geholfen, sehr kluge Art, es zu tun :)
Javis Perez
2

Ich denke, es ist eine bessere Möglichkeit, den Dienst selbst zu binden, als die Attribute darauf.

Hier ist der Grund:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">

  <div ng-controller="BindToServiceCtrl as ctrl">
    ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span>
    <br />
    ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span>
    <br />
    <br />
    <!-- This is empty since $scope.arrOne never changes -->
    arrOne: <span ng-repeat="v in arrOne">{{v}}</span>
    <br />
    <!-- This is not empty since $scope.arrTwo === ArrService.arrTwo -->
    <!-- Both of them point the memory space modified by the `push` function below -->
    arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span>
  </div>

  <script type="text/javascript">
    var app = angular.module("BindToService", []);

    app.controller("BindToServiceCtrl", function ($scope, ArrService) {
      $scope.ArrService = ArrService;
      $scope.arrOne = ArrService.arrOne;
      $scope.arrTwo = ArrService.arrTwo;
    });

    app.service("ArrService", function ($interval) {
      var that = this,
          i = 0;
      this.arrOne = [];
      that.arrTwo = [];

      $interval(function () {
        // This will change arrOne (the pointer).
        // However, $scope.arrOne is still same as the original arrOne.
        that.arrOne = that.arrOne.concat([i]);

        // This line changes the memory block pointed by arrTwo.
        // And arrTwo (the pointer) itself never changes.
        that.arrTwo.push(i);
        i += 1;
      }, 1000);

    });
  </script>
</body> 

Sie können es auf diesem Plunker spielen .

Trantor Liu
quelle
1

Ich würde meine Beobachter lieber so wenig wie möglich halten. Mein Grund basiert auf meinen Erfahrungen und man könnte es theoretisch argumentieren.
Das Problem bei der Verwendung von Watchern besteht darin, dass Sie jede Eigenschaft im Bereich verwenden können, um eine der Methoden in einer beliebigen Komponente oder einem beliebigen Dienst aufzurufen.
In einem realen Projekt werden Sie bald eine nicht nachvollziehbare (besser gesagt schwer nachvollziehbare) Kette von Methoden aufrufen und Werte ändern, was den Onboarding -Prozess besonders tragisch macht.

Reyraa
quelle
0

Das Binden von Daten, die einen Dienst senden, ist keine gute Idee (Architektur), aber wenn Sie sie mehr benötigen, empfehle ich Ihnen zwei Möglichkeiten, dies zu tun

1) Sie können die Daten erhalten, die nicht in Ihrem Dienst enthalten sind. Sie können Daten in Ihrem Controller / Ihrer Direktive abrufen, und Sie werden kein Problem damit haben, sie irgendwo zu binden

2) Sie können anglejs-Ereignisse verwenden. Wann immer Sie möchten, können Sie ein Signal (von $ rootScope) senden und es abfangen, wo immer Sie möchten. Sie können sogar Daten zu diesem Ereignisnamen senden.

Vielleicht kann dir das helfen. Wenn Sie mehr mit Beispielen benötigen, finden Sie hier den Link

http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html

Hazarapet Tunanyan
quelle
-1

Wie wäre es mit

scope = _.extend(scope, ParentScope);

Wo ist ParentScope ein injizierter Dienst?

Tomascharad
quelle
-2

Die elegantesten Lösungen ...

app.service('svc', function(){ this.attr = []; return this; });
app.controller('ctrl', function($scope, svc){
    $scope.attr = svc.attr || [];
    $scope.$watch('attr', function(neo, old){ /* if necessary */ });
});
app.run(function($rootScope, svc){
    $rootScope.svc = svc;
    $rootScope.$watch('svc', function(neo, old){ /* change the world */ });
});

Außerdem schreibe ich EDAs (Event-Driven Architectures), sodass ich dazu neige, Folgendes zu tun [stark vereinfachte Version]:

var Service = function Service($rootScope) {
    var $scope = $rootScope.$new(this);
    $scope.that = [];
    $scope.$watch('that', thatObserver, true);
    function thatObserver(what) {
        $scope.$broadcast('that:changed', what);
    }
};

Dann setze ich einen Listener in meinen Controller auf den gewünschten Kanal und halte meinen lokalen Bereich auf diese Weise auf dem neuesten Stand.

Abschließend gibt es nicht viel von einem „Best Practice“ - besser gesagt, seine meist bevorzugt - solange Sie halten sind Dinge SOLID und unter Verwendung schwache Kopplung. Der Grund, warum ich den letzteren Code befürworten würde, ist, dass EDAs die niedrigste von Natur aus mögliche Kopplung aufweisen . Und wenn Sie sich darüber keine Sorgen machen, lassen Sie uns vermeiden, gemeinsam an demselben Projekt zu arbeiten.

Hoffe das hilft...

Cody
quelle