Обмін даними між контролерами AngularJS


362

Я намагаюся ділитися даними між контролерами. Приклад використання - це багатоетапна форма, дані, що вводяться на одному вході, пізніше використовуються у кількох місцях відображення поза вихідним контролером. Код нижче та у jsfiddle тут .

HTML

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="FirstName"><!-- Input entered here -->
    <br>Input is : <strong>{{FirstName}}</strong><!-- Successfully updates here -->
</div>

<hr>

<div ng-controller="SecondCtrl">
    Input should also be here: {{FirstName}}<!-- How do I automatically updated it here? -->
</div>

JS

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// make a factory to share data between controllers
myApp.factory('Data', function(){
    // I know this doesn't work, but what will?
    var FirstName = '';
    return FirstName;
});

// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

Будь-яка допомога дуже вдячна.


Відповіді:


481

Просте рішення - дозволити фабриці повернути об'єкт і дозволити вашим контролерам працювати з посиланням на той самий об'єкт:

JS:

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// Create the factory that share the Fact
myApp.factory('Fact', function(){
  return { Field: '' };
});

// Two controllers sharing an object that has a string in it
myApp.controller('FirstCtrl', function( $scope, Fact ){
  $scope.Alpha = Fact;
});

myApp.controller('SecondCtrl', function( $scope, Fact ){
  $scope.Beta = Fact;
});

HTML:

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="Alpha.Field">
    First {{Alpha.Field}}
</div>

<div ng-controller="SecondCtrl">
<input type="text" ng-model="Beta.Field">
    Second {{Beta.Field}}
</div>

Демо: http://jsfiddle.net/HEdJF/

Коли додатки стають масштабнішими, складнішими та складнішими для тестування, ви, можливо, не захочете виставляти весь об'єкт із заводу таким чином, а натомість надаєте обмежений доступ, наприклад, через геттери та сетери:

myApp.factory('Data', function () {

    var data = {
        FirstName: ''
    };

    return {
        getFirstName: function () {
            return data.FirstName;
        },
        setFirstName: function (firstName) {
            data.FirstName = firstName;
        }
    };
});

При такому підході до споживачих контролерів доводиться оновлювати фабрику новими значеннями та стежити за змінами, щоб отримати їх:

myApp.controller('FirstCtrl', function ($scope, Data) {

    $scope.firstName = '';

    $scope.$watch('firstName', function (newValue, oldValue) {
        if (newValue !== oldValue) Data.setFirstName(newValue);
    });
});

myApp.controller('SecondCtrl', function ($scope, Data) {

    $scope.$watch(function () { return Data.getFirstName(); }, function (newValue, oldValue) {
        if (newValue !== oldValue) $scope.firstName = newValue;
    });
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="firstName">
  <br>Input is : <strong>{{firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{firstName}}
</div>

Демо: http://jsfiddle.net/27mk1n1o/


2
Перше рішення працювало найкраще для мене - якщо сервіс підтримував об'єкт, а контролери просто обробляли посилання. Мухо дякую.
johnkeese

5
Метод $. $ watch прекрасно працює для здійснення виклику відпочинку з однієї області та застосування результатів до іншої області
aclave1

Замість того, щоб повертати такий об’єкт, як return { FirstName: '' };або повернути об'єкт, що містить методи, які взаємодіють з об'єктом, не було б переважним повернути екземпляр класу ( function), щоб при використанні вашого об'єкта він мав певний тип і це не тільки "Об'єкт {}"?
RPDeshaies

8
Просто голова вгору, функція слухача не потребує newValue !== oldValueперевірки, оскільки Angular не виконує цю функцію, якщо oldValue і newValue однакові. Єдиний виняток з цього - якщо ви дбаєте про початкове значення. Дивіться: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
C.

@tasseKATT, Це добре, якщо я не оновлюю сторінку. Чи можете ви запропонувати мені будь-яке рішення, якщо я оновлю сторінку ???
MaNn

71

Я вважаю за краще не використовувати $watchдля цього. Замість того, щоб призначати всю службу в область контролера, ви можете призначити лише дані.

JS:

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

myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    }
    // Other methods or objects can go here
  };
});

myApp.controller('FirstCtrl', function($scope, MyService){
  $scope.data = MyService.data;
});

myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="data.firstName">
  <br>Input is : <strong>{{data.firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{data.firstName}}
</div>

Можна також оновити дані служби прямим методом.

JS:

// A new factory with an update method
myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    },
    update: function(first, last) {
      // Improve this method as needed
      this.data.firstName = first;
      this.data.lastName = last;
    }
  };
});

// Your controller can use the service's update method
myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;

   $scope.updateData = function(first, last) {
     MyService.update(first, last);
   }
});

Ви не використовуєте явно watch (), але внутрішньо AngularJS робить оновлення подання новими значеннями в $ змінних змінних. З точки зору продуктивності це те саме чи ні?
Март Домінгуес

4
Я не впевнений у ефективності будь-якого методу. З того, що я розумію, Angular вже працює дайвінг циклу в області застосування, тому налаштування додаткових виразів годинника, ймовірно, додає більше роботи для Angular. Вам доведеться запустити тест на працездатність, щоб справді це з'ясувати. Я просто віддаю перевагу передачі та обміну даними за допомогою відповіді вище над оновленням через вирази $ watch.
Бенник

2
Це не альтернатива $ watch. Це інший спосіб обміну даними між двома кутовими об'єктами, в даному випадку контролерами, без використання $ watch.
Бенник

1
IMO це рішення є найкращим, оскільки воно більше відповідає духу декларативного підходу Angular, на відміну від додавання $ watch висловлювань, що відчуває трохи більше jQuery-esque.
JamesG

8
Чому я це не найбільш голосований відповідь? Адже $ watch робить код складнішим
Shyamal Parikh

11

Існує багато способів обміну даними між контролерами

  1. користуючись послугами
  2. користуючись послугами $ state.go
  3. використовуючи параметри держави
  4. за допомогою кореневого кореня

Пояснення кожного методу:

  1. Я не збираюся пояснювати так, як це вже хтось пояснив

  2. використовуючи $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparamпрацює аналогічно тому $state.go, ви передаєте його як об'єкт від контролера відправника і збираєте в контролер приймача за допомогою stateparam

  4. використовуючи $rootscope

    (a) відправлення даних від дочірнього до батьківського контролера

      $scope.Save(Obj,function(data) {
          $scope.$emit('savedata',data); 
          //pass the data as the second parameter
      });
    
      $scope.$on('savedata',function(event,data) {
          //receive the data as second parameter
      }); 

    (b) передача даних від батьків до дочірнього контролера

      $scope.SaveDB(Obj,function(data){
          $scope.$broadcast('savedata',data);
      });
    
      $scope.SaveDB(Obj,function(data){`enter code here`
          $rootScope.$broadcast('saveCallback',data);
      });

6

Я створив фабрику, яка керує спільним діапазоном між шаблоном маршруту маршруту, тому ви можете підтримувати спільні дані лише тоді, коли користувачі пересуваються на одному батьківському шляху маршруту.

.controller('CadastroController', ['$scope', 'RouteSharedScope',
    function($scope, routeSharedScope) {
      var customerScope = routeSharedScope.scopeFor('/Customer');
      //var indexScope = routeSharedScope.scopeFor('/');
    }
 ])

Отже, якщо користувач піде в інший шлях маршруту, наприклад, "/ Підтримка", спільні дані для шляху "/ Клієнт" будуть автоматично знищені. Але, якщо замість цього, користувач піде на «дочірні» контури, як-от «/ Клієнт / 1» або «/ Клієнт / список», область не буде знищена.

Ви можете подивитися зразок тут: http://plnkr.co/edit/OL8of9


Використовуйте, $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })якщо у вас є ui.router
Nuno Silva

6

Існує кілька способів обміну даними між контролерами

  • Кутові послуги
  • $ трансляція, метод $ емісія
  • Спілкування батьків з дитиною-контролером
  • $ коренеплід

Як ми знаємо, $rootscopeце не є кращим способом передачі даних або зв'язку, оскільки це глобальна сфера, яка доступна для всього застосування

Для обміну даними між контролерами Angular Js, найкращі практики, наприклад, є кутові послуги. .factory, .service
Для довідки

У разі передачі даних від батьків до контролера дитини ви можете отримати прямий доступ батьківських даних в контролері через дитина $scope
Якщо ви використовуєте , ui-routerто ви можете використовувати , $stateParmasщоб передати параметри URL , як id, name, key, і т.д.

$broadcastтакож є хорошим способом передачі даних між контролерами від батьків до дитини та $emitпередачі даних від дочірнього до батьківського контролерів

HTML

<div ng-controller="FirstCtrl">
   <input type="text" ng-model="FirstName">
   <br>Input is : <strong>{{FirstName}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
   Input should also be here: {{FirstName}}
</div>

JS

myApp.controller('FirstCtrl', function( $rootScope, Data ){
    $rootScope.$broadcast('myData', {'FirstName': 'Peter'})
});

myApp.controller('SecondCtrl', function( $rootScope, Data ){
    $rootScope.$on('myData', function(event, data) {
       $scope.FirstName = data;
       console.log(data); // Check in console how data is coming
    });
});

Перейдіть за посиланням, щоб дізнатися більше про це$broadcast


4

Найпростіше рішення:

Я скористався послугою AngularJS .

Крок 1: Я створив службу AngularJS під назвою SharedDataService.

myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

Крок 2: Створіть два контролери та скористайтеся вище створеною послугою.

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

Крок 3: Просто використовуйте створені контролери у поданні.

<body ng-app="myApp">

<div ng-controller="FirstCtrl">
<input type="text" ng-model="Person.name">
<br>Input is : <strong>{{Person.name}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
Input should also be here: {{Person.name}}
</div>

</body>

Щоб побачити робоче рішення цієї проблеми, натисніть посилання нижче

https://codepen.io/wins/pen/bmoYLr

.html файл:

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="FirstCtrl">
    <input type="text" ng-model="Person.name">
    <br>Input is : <strong>{{Person.name}}</strong>
   </div>

<hr>

  <div ng-controller="SecondCtrl">
    Input should also be here: {{Person.name}}
  </div>

//Script starts from here

<script>

var myApp = angular.module("myApp",[]);
//create SharedDataService
myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
    }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
}]);

</script>


</body>
</html>

1
це дуже чіткий приклад для нас нових людей із використанням angularJs. Як би ви поділилися / передали дані із стосунків батьків / дитини? FirstCtrlє батьком і отримує обіцянку, що SecondCtrl(дитині) також знадобиться? Я не хочу робити два дзвінки api. Дякую.
Кріс22

1

Є ще один спосіб без використання $ watch, використовуючи angular.copy:

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

myApp.factory('Data', function(){

    var service = {
        FirstName: '',
        setFirstName: function(name) {
            // this is the trick to sync the data
            // so no need for a $watch function
            // call this from anywhere when you need to update FirstName
            angular.copy(name, service.FirstName); 
        }
    };
    return service;
});


// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

1

Існує кілька способів зробити це.

  1. Події - вже добре пояснено.

  2. маршрутизатор ui - пояснено вище.

  3. Сервіс - із способом оновлення, показаним вище
  4. BAD - Спостереження за змінами.
  5. Інший підхід для батьків, а не випромінювання та передача бродкасту -

*

<superhero flight speed strength> Superman is here! </superhero>
<superhero speed> Flash is here! </superhero>

*

app.directive('superhero', function(){
    return {
        restrict: 'E',
        scope:{}, // IMPORTANT - to make the scope isolated else we will pollute it in case of a multiple components.
        controller: function($scope){
            $scope.abilities = [];
            this.addStrength = function(){
                $scope.abilities.push("strength");
            }
            this.addSpeed = function(){
                $scope.abilities.push("speed");
            }
            this.addFlight = function(){
                $scope.abilities.push("flight");
            }
        },
        link: function(scope, element, attrs){
            element.addClass('button');
            element.on('mouseenter', function(){
               console.log(scope.abilities);
            })
        }
    }
});
app.directive('strength', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addStrength();
        }
    }
});
app.directive('speed', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addSpeed();
        }
    }
});
app.directive('flight', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addFlight();
        }
    }
});

0

Не впевнений, де я взяв цю схему, але для обміну даними через контролери та зменшення $ rootScope та $ range це чудово працює. Це нагадує реплікацію даних, де у вас є видавці та передплатники. Сподіваюся, це допомагає.

Сервіс:

(function(app) {
    "use strict";
    app.factory("sharedDataEventHub", sharedDataEventHub);

    sharedDataEventHub.$inject = ["$rootScope"];

    function sharedDataEventHub($rootScope) {
        var DATA_CHANGE = "DATA_CHANGE_EVENT";
        var service = {
            changeData: changeData,
            onChangeData: onChangeData
        };
        return service;

        function changeData(obj) {
            $rootScope.$broadcast(DATA_CHANGE, obj);
        }

        function onChangeData($scope, handler) {
            $scope.$on(DATA_CHANGE, function(event, obj) {
                handler(obj);
            });
        }
    }
}(app));

Контролер, який отримує нові дані, а саме Видавець, зробив би щось подібне.

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

Контролер, який також використовує ці нові дані, які називаються Абонент, зробив би щось подібне ...

sharedDataEventHub.onChangeData($scope, function(data) {
    vm.localData.Property1 = data.Property1;
    vm.localData.Property2 = data.Property2;
});

Це буде працювати для будь-якого сценарію. Отже, коли ініціалізується первинний контролер і він отримує дані, він би викликав метод changeData, який потім транслюватиме його всім абонентам цих даних. Це зменшує з'єднання наших контролерів один з одним.


використання 'моделі', яка розділяє стан між контролерами, схоже на варіант, який використовується в основному. На жаль, це не охоплює сценарій, коли ви б оновили дочірній контролер. Як ви "перезавантажуєте" дані з сервера та відтворюєте модель? А як би ви попрацювали з недійсним кешем?
Андрій

Я думаю, що ідея контролерів батьків та дітей є дещо небезпечною і викликає занадто багато залежностей один від одного. Однак, використовуючи їх у поєднанні з UI-маршрутизатором, ви можете легко оновити дані про свою "дитину", особливо якщо ці дані вводяться через конфігурацію стану. Вимкнення кешу відбувається у видавця, і вони відповідають за отримання даних та викликsharedDataEventHub.changeData(newData)
ewahner

Я думаю, що є непорозуміння. Під оновленням я маю на увазі, що користувачі буквально натискають клавішу F5 на клавіатурі і змушує браузер робити повідомлення назад. Якщо ви перебуваєте в перегляді дочірніх даних / деталей, коли це трапляється, немає кому викликати "var someData = yourDataService.getSomeData ()". Питання в тому, як би ви вирішили цей сценарій? хто повинен оновити модель?
Андрій

Залежно від того, як ви активуєте контролери, це може бути дуже просто. Якщо у вас є метод , який активує то контролер , який відповідає за початкову завантаження даних, буде завантажити його , а потім використовувати sharedDataEventHub.changeData(newData)то будь-яка дитина або контролер , який залежить від того, що дані матиме де - то в своєму тілі контролера наступного: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });якщо ви використовували UI -Річніше це можна ввести з конфігурації стану.
ewahner

Ось приклад, який ілюструє проблему, яку я намагаюся описати: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Якщо ви вийдете з планку, перейдіть на сторінку редагування та оновіть браузер, він вийде з ладу. Хто повинен відтворити модель в цьому випадку?
Андрій

-3

Просто зробіть це просто (перевірено версією v1.3.15):

<article ng-controller="ctrl1 as c1">
    <label>Change name here:</label>
    <input ng-model="c1.sData.name" />
    <h1>Control 1: {{c1.sData.name}}, {{c1.sData.age}}</h1>
</article>
<article ng-controller="ctrl2 as c2">
    <label>Change age here:</label>
    <input ng-model="c2.sData.age" />
    <h1>Control 2: {{c2.sData.name}}, {{c2.sData.age}}</h1>
</article>

<script>
    var app = angular.module("MyApp", []);

    var dummy = {name: "Joe", age: 25};

    app.controller("ctrl1", function () {
        this.sData = dummy;
    });

    app.controller("ctrl2", function () {
        this.sData = dummy;
    });
</script>


5
Я думаю, що це було знято з голосу, оскільки це не модульно. dummyє глобальним для обох контролерів, а не ділиться більш модульним шляхом успадкування за допомогою $ range або ControllerAs або за допомогою послуги.
Чад Джонсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.