AngularJS: poprawny sposób tworzenia powiązania z właściwościami usługi

162

Szukam najlepszych praktyk tworzenia powiązań z usługą w AngularJS.

Omówiłem wiele przykładów, aby zrozumieć, jak powiązać właściwości w usłudze utworzonej za pomocą AngularJS.

Poniżej mam dwa przykłady powiązania z właściwościami w usłudze; oboje pracują. W pierwszym przykładzie zastosowano podstawowe powiązania, a w drugim użyto $ scope. $ Watch do powiązania z właściwościami usługi

Czy któryś z tych przykładów jest preferowany podczas tworzenia powiązania z właściwościami w usłudze, czy też jest zalecana inna opcja, o której nie wiem?

Założeniem tych przykładów jest to, że usługa powinna aktualizować swoje właściwości „lastUpdated” i „call” co 5 sekund. Po zaktualizowaniu właściwości usługi widok powinien odzwierciedlać te zmiany. Oba te przykłady działają pomyślnie; Zastanawiam się, czy jest na to lepszy sposób.

Podstawowe wiązanie

Poniższy kod można wyświetlić i uruchomić tutaj: 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>

Innym sposobem, w jaki rozwiązałem powiązanie z właściwościami usługi, jest użycie $ scope. $ Watch w kontrolerze.

$ scope. $ watch

Poniższy kod można wyświetlić i uruchomić tutaj: 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>

Zdaję sobie sprawę, że mogę użyć $ rootscope. $ Broadcast w usłudze i $ root. $ On w kontrolerze, ale w innych przykładach, które utworzyłem, używających $ broadcast / $ w pierwszej transmisji nie jest przechwytywane przez kontroler, ale dodatkowe wywołania, które są transmitowane, są wyzwalane w kontrolerze. Jeśli znasz sposób rozwiązania problemu z emisją $ rootscope. $, Podaj odpowiedź.

Ale aby powtórzyć to, o czym wspomniałem wcześniej, chciałbym poznać najlepsze praktyki tworzenia powiązań z właściwościami usługi.


Aktualizacja

To pytanie zostało pierwotnie zadane i udzielono na nie odpowiedzi w kwietniu 2013 r. W maju 2014 r. Gil Birman udzielił nowej odpowiedzi, którą zmieniłem jako poprawną. Ponieważ odpowiedź Gila Birmana ma bardzo mało głosów pozytywnych, obawiam się, że ludzie czytający to pytanie zignorują jego odpowiedź na rzecz innych odpowiedzi, które otrzymają znacznie więcej głosów. Zanim podejmiesz decyzję o najlepszej odpowiedzi, gorąco polecam odpowiedź Gila Birmana.

Mike Barlow - BarDev
źródło
Myślę, że odpowiedź Josha Davida Millera jest lepsza niż odpowiedź Gila Birmana. A użycie $ watch, $ watchGroup i $ watchCollection może nawet uczynić to jeszcze lepszym. Rozdzielenie problemów jest bardzo ważne w aplikacjach o średnim i dużym rozmiarze.
Jonathan
@bardev Myślę, że obie odpowiedzi nie były przydatne, a nowi programiści zrozumieliby je całkowicie źle.
Zalaboza
Problem, o który pytasz, dotyczy zachowania zmiennych natywnych JavaScript obiektów odwołujących się, dodałem trochę wyjaśnienia poniżej
Zalaboza.

Odpowiedzi:

100

Rozważ kilka zalet i wad drugiego podejścia :

  • 0 {{lastUpdated}} zamiast {{timerData.lastUpdated}}, co mogłoby równie łatwo być {{timer.lastUpdated}}, co może być bardziej czytelne (ale nie kłóćmy się ... daję temu punktowi ocenę neutralną, więc sam decydujesz)

  • +1 Może być dogodne, aby kontroler działał jako rodzaj API dla znaczników, tak że jeśli w jakiś sposób zmieni się struktura modelu danych, można (teoretycznie) zaktualizować mapowania API kontrolera bez dotykania częściowej kodu HTML.

  • -1 jednak teoria nie zawsze ćwiczyć i ja zwykle znaleźć sobie konieczności modyfikowania znaczników i Logic Controller, gdy zmiany są nazywane dla, tak czy owak . Zatem dodatkowy wysiłek związany z napisaniem API neguje jego zalety.

  • -1 Co więcej, to podejście nie jest bardzo SUCHE.

  • -1 Jeśli chcesz powiązać dane z ng-modelkodem, stań się jeszcze mniej SUCHY, ponieważ musisz ponownie spakować $scope.scalar_valuesw kontrolerze, aby wykonać nowe wywołanie REST.

  • -0,1 Jest mały hit wydajnościowy tworzący dodatkowych obserwatorów. Ponadto, jeśli właściwości danych są dołączone do modelu, które nie muszą być obserwowane w określonym kontrolerze, spowodują dodatkowe obciążenie dla głębokich obserwatorów.

  • -1 A jeśli wielu administratorów potrzebuje tych samych modeli danych? Oznacza to, że masz wiele interfejsów API do aktualizacji przy każdej zmianie modelu.

$scope.timerData = Timer.data;zaczyna teraz brzmieć bardzo kusząco… Zanurzmy się trochę głębiej w ten ostatni punkt… O jakich zmianach modelu mówiliśmy? Model na zapleczu (serwerze)? A może model, który jest tworzony i żyje tylko na froncie? W obu przypadkach to, co zasadniczo jest interfejsem API mapowania danych, należy do warstwy usług front-end (fabryka lub usługa kątowa). (Zwróć uwagę, że Twój pierwszy przykład - moje preferencje - nie ma takiego interfejsu API w warstwie usług , co jest w porządku, ponieważ jest wystarczająco prosty, że go nie potrzebuje).

Podsumowując , wszystko nie musi być odsprzęgane. Jeśli chodzi o całkowite oddzielenie znaczników od modelu danych, wady przeważają nad zaletami.


Ogólnie rzecz biorąc, kontrolery nie powinny być zaśmiecane $scope = injectable.data.scalar. Raczej powinny być posypane $scope = injectable.data's, promise.then(..)' i$scope.complexClickAction = function() {..} „s”

Jako alternatywne podejście do oddzielenia danych, a tym samym hermetyzacji widoku, jedynym miejscem, w którym naprawdę ma sens oddzielenie widoku od modelu, jest dyrektywa . Ale nawet tam nie należy $watchużywać wartości skalarnych w funkcjach controllerlub link. Nie zaoszczędzi to czasu ani nie sprawi, że kod będzie łatwiejszy w utrzymaniu ani czytelny. Nie ułatwi to nawet testowania, ponieważ solidne testy kątowe zwykle i tak testują wynikowy DOM . Zamiast tego w dyrektywie żądaj interfejsu API danych w postaci obiektowej i preferuj używanie tylko $watchers utworzonych przez ng-bind.


Przykład 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>

AKTUALIZACJA : W końcu wróciłem do tego pytania, aby dodać, że nie sądzę, aby którekolwiek podejście było „złe”. Pierwotnie napisałem, że odpowiedź Josha Davida Millera była niepoprawna, ale z perspektywy czasu jego uwagi są całkowicie słuszne, zwłaszcza jego uwaga dotycząca oddzielenia obaw.

Odkładając na bok obawy (ale są one stycznie powiązane), istnieje jeszcze jeden powód kopiowania obronnego , którego nie uwzględniłem. To pytanie dotyczy głównie odczytu danych bezpośrednio z usługi. Ale co, jeśli programista w Twoim zespole zdecyduje, że kontroler musi przekształcić dane w jakiś trywialny sposób, zanim widok je wyświetli? (To, czy kontrolery powinny w ogóle transformować dane, to inna dyskusja.) Jeśli najpierw nie utworzy kopii obiektu, może nieświadomie spowodować regresje w innym komponencie widoku, który zużywa te same dane.

To, co naprawdę podkreśla to pytanie, to architektoniczne wady typowej aplikacji kątowej (a tak naprawdę każdej aplikacji JavaScript): ścisłe powiązanie problemów i zmienność obiektów. Niedawno zakochałem się w projektowaniu aplikacji z Reactem i niezmiennymi strukturami danych. W ten sposób cudownie rozwiązuje to dwa następujące problemy:

  1. Rozdzielenie problemów : komponent zużywa wszystkie swoje dane za pośrednictwem rekwizytów i w niewielkim stopniu polega na globalnych singletonach (takich jak usługi Angular) i nie wie nic o tym, co wydarzyło się nad nim w hierarchii widoku.

  2. Zmienność : wszystkie rekwizyty są niezmienne, co eliminuje ryzyko niezamierzonej mutacji danych.

Angular 2.0 jest teraz na dobrej drodze do zapożyczenia od Reacta, aby osiągnąć dwa powyższe punkty.

Gil Birman
źródło
1
Im więcej pracuję z AngularJS, tym więcej rozumiem. Uważam, że kontrolery AngularJS powinny być tak proste i cienkie, jak to tylko możliwe. Dodanie $ zegarków do kontrolera komplikuje logikę. Po prostu odwołując się do wartości w usłudze, jest to znacznie prostsze i wydaje się być bardziej zgodne z AngularJS. -
Mike Barlow - BarDev
3
„Dziesięć przykazań” AngularJS. Nie bez powodu jest to deklaratywne.
pilau
Odpowiedź Josha Davida Millera nie jest „NIEPRAWIDŁOWA”. Na wszystko jest czas i miejsce.
JoshuaDavid
Myślę, że masz rację, @FireCoding. Planuję zaktualizować odpowiedź.
Gil Birman
@GilBirman doskonała odpowiedź. Czy mógłbyś napisać i przedstawić przykład dyrektywy? Aby zilustrować to, co powiedziałeś, „jedynym miejscem, w którym naprawdę ma sens oddzielenie widoku od modelu, jest dyrektywa. [...] Zamiast tego w dyrektywie należy żądać interfejsu API danych w postaci obiektowej i preferować użycie tylko $ obserwatorzy stworzeni przez ng-bind. "
klode
78

Z mojej perspektywy $watchbyłaby to najlepsza praktyka.

Możesz nieco uprościć swój przykład:

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

To wszystko, czego potrzebujesz.

Ponieważ właściwości są aktualizowane jednocześnie, potrzebujesz tylko jednego zegarka. Ponadto, ponieważ pochodzą z jednego, raczej małego obiektu, zmieniłem je, aby po prostu obserwować Timer.datanieruchomość. Ostatni parametr przekazany do $watchnakazuje mu sprawdzenie głębokiej równości, a nie tylko upewnienie się, że odwołanie jest takie samo.


Aby podać trochę kontekstu, powodem, dla którego wolałbym tę metodę niż umieszczanie wartości usługi bezpośrednio w zakresie, jest zapewnienie właściwego rozdzielenia obaw. Twój pogląd nie powinien wymagać nic o twoich usługach, aby działać. Zadaniem kontrolera jest sklejenie wszystkiego razem; jego zadaniem jest pobieranie danych z Twoich usług i przetwarzanie ich w dowolny niezbędny sposób, a następnie dostarczanie Twojemu widokowi wszelkich potrzebnych mu szczegółów. Ale nie sądzę, że jego zadaniem jest po prostu przekazanie usługi bezpośrednio do widoku. W przeciwnym razie co w ogóle robi kontroler? Programiści AngularJS kierowali się tym samym rozumowaniem, gdy zdecydowali się nie włączać żadnej „logiki” do szablonów (np. ifInstrukcji).

Szczerze mówiąc, jest tu prawdopodobnie wiele perspektyw i nie mogę się doczekać innych odpowiedzi.

Josh David Miller
źródło
3
Czy możesz trochę rozwinąć? Wolisz zegarki $, ponieważ widok jest mniej powiązany z usługą? Tj {{lastUpdated}}vs.{{timerData.lastUpdated}}
Mark Rajcok
2
@BarDev, aby użyć go Timer.dataw $ watch, Timermusi być zdefiniowany w zakresie $ scope, ponieważ wyrażenie ciągu przekazywane do $ watch jest oceniane względem zakresu. Oto plunker, który pokazuje, jak to działa. Parametr objectEquality jest tutaj udokumentowany - trzeci parametr - ale niezbyt dobrze wyjaśniony.
Mark Rajcok,
2
Pod względem wydajności $watchjest dość nieefektywny. Zobacz odpowiedzi na stackoverflow.com/a/17558885/932632 i stackoverflow.com/questions/12576798/ ...
Krym
11
@Kyrm To prawdopodobnie prawda w niektórych okolicznościach, ale gdy mamy do czynienia z wydajnością, musimy szukać „klinicznie istotnych” ulepszeń wydajności, a nie tylko statystycznie istotnych, uogólnionych. Jeśli występuje problem z wydajnością w istniejącej aplikacji, należy go rozwiązać. W przeciwnym razie jest to tylko przypadek przedwczesnej optymalizacji, która prowadzi do trudniejszego do odczytania, podatnego na błędy kodu, który nie jest zgodny z najlepszymi praktykami i który nie ma zademonstrowanych korzyści.
Josh David Miller,
1
Zakładając, że obserwator używa funkcji do wywołania metody pobierającej, to tak. To dobrze działa. Jestem również zwolennikiem usług zwracających wystąpienia w kolekcji, w których funkcje getter i setter w es5 są całkiem dobre.
Josh David Miller,
19

Spóźniony na przyjęcie, ale dla przyszłych pracowników Google - nie używaj podanej odpowiedzi.

JavaScript ma mechanizm przekazywania obiektów przez odniesienie, podczas gdy przekazuje tylko płytką kopię wartości „liczby, ciągi znaków itp.”.

W powyższym przykładzie, zamiast wiązać atrybuty usługi, dlaczego nie ujawnimy usługi w zakresie?

$scope.hello = HelloService;

To proste podejście sprawi, że Angular będzie w stanie wykonać dwukierunkowe wiązanie i wszystkie magiczne rzeczy, których potrzebujesz. Nie hakuj kontrolera za pomocą obserwatorów lub niepotrzebnych znaczników.

A jeśli obawiasz się, że widok przypadkowo nadpisuje atrybuty usługi, użyj, definePropertyaby uczynić go czytelnym, wyliczalnym, konfigurowalnym lub zdefiniować metody pobierające i ustawiające. Możesz uzyskać dużą kontrolę, czyniąc swoją usługę solidniejszą.

Ostatnia wskazówka: jeśli poświęcasz więcej czasu na pracę nad kontrolerem niż na usługach, robisz to źle :(.

W tym konkretnym kodzie demonstracyjnym, który dostarczyłeś, radziłbym:

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

Edytować:

Jak wspomniałem powyżej, możesz kontrolować zachowanie atrybutów usługi za pomocą defineProperty

Przykład:

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

Teraz w naszym kontrolerze, jeśli to zrobimy

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

nasza usługa propertyWithSetterjakoś zmieni wartość, a także umieści nową wartość w bazie danych!

Lub możemy przyjąć dowolne podejście.

Zapoznaj się z dokumentacją MDN dotyczącą defineProperty.

Zalaboza
źródło
Jestem $scope.model = {timerData: Timer.data};prawie pewien, że to właśnie opisałem powyżej , po prostu dołączając go do modelu zamiast bezpośrednio na Scope.
Scott Silvi
1
AFAIK, odwołanie do obiektu js działa dla wszystkiego w usłudze. Kodowanie w ten sposób: $ scope.controllerVar = ServiceVar, wszystko co robisz w $ scope.controllerVar jest również wykonywane w ServiceVar.
Kai Wang
@KaiWang zgadzam się, chyba że zdecydowałeś się użyć DefineAttribute, możesz sprawić, aby Twoje usługi miały funkcję ustawiania, aby zapobiec przypadkowej mutacji danych usług przez kontrolerów.
Zalaboza
12

Myślę, że to pytanie ma element kontekstowy.

Jeśli po prostu pobierasz dane z usługi i emitujesz te informacje do jej widoku, myślę, że powiązanie bezpośrednio z właściwością usługi jest w porządku. Nie chcę pisać dużo standardowego kodu aby po prostu zmapować właściwości usługi na właściwości modelu do wykorzystania w moim widoku.

Co więcej, wydajność w kątach opiera się na dwóch rzeczach. Pierwsza to liczba powiązań na stronie. Drugi to kosztowne funkcje pobierające. Misko mówi o tym tutaj

Jeśli potrzebujesz wykonać logikę specyficzną dla instancji na danych usługi (w przeciwieństwie do masowania danych stosowanego w samej usłudze), a wynik tego wpływa na model danych wyświetlany w widoku, to powiedziałbym, że $ watcher jest odpowiedni, ponieważ pod warunkiem, że funkcja nie jest strasznie droga. W przypadku drogiej funkcji sugerowałbym buforowanie wyników w zmiennej lokalnej (do kontrolera), wykonywanie złożonych operacji poza funkcją $ watcher, a następnie wiązanie zakresu z wynikiem tego.

Uwaga: nie powinieneś zawieszać żadnych właściwości bezpośrednio na swoim $ scope. $scopeZmienna nie jest dany model. Zawiera odniesienia do Twojego modelu.

Moim zdaniem „najlepsza praktyka” polegająca na prostym przekazywaniu informacji z serwisu do widoku:

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

A wtedy twój widok zawierałby {{model.timerData.lastupdated}}.

Scott Silvi
źródło
ostrożnie z tą sugestią, ktoś, kto nie jest ekspertem w javascript, mógłby spróbować zrobić to z właściwością, która jest ciągiem znaków. W takim przypadku javascript nie odwołuje się do obiektu, ale nieprzetworzony kopiuje ciąg. (i to jest złe, ponieważ nie jest aktualizowane, jeśli się zmienia)
Valerio,
7
Czy nie zakryłem tego moim 'zastrzeżeniem', że zawsze powinieneś używać kropki (co oznacza, że ​​nie zawieszaj jej poza $ scope, ale poza $ scope.model). Jeśli masz $ scope.model.someStringProperty i odwołujesz się do model.someStringProperty w swoim widoku, zostanie on zaktualizowany, ponieważ wewnętrzne obserwatory będą znajdować się na obiekcie, a nie na właściwości.
Scott Silvi,
6

Opierając się na powyższych przykładach, pomyślałem, że wrzucę sposób przezroczystego powiązania zmiennej kontrolera ze zmienną usługi.

W poniższym przykładzie zmiany w $scope.countzmiennej Controller zostaną automatycznie odzwierciedlone w countzmiennej Service .

W środowisku produkcyjnym faktycznie używamy tego powiązania do aktualizowania identyfikatora usługi, która następnie asynchronicznie pobiera dane i aktualizuje zmienne usługi. Dalsze powiązanie oznacza, że ​​kontrolery są automatycznie aktualizowane, gdy usługa aktualizuje się sama.

Poniższy kod można zobaczyć na stronie http://jsfiddle.net/xuUHS/163/

Widok:

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

Serwis / kontroler:

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
źródło
To niesamowite rozwiązanie, dziękuję, bardzo mi pomogło, bardzo sprytny sposób na zrobienie tego :)
Javis Perez
2

Myślę, że to lepszy sposób na powiązanie samej usługi zamiast atrybutów w niej.

Dlatego:

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

Możesz to zagrać na tym plunkrze .

Trantor Liu
źródło
1

Wolałbym, aby moi obserwatorzy byli mniej, jak to możliwe. Mój rozum oparty jest na moich doświadczeniach i można by to spierać teoretycznie.
Problem z używaniem obserwatorów polega na tym, że możesz użyć dowolnej właściwości w zakresie do wywołania dowolnej metody w dowolnym komponencie lub usłudze.
W prawdziwym projekcie wkrótce skończysz z niemożliwym do śledzenia (lepiej mówiąc trudnym do wyśledzenia) łańcuchem wywoływanych metod i zmienianych wartości, co szczególnie sprawia, że ​​proces wdrażania jest tragiczny.

Reyraa
źródło
0

Powiązanie jakichkolwiek danych, które wysyła usługę nie jest dobrym pomysłem (architektura), ale jeśli już tego potrzebujesz, sugeruję 2 sposoby, aby to zrobić

1) możesz uzyskać dane, które nie są w Twoim serwisie. Możesz uzyskać dane wewnątrz swojego kontrolera / dyrektywy i nie będziesz miał problemu z ich powiązaniem

2) możesz używać zdarzeń angularjs, kiedy tylko chcesz, możesz wysłać sygnał (z $ rootScope) i złapać go w dowolnym miejscu, a nawet wysłać dane dotyczące tego eventName.

Może to ci pomoże. Jeśli potrzebujesz więcej przykładów, oto link

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

Hazarapet Tunanyan
źródło
-1

Co powiesz na

scope = _.extend(scope, ParentScope);

Gdzie ParentScope jest usługą wstrzykniętą?

tomascharad
źródło
-2

Najbardziej eleganckie rozwiązania ...

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

Piszę też EDA (architektury sterowane zdarzeniami), więc zwykle robię coś podobnego do następującego [uproszczona wersja]:

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

Następnie umieszczam odbiornik w moim kontrolerze na wybranym kanale i po prostu aktualizuję mój lokalny zakres w ten sposób.

Podsumowując, nie ma wiele z „Dobrych Praktyk” - a raczej jego głównie preferencji - tak długo, jak jesteś utrzymanie rzeczy stałych i zatrudniania słabego sprzężenia. Powodem, dla którego zalecałbym ten drugi kodeks, jest to, że EDA mają najniższe możliwe połączenie z natury. A jeśli nie przejmujesz się tym zbytnio, unikajmy wspólnej pracy nad tym samym projektem.

Mam nadzieję że to pomoże...

Cody
źródło