AngularJS: Jak mogę przekazywać zmienne między kontrolerami?

326

Mam dwa kontrolery Angular:

function Ctrl1($scope) {
    $scope.prop1 = "First";
}

function Ctrl2($scope) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

Nie mogę użyć w Ctrl1środku, Ctrl2ponieważ jest niezdefiniowany. Jeśli jednak spróbuję przekazać to tak…

function Ctrl2($scope, Ctrl1) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

Dostaję błąd Czy ktoś wie jak to zrobić?

Robić

Ctrl2.prototype = new Ctrl1();

Również się nie udaje.

UWAGA: Te kontrolery nie są zagnieżdżone w sobie.

dopatraman
źródło
Istnieje wiele sposobów, ale najlepszym sposobem jest zegarek kątowy. Zawsze, gdy korzystamy z frameworka, to najlepszy sposób na wykorzystanie własnych metod do pracy, nie zapomnij o tym
pejman
Uważam, że ten blog jest bardzo pomocny Blog
Black Mamba

Odpowiedzi:

503

Jednym ze sposobów udostępniania zmiennych między wieloma kontrolerami jest utworzenie usługi i wstrzyknięcie jej do dowolnego kontrolera, w którym chcesz z niej korzystać.

Przykład prostej usługi:

angular.module('myApp', [])
    .service('sharedProperties', function () {
        var property = 'First';

        return {
            getProperty: function () {
                return property;
            },
            setProperty: function(value) {
                property = value;
            }
        };
    });

Korzystanie z usługi w kontrolerze:

function Ctrl2($scope, sharedProperties) {
    $scope.prop2 = "Second";
    $scope.both = sharedProperties.getProperty() + $scope.prop2;
}

Jest to bardzo ładnie opisane na tym blogu (lekcja 2 i dalsze).

Przekonałem się, że jeśli chcesz powiązać te właściwości na wielu kontrolerach, działa to lepiej, jeśli powiążesz z właściwością obiektu zamiast typu pierwotnego (wartość logiczna, łańcuch, liczba), aby zachować powiązane odniesienie.

Przykład: var property = { Property1: 'First' };zamiast var property = 'First';.


AKTUALIZACJA: Aby (miejmy nadzieję) wyjaśnić tutaj, jest skrzypce, która pokazuje przykład:

  • Powiązanie ze statycznymi kopiami wspólnej wartości (w myController1)
    • Powiązanie z operacją podstawową (ciąg)
    • Powiązanie z właściwością obiektu (zapisaną w zmiennej zasięgu)
  • Powiązanie z wartościami współdzielonymi, które aktualizują interfejs użytkownika podczas aktualizowania wartości (w myController2)
    • Powiązanie z funkcją zwracającą prymityw (ciąg)
    • Powiązanie z właściwością obiektu
    • Dwukierunkowe wiązanie z właściwością obiektu
Gloopy
źródło
5
W takim przypadku - w jaki sposób zakres Ctrl2 „wiedziałby”, kiedy sharedProperties.getProperty () zmienia wartość?
OpherV
5
Jeśli chcesz, aby Twój interfejs użytkownika aktualizował się za każdym razem, gdy zmienia się właściwość, możesz zmienić bothgo na funkcję i będzie on wywoływany / ponownie oceniany podczas procesu analizy kątowej. Zobacz to skrzypce na przykład. Również jeśli powiążesz z właściwością obiektu, możesz użyć go bezpośrednio w swoim widoku i zaktualizuje się on wraz ze zmianą danych, podobnie jak w tym przykładzie .
Gloopy,
11
Jeśli chcesz wykryć i zareagować na zmiany w kontrolerze, możesz dodać getProperty()funkcję do zakresu i użyć $ scope. $ Watch jak w tym przykładzie . Mam nadzieję, że te przykłady pomogą!
Gloopy,
1
Występuje tutaj problem, ponieważ usługi powinny być bezpaństwowcami. Przechowywanie nieruchomości wewnątrz usługi jest nieprawidłowe (ale wygodne). Zacząłem używać $ cacheFactory do odczytu i zapisu danych. Używam prawie identycznej usługi jak Gloopy, ale zamiast zapisywać stan w usłudze, jest ona teraz w pamięci podręcznej. Najpierw utwórz usługę pamięci podręcznej: angular.module ('CacheService', ['ng']) .factory ('CacheService', function ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Dołącz do pliku app.js, wstrzyknij go do usługi, użyj go w następujący sposób: return CacheService.get (key); lub CacheService.put (klucz, wartość);
Jordan Papaleo
4
Próbuję zrozumieć, w jaki sposób i dlaczego ta odpowiedź używa .servicezamiast .factoryopisanego w dokumentach Angular. Dlaczego ta odpowiedź jest tak wysoko oceniana, skoro dokumentacja używa innej metody?
pspahn
44

Lubię ilustrować proste rzeczy prostymi przykładami :)

Oto bardzo prosty Serviceprzykład:


angular.module('toDo',[])

.service('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  this.dataObj = _dataObj;
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

A tutaj jsbin

A oto bardzo prosty Factoryprzykład:


angular.module('toDo',[])

.factory('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  return {
    dataObj: _dataObj
  };
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

A tutaj jsbin


Jeśli jest to zbyt proste, oto bardziej wyrafinowany przykład

Również zobaczyć odpowiedź tutaj najlepszych praktyk związanych komentarzach

Dmitri Zaitsev
źródło
1
Tak, zgadzam się z wami. Zawsze staraj się uprościć sprawę.
Evan Hu
Jaki jest sens deklarowania, var _dataObj = {};gdy zwracasz bezpośrednie odniesienie do niego ..? To nie jest prywatne . W pierwszym przykładzie możesz to zrobić, this.dataObj = {};aw drugim return { dataObj: {} };jest to bezużyteczna deklaracja zmiennej IMHO.
TJ,
@TJ Chodzi o to, aby udostępnić tę zmienną innym komponentom. Jest to podstawowy przykład ilustrujący koncepcję udostępniania. Zmienna JEST prywatna w bloku, a następnie ujawniasz ją jako zmienną publiczną za pomocą wzorca ujawniającego. W ten sposób rozdziela się obowiązki między trzymaniem zmiennej a jej używaniem.
Dmitri Zaitsev,
@DmitriZaitsev mówisz „proste przykłady”, ale jeśli nie pokażesz, jak korzystać ze stanu prywatnego, po prostu mylisz ludzi. W twoim przykładzie nie ma stanu prywatnego, o ile zwrócisz bezpośrednie odniesienie.
TJ,
@TJ Nie widzę nic mylącego. Zmienna prywatna może być ujawniona przez moduł. Napisz lepszą odpowiedź.
Dmitri Zaitsev,
26

--- Wiem, że ta odpowiedź nie dotyczy tego pytania, ale chcę, aby osoby, które przeczytały to pytanie i chciały obsługiwać usługi takie jak fabryki, aby uniknąć problemów z zrobieniem tego ----

W tym celu musisz skorzystać z Usługi lub Fabryki.

Usługi są NAJLEPSZĄ PRAKTYKĄ w zakresie udostępniania danych między nie zagnieżdżonymi kontrolerami.

Bardzo dobrą adnotacją na ten temat na temat udostępniania danych jest sposób deklarowania obiektów. Miałem pecha, ponieważ wpadłem w pułapkę AngularJS, zanim o tym przeczytałem, i byłem bardzo sfrustrowany. Pozwól, że pomogę ci uniknąć tego problemu.

Czytam z „ng-book: Cała książka o AngularJS”, że modele ng AngularJS, które są tworzone w kontrolerach jako same dane, są NIEPRAWIDŁOWE!

Element zakresu $ powinien zostać utworzony w następujący sposób:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // best practice, always use a model
  $scope.someModel = {
    someValue: 'hello computer'
  });

I nie tak:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // anti-pattern, bare value
  $scope.someBareValue = 'hello computer';
  };
});

Jest tak, ponieważ zaleca się (NAJLEPSZA PRAKTYKA), aby DOM (dokument HTML) zawierał wywołania jako

<div ng-model="someModel.someValue"></div>  //NOTICE THE DOT.

Jest to bardzo pomocne dla kontrolerów zagnieżdżonych, jeśli chcesz, aby kontroler podrzędny mógł zmienić obiekt z kontrolera nadrzędnego ....

Ale w twoim przypadku nie chcesz zagnieżdżonych zakresów, ale istnieje podobny aspekt pobierania obiektów z usług do kontrolerów.

Załóżmy, że masz swoją usługę „Fabryka”, aw przestrzeni powrotnej znajduje się obiekt A zawierający obiekt B zawierający obiekt C.

Jeśli ze swojego kontrolera chcesz dostać obiekt C do swojego zakresu, błędem jest powiedzieć:

$scope.neededObjectInController = Factory.objectA.objectB.objectC;

To nie zadziała ... Zamiast tego użyj tylko jednej kropki.

$scope.neededObjectInController = Factory.ObjectA;

Następnie w DOM można wywołać objectC z objectA. Jest to najlepsza praktyka związana z fabrykami, a co najważniejsze, pomoże uniknąć nieoczekiwanych i niemożliwych do złapania błędów.

AFP_555
źródło
2
Myślę, że to dobra odpowiedź, ale jest dość trudna do strawienia.
pspahn
17

Rozwiązanie bez tworzenia usługi przy użyciu $ rootScope:

Aby udostępnić właściwości między kontrolerami aplikacji, możesz użyć Angular $ rootScope. To kolejna opcja udostępniania danych, dzięki czemu ludzie o nich wiedzą.

Preferowanym sposobem udostępniania niektórych funkcji między kontrolerami są Usługi, do odczytu lub zmiany globalnej właściwości, której można użyć $ rootscope.

var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = true;
}]);

app.controller('Ctrl2', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = false;
}]);

Używanie $ rootScope w szablonie (Dostęp do właściwości za pomocą $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
źródło
5
W tym momencie używasz zmiennych o globalnym zasięgu, co odbiega od idei AngularJS polegającej na lokalnym określaniu zakresu wszystkich jego struktur. Dodanie pliku zmiennej globalnej osiągnęłoby to samo i ułatwiłoby odnalezienie pierwotnej definicji zmiennej. Tak czy inaczej, nie sugerowane.
Organiccat
4
@Organiccat - Rozumiem twoje obawy i dlatego już wspomniałem, że preferowanym sposobem będą usługi, bez wątpienia. Ale twój kąt również to zapewnia. To od Ciebie zależy, jak chcesz zarządzać swoją globalną. Miałem scenariusz, w którym to podejście działało najlepiej dla mnie.
Sanjeev
8

Powyższa próbka działała jak urok. Właśnie zrobiłem modyfikację na wypadek, gdy muszę zarządzać wieloma wartościami. Mam nadzieję, że to pomoże!

app.service('sharedProperties', function () {

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});
Juan Zamora
źródło
1
Stworzyłem również próbkę za pomocą usługi do udostępniania danych między różnymi kontrolerami. Mam nadzieję, że wam się spodoba. jsfiddle.net/juazammo/du53553a/1
Juan Zamora
1
Mimo że działa, jest to zwykle składnia .factory. .servicePowinny być stosowane „jeśli zdefiniować jako usługi typu / klasy” jak za docs.angularjs.org/api/auto/service/$provide#service
Dmitri Zaitsev
1
Dmitri, masz rację, jednak Angularowie z mojej perspektywy zmienili trochę koncepcję między usługami (fasadami) a fabrykami ... no cóż ...
Juan Zamora
1
I popraw mnie, jeśli się mylę, usługi mają na celu zwrócenie czegoś, co może być przedmiotem lub wartością. Fabryki służą do tworzenia obiektów. Serwis, który w rzeczywistości jest zbiorem funkcji, które coś zwracają, to, o czym myślałem, gdzie usługi. W tym wywoływanie funkcjonalności z fabryk. Ponownie wchodzę w podstawowe pojęcie, co to jest dla mnie, a nie to, co faktycznie jest z perspektywy kątowej. (Abstract Factory dofactory.com/net/abstract-factory-design-pattern ) i podejście adaptacyjne ujawnię jako usługę
Juan Zamora
1
Sprawdź wzór adaptera tutaj dofactory.com/net/adapter-design-pattern
Juan Zamora
6

Zwykle używam wartości, chętnie dyskutując, dlaczego to zły pomysł.

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

myApp.value('sharedProperties', {}); //set to empty object - 

Następnie wprowadź wartość zgodnie z usługą.

Ustaw w ctrl1:

myApp.controller('ctrl1', function DemoController(sharedProperties) {
  sharedProperties.carModel = "Galaxy";
  sharedProperties.carMake = "Ford";
});

i dostęp z ctrl2:

myApp.controller('ctrl2', function DemoController(sharedProperties) {
  this.car = sharedProperties.carModel + sharedProperties.carMake; 

});
Chłodzony płomień
źródło
czym to się różni od korzystania z usługi?
dopatraman
5

Poniższy przykład pokazuje, jak przekazywać zmienne między kontrolerami rodzeństwa i podejmować działania w przypadku zmiany wartości.

Przykład zastosowania: na pasku bocznym znajduje się filtr, który zmienia zawartość innego widoku.

angular.module('myApp', [])

  .factory('MyService', function() {

    // private
    var value = 0;

    // public
    return {
      
      getValue: function() {
        return value;
      },
      
      setValue: function(val) {
        value = val;
      }
      
    };
  })
  
  .controller('Ctrl1', function($scope, $rootScope, MyService) {

    $scope.update = function() {
      MyService.setValue($scope.value);
      $rootScope.$broadcast('increment-value-event');
    };
  })
  
  .controller('Ctrl2', function($scope, MyService) {

    $scope.value = MyService.getValue();

    $scope.$on('increment-value-event', function() {    
      $scope.value = MyService.getValue();
    });
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="myApp">
  
  <h3>Controller 1 Scope</h3>
  <div ng-controller="Ctrl1">
    <input type="text" ng-model="value"/>
    <button ng-click="update()">Update</button>
  </div>
  
  <hr>
  
  <h3>Controller 2 Scope</h3>
  <div ng-controller="Ctrl2">
    Value: {{ value }}
  </div>  

</div>

Zanon
źródło
4

Chciałbym przyczynić się do tego pytania, wskazując, że zalecanym sposobem udostępniania danych między administratorami, a nawet dyrektywami, jest korzystanie z usług (fabryk), jak już wskazano, ale także chciałbym zapewnić praktyczny przykład tego, jak to zrobić.

Oto działający plunker: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Po pierwsze, tworzyć swoją usługę , która będzie mieć swoje dane udostępnione :

app.factory('SharedService', function() {
  return {
    sharedObject: {
      value: '',
      value2: ''
    }
  };
});

Następnie po prostu wstrzyknij go do kontrolerów i pobierz współdzielone dane ze swojego zakresu:

app.controller('FirstCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('SecondCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('MainCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

Możesz to również zrobić dla swoich dyrektyw , działa to w ten sam sposób:

app.directive('myDirective',['SharedService', function(SharedService){
  return{
    restrict: 'E',
    link: function(scope){
      scope.model = SharedService.sharedObject;
    },
    template: '<div><input type="text" ng-model="model.value"/></div>'
  }
}]);

Mam nadzieję, że ta praktyczna i czysta odpowiedź może komuś pomóc.

Fedaykin
źródło
3

Możesz to zrobić za pomocą usług lub fabryk. Są one zasadniczo takie same dla niektórych podstawowych różnic. To wyjaśnienie na thinkster.io uważam za najłatwiejsze do naśladowania. Prosty, konkretny i skuteczny.

Noahdecoco
źródło
1
„Mógłbyś to zrobić za pomocą usług lub fabryk” - Jak ..? Jak to zrobić, o co prosi OP ... prosimy o umieszczenie pełnej odpowiedzi w samym przepełnieniu stosu zamiast łączenia się z zasobami zewnętrznymi, linki mogą przestać działać z czasem.
TJ,
2

Czy nie można również uczynić tej właściwości częścią nadrzędną dla zakresów?

$scope.$parent.property = somevalue;

Nie mówię, że to prawda, ale działa.

SideFX
źródło
3
Autor stwierdził, że NOTE: These controllers are not nested inside each other.. Gdyby to były zagnieżdżone kontrolery lub kontrolery, które współużytkowały tego samego rodzica, działałoby to, ale nie możemy się tego spodziewać.
Chris Foster
2
Zasadą jest, aby polegać, $parentjeśli można tego uniknąć. Dobrze zaprojektowany komponent wielokrotnego użytku nie powinien wiedzieć o swoich rodzicach.
Dmitri Zaitsev
2

Ach, miej trochę tych nowych rzeczy jako kolejną alternatywę. Jest to magazyn lokalny i działa tam, gdzie działa kąt. Nie ma za co. (Ale naprawdę, dziękuję facetowi)

https://github.com/gsklee/ngStorage

Określ swoje ustawienia domyślne:

$scope.$storage = $localStorage.$default({
    prop1: 'First',
    prop2: 'Second'
});

Uzyskaj dostęp do wartości:

$scope.prop1 = $localStorage.prop1;
$scope.prop2 = $localStorage.prop2;

Zapisz wartości

$localStorage.prop1 = $scope.prop1;
$localStorage.prop2 = $scope.prop2;

Pamiętaj, aby wstrzyknąć ngStorage do swojej aplikacji i $ localStorage do kontrolera.

kJamesy
źródło
1
To rozwiązuje inny problem - trwałe przechowywanie. Nie jest to skalowalne rozwiązanie tego problemu, ponieważ powoduje wyciek kodu z efektami ubocznymi, takimi jak modyfikacja lokalnego obiektu pamięci masowej, między innymi z podatnością na konflikt nazw.
Dmitri Zaitsev,
1

Można to zrobić na dwa sposoby

1) Skorzystaj z usługi get / set

2) $scope.$emit('key', {data: value}); //to set the value

 $rootScope.$on('key', function (event, data) {}); // to get the value
Rohan Kawade
źródło
1

Drugie podejście:

angular.module('myApp', [])
  .controller('Ctrl1', ['$scope',
    function($scope) {

    $scope.prop1 = "First";

    $scope.clickFunction = function() {
      $scope.$broadcast('update_Ctrl2_controller', $scope.prop1);
    };
   }
])
.controller('Ctrl2', ['$scope',
    function($scope) {
      $scope.prop2 = "Second";

        $scope.$on("update_Ctrl2_controller", function(event, prop) {
        $scope.prop = prop;

        $scope.both = prop + $scope.prop2; 
    });
  }
])

HTML:

<div ng-controller="Ctrl2">
  <p>{{both}}</p>
</div>

<button ng-click="clickFunction()">Click</button>

Aby uzyskać więcej informacji, zobacz plunker:

http://plnkr.co/edit/cKVsPcfs1A1Wwlud2jtO?p=preview

Codiee
źródło
1
Działa tylko wtedy, gdy Ctrl2(nasłuchujący) jest kontrolerem potomnym Ctrl1. Kontrolery rodzeństwa muszą się komunikować za pośrednictwem $rootScope.
herzbube
0

Jeśli nie chcesz świadczyć usługi, możesz to zrobić w ten sposób.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
dziękczynienie
źródło
37
Nie wątpię, że ta odpowiedź działa, ale chcę zauważyć, że jest to sprzeczne z filozofią AngularJS, aby nigdy nie mieć obiektów DOM w kodzie modelu / kontrolera.
JoeCool
3
-1, ponieważ moim zdaniem komunikacja kontrolera przez DOM jest słabą praktyką.
Chris Foster
3
@ChrisFoster, tylko dlatego, że młotek jest sprzedawany jako „narzędzie”, nie oznacza to, że nie można go używać jako ciężaru papieru. Jestem pewien, że dla każdego frameworka lub narzędzia zawsze znajdziesz programistów, którzy muszą „zgiąć” listę „najlepszych praktyk”.
Andrei V
5
@AndreiV - Słaba analogia, nie ma wady używania młotka jako ciężaru papieru. Takie złe praktyki mają wyraźne wady i mogą łatwo doprowadzić do powstania kodu spaghetti. Powyższy kod jest delikatny, ponieważ teraz zależy od tego, gdzie kontroler znajduje się w DOM i jest bardzo trudny do przetestowania. Korzystanie z usługi jest lepszym rozwiązaniem z jakiegoś powodu, ponieważ nie wiąże implementacji z szablonem. Zgadzam się, że programiści często muszą naginać listę najlepszych praktyk, ale nie wtedy, gdy istnieje jasna, wspólna, bardziej modularna najlepsza praktyka, która działa lepiej.
Chris Foster
-1

Oprócz $ rootScope i usług, istnieje czyste i łatwe alternatywne rozwiązanie do rozszerzenia kąta w celu dodania współdzielonych danych:

w kontrolerach:

angular.sharedProperties = angular.sharedProperties 
    || angular.extend(the-properties-objects);

Te właściwości należą do „kątowego” obiektu, oddzielonego od zakresów i mogą być współużytkowane w zakresach i usługach.

Zaletą tego jest to, że nie musisz wstrzykiwać obiektu: są one dostępne w dowolnym miejscu natychmiast po zdeklasowaniu!

williamjxj
źródło
2
To tak, jakby mieć zmienne globalne w całym windowobiekcie ... Jeśli zamierzasz zanieczyścić kątowo, dlaczego po prostu nie zanieczyszczaj obiektu okna ...
TJ