Обмен данными между контроллерами 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;
});

Любая помощь очень ценится.

johnkeese
источник

Ответы:

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/

tasseKATT
источник
2
Первое решение работало лучше для меня - если бы служба поддерживала объект, а контроллеры просто обрабатывали ссылку. Большое спасибо.
Джонкиз
5
Метод $ scope. $ 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);
   }
});
bennick
источник
Вы явно не используете watch (), но внутри AngularJS обновляет представление новыми значениями в переменных $ scope. С точки зрения производительности это то же самое или нет?
Март Домингес
4
Я не уверен в производительности любого метода. Насколько я понимаю, Angular уже выполняет дайджест-цикл в области видимости, поэтому настройка дополнительных выражений наблюдения, вероятно, добавляет больше работы для Angular. Вы должны запустить тест производительности, чтобы действительно выяснить это. Я просто предпочитаю передачу и обмен данными через ответ выше, чем обновление через выражения $ watch.
Бенник,
2
Это не альтернатива $ watch. Это другой способ обмена данными между двумя объектами Angular, в данном случае контроллерами, без использования $ watch.
Бенник
1
IMO - это лучшее решение, потому что оно в большей степени соответствует духу декларативного подхода Angular, а не добавлению операторов $ watch, которые кажутся более jQuery-esque.
JamesG
8
Интересно, почему это не самый популярный ответ? В конце концов, $ watch делает код более сложным
Shyamal Parikh
11

Существует много способов обмена данными между контроллерами.

  1. пользуясь услугами
  2. используя сервисы $ state.go
  3. используя stateparams
  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

    (а) отправка данных от дочернего к родительскому контроллеру

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

    (б) отправка данных от родителя к дочернему контроллеру

      $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('/');
    }
 ])

Таким образом, если пользователь перейдет к другому пути маршрута, например, «/ Поддержка», общие данные для пути «/ Клиент» будут автоматически уничтожены. Но если вместо этого пользователь перейдет к «дочерним» путям, таким как «/ Customer / 1» или «/ Customer / list», область действия не будет уничтожена.

Вы можете увидеть образец здесь: http://plnkr.co/edit/OL8of9

Обердан Нуньес
источник
Используйте, $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })если у вас есть ui.router
Нуно Силва
6

Есть несколько способов обмена данными между контроллерами

  • Угловые услуги
  • $ широковещательный, метод $ emit
  • Связь между родителем и ребенком
  • $ rootscope

Как мы знаем $rootscope это не предпочтительный способ передачи данных или обмена данными, потому что это глобальная область, которая доступна для всего приложения

Для обмена данными между контроллерами Angular Js лучшими практиками являются, например, сервисы Angular. .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>
wins999
источник
1
Это очень яркий пример для нас, новых людей, использующих angularJs. Как бы вы поделились / передали данные от родителя / ребенка? FirstCtrlявляется родителем и получает обещание, которое SecondCtrl(ребенок) также понадобится? Я не хочу делать два вызова API. Спасибо.
Chris22
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;
});
Hinrich
источник
1

Есть несколько способов сделать это.

  1. События - уже объяснили хорошо.

  2. UI роутер - объяснено выше.

  3. Сервис - с методом обновления, показанным выше
  4. ПЛОХОЙ - Наблюдаю за изменениями.
  5. Другой родитель ребенка подход , а не Emit и brodcast -

*

<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();
        }
    }
});
user2756335
источник
0

Не уверен, где я выбрал этот шаблон, но для совместного использования данных между контроллерами и сокращения $ rootScope и $ scope это прекрасно работает. Это напоминает репликацию данных, когда у вас есть издатели и подписчики. Надеюсь, поможет.

Сервис:

(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, который затем передает это всем подписчикам этих данных. Это уменьшает связь наших контроллеров друг с другом.

ewahner
источник
использование «модели», которая разделяет состояние между контроллерами, похоже на вариант, который в основном используется. К сожалению, это не относится к сценарию, в котором вы обновите дочерний контроллер. Как вы «заново» получаете данные с сервера и воссоздаете модель? И как бы вы справились с аннулированием кэша?
Андрей
Я думаю, что идея родительских и дочерних контроллеров немного опасна и вызывает слишком много зависимостей друг от друга. Однако, при использовании в сочетании с UI-Router, вы можете легко обновить данные вашего «потомка», особенно если эти данные вводятся через конфигурацию состояния. Аннулирование кэша происходит на издателе, в результате чего они будут нести ответственность за получение данных и вызовsharedDataEventHub.changeData(newData)
ewahner
Я думаю, что есть недоразумение. Под обновлением я подразумеваю, что пользователи буквально нажимают клавишу F5 на клавиатуре, и браузер отправляет сообщение обратно. Если вы находитесь в дочернем / подробном представлении, когда это происходит, никто не может вызвать «var someData = yourDataService.getSomeData ()». Вопрос в том, как бы вы справились с этим сценарием? кто должен обновить модель?
Андрей
В зависимости от того, как вы активируете свои контроллеры, это может быть очень просто. Если у вас есть метод, который активирует, то контроллер, отвечающий за первоначальную загрузку данных, загрузит его и затем будет использовать sharedDataEventHub.changeData(newData)любой дочерний элемент или контроллер, который зависит от этих данных, где-то в их теле контроллера: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Если вы использовали пользовательский интерфейс -Router это может быть введено из конфига состояния.
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>

Lumic
источник
5
Я думаю, что за это проголосовали, потому что это не модульно. dummyявляется глобальным для обоих контроллеров, а не совместно используемым более модульным способом через наследование с $ scope или controllerAs или с помощью службы.
Чад Джонсон,