AngularJS: Как я могу передавать переменные между контроллерами?

326

У меня есть два угловых контроллера:

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
}

Я не могу использовать Ctrl1внутри, Ctrl2потому что он не определен. Однако, если я попытаюсь передать это так ...

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

Я получаю ошибку. Кто-нибудь знает как это сделать?

дела

Ctrl2.prototype = new Ctrl1();

Также не удается.

ПРИМЕЧАНИЕ. Эти контроллеры не вложены друг в друга.

dopatraman
источник
Есть много способов, но лучший способ - это угловые часы. Всегда, когда мы используем фреймворк, это лучший способ использовать ее собственные методы для работы, не забывайте об этом
pejman
Я нашел этот блог очень полезным Блог
Черная мамба

Ответы:

503

Один из способов обмена переменными между несколькими контроллерами - это создать сервис и внедрить его в любой контроллер, где вы хотите его использовать.

Простой пример обслуживания:

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

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

Использование сервиса в контроллере:

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

Это очень хорошо описано в этом блоге (урок 2 и в частности).

Я обнаружил, что если вы хотите связать эти свойства с несколькими контроллерами, это будет работать лучше, если вы связываетесь со свойством объекта вместо примитивного типа (логическое, строка, число), чтобы сохранить привязанную ссылку.

Пример: var property = { Property1: 'First' };вместо var property = 'First';.


ОБНОВЛЕНИЕ: Чтобы (надеюсь) сделать вещи более ясными, вот скрипка, которая показывает пример:

  • Привязка к статическим копиям общего значения (в myController1)
    • Привязка к примитиву (строка)
    • Привязка к свойству объекта (сохраняется в переменной области)
  • Связывание с общими значениями, которые обновляют пользовательский интерфейс при обновлении значений (в myController2)
    • Привязка к функции, которая возвращает примитив (строку)
    • Привязка к свойству объекта
    • Двухстороннее связывание со свойством объекта
Gloopy
источник
5
В этом случае - как область действия Ctrl2 «узнает», когда sharedProperties.getProperty () меняет значение?
OpherV
5
Если вы хотите, чтобы ваш пользовательский интерфейс обновлялся каждый раз при изменении свойства, вы можете изменить bothего на функцию, и она будет вызываться / переоцениваться в процессе углового дайджеста. Смотрите эту скрипку для примера. Также, если вы связываетесь со свойством объекта, вы можете использовать его непосредственно в своем представлении, и оно будет обновляться по мере изменения данных, как в этом примере .
Глупый
11
Если вы хотите обнаружить и отреагировать на изменения в вашем контроллере, вы можете добавить getProperty()функцию в область и использовать $ scope. $ Watch, как в этом примере . Надеюсь, эти примеры помогут!
Глупый
1
Здесь есть проблема, поскольку услуги должны быть без гражданства. Хранение собственности внутри службы некорректно (но удобно). Я начал использовать $ cacheFactory для чтения и записи данных. Я использую почти такой же сервис, как Gloopy, но вместо сохранения состояния в сервисе, он теперь находится в кеше. Сначала создайте службу кэширования: angular.module ('CacheService', ['ng']) .factory ('CacheService', function ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Включите в ваш app.js, вставьте его в сервис, используйте его так: return CacheService.get (key); или CacheService.put (ключ, значение);
Джордан Папалео
4
Попытка понять, как и почему этот ответ используется .serviceвместо того, .factoryкак описано в Angular Docs. Почему этот ответ так высоко оценивается, когда в документации используется другой метод?
pspahn
44

Мне нравится иллюстрировать простые вещи простыми примерами :)

Вот очень простой Serviceпример:


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

А вот jsbin

И вот очень простой Factoryпример:


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

А вот jsbin


Если это слишком просто, вот более сложный пример

Также см. Ответ здесь для соответствующих комментариев лучших практик

Дмитрий Зайцев
источник
1
Да, я согласен с тобой. Всегда старайтесь сделать вещи простыми.
Эван Ху
Какой смысл декларировать, var _dataObj = {};когда вы возвращаете прямую ссылку на него? Это не личное . В первом примере это можно сделать, this.dataObj = {};а во втором return { dataObj: {} };- бесполезное объявление переменных IMHO.
TJ
@TJ Дело в том, чтобы поделиться этой переменной среди других компонентов. Это основной пример, иллюстрирующий концепцию совместного использования. Переменная IS приватная внутри блока, затем вы выставляете ее как публичную переменную, используя шаблон выявления. Таким образом, существует разделение обязанностей между хранением переменной и ее использованием.
Дмитрий Зайцев
@DmitriZaitsev вы говорите «простые примеры», но если вы не показываете, как правильно использовать частное государство, вы просто путаете людей. В вашем примере нет частного состояния, пока вы возвращаете прямую ссылку.
TJ
@TJ Я не вижу ничего запутанного. Закрытая переменная может быть представлена ​​модулем. Не стесняйтесь написать лучший ответ.
Дмитрий Зайцев
26

--- Я знаю, что этот ответ не для этого вопроса, но я хочу, чтобы люди, которые читают этот вопрос и хотят обращаться с такими Сервисами, как Фабрики, чтобы избежать проблем с этим ----

Для этого вам нужно будет использовать Сервис или Фабрику.

Эти сервисы - ЛУЧШАЯ ПРАКТИКА для обмена данными между не вложенными контроллерами.

Очень хорошая аннотация на эту тему об обмене данными о том, как объявлять объекты. Мне не повезло, потому что я попал в ловушку AngularJS, прежде чем я прочитал об этом, и я был очень расстроен. Итак, позвольте мне помочь вам избежать этой проблемы.

Из "ng-book: полная книга по AngularJS" я прочитал, что ng-модели AngularJS, созданные в контроллерах как голые данные, НЕПРАВИЛЬНЫ!

Элемент $ scope должен быть создан следующим образом:

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

И не так:

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

Это потому, что рекомендуется (ЛУЧШАЯ ПРАКТИКА) для DOM (html-документ) содержать вызовы как

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

Это очень полезно для вложенных контроллеров, если вы хотите, чтобы ваш дочерний контроллер мог изменять объект из родительского контроллера ....

Но в вашем случае вам не нужны вложенные области видимости, но есть аналогичный аспект для передачи объектов из сервисов в контроллеры.

Допустим, у вас есть сервис «Фабрика», а в возвращаемом пространстве есть объект A, который содержит объект B, который содержит объект C.

Если из вашего контроллера вы хотите получить objectC в вашу область, было бы ошибкой сказать:

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

Это не сработает ... Вместо этого используйте только одну точку.

$scope.neededObjectInController = Factory.ObjectA;

Затем в DOM вы можете вызвать objectC из objectA. Это лучшая практика, связанная с фабриками, и самое главное, она поможет избежать непредвиденных и неуловимых ошибок.

AFP_555
источник
2
Я думаю, что это хороший ответ, но его довольно сложно переварить.
pspahn
17

Решение без создания Сервиса с использованием $ rootScope:

Для совместного использования свойств через контроллеры приложения вы можете использовать Angular $ rootScope. Это еще один вариант обмена данными, чтобы люди знали об этом.

Предпочтительный способ обмена некоторыми функциями между контроллерами - это службы, для чтения или изменения глобального свойства вы можете использовать $ rootcope.

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

Использование $ rootScope в шаблоне (доступ к свойствам с помощью $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Санджив
источник
5
В этот момент вы используете глобальные переменные, которые отличаются от идеи AngularJS о локальной области видимости всего внутри ее различных структур. Добавление файла глобальной переменной позволит добиться того же результата и упростит поиск места, где переменная была первоначально определена. В любом случае, не предлагается.
Organiccat
4
@ Organiccat - я понимаю вашу озабоченность, и поэтому я уже упоминал, что предпочтительным способом будут услуги, без сомнения в этом. Но у меня есть и угловой вариант. Это зависит от вас, как вы хотите управлять своими глобальными. У меня был сценарий, где этот подход работал лучше всего для меня.
Санджив
8

Пример выше работал как шарм. Я только что сделал изменение на тот случай, если мне нужно управлять несколькими значениями. Надеюсь, это поможет!

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

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});
Хуан Самора
источник
1
Я также создал образец, используя сервис для обмена данными между различными контроллерами. Надеюсь, вам это нравится. jsfiddle.net/juazammo/du53553a/1
Хуан Самора
1
Хотя это работает, обычно это синтаксис для .factory. .serviceСледует использовать « если вы определяете услугу как тип / класс» , как на docs.angularjs.org/api/auto/service/$provide#service
Дмитрий Зайцев
1
Дмитрий, вы правы, однако ребята из Angular, с моей точки зрения, просто немного изменили концепцию, которая была у меня между сервисами (фасадами) и фабриками ... да ладно ...
Хуан Замора
1
И поправьте меня, если я ошибаюсь, сервисы предназначены для возврата чего-то, что может быть объектом или значением. Фабрики предназначены для создания объектов. Фасад, который на самом деле представляет собой набор функциональных возможностей, которые возвращают что-то, - это то, что я думал, где услуги. В том числе вызывая функциональные возможности от фабрик. Опять же, я вхожу в базовое понятие, что это для меня, а не то, что на самом деле с точки зрения англ. (Абстрактная фабрика dofactory.com/net/abstract-factory-design-pattern ) и подход с использованием адаптера - это то, что я покажу как услугу
Хуан Замора,
1
Проверьте здесь шаблон адаптера. Dofactory.com/net/adapter-design-pattern
Хуан Самора,
6

Я склонен использовать ценности, рад, что кто-нибудь обсудит, почему это плохая идея.

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

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

Затем введите значение согласно услуге.

Установить в ctrl1:

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

и доступ из ctrl2:

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

});
Chilledflame
источник
чем это отличается от использования сервиса?
Допатрам
5

В следующем примере показано , как передать переменные между братьями и сестрами контроллерами и принимать меры , когда изменяется значение.

Пример использования: у вас есть фильтр на боковой панели, который изменяет содержимое другого представления.

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
источник
4

Я хотел бы внести свой вклад в этот вопрос, указав, что рекомендуемый способ обмена данными между контроллерами и даже директивами - это использование сервисов (фабрик), как это уже было указано, но я также хотел бы предоставить Рабочий практический пример того, как это должно быть сделано.

Вот рабочий плункер: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Сначала создайте свой сервис , который будет иметь ваши общие данные :

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

Затем просто вставьте его в свои контроллеры и получите общие данные в своей области:

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

Вы также можете сделать это для ваших директив , это работает так же:

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

Надеюсь, что этот практичный и чистый ответ может быть полезным для кого-то.

Fedaykin
источник
3

Вы можете сделать это с помощью услуг или заводов. Они по сути одинаковы для некоторых основных различий. Я нашел это объяснение на сайте thinkster.io наиболее простым для подражания. Просто, точно и эффективно.

Noahdecoco
источник
1
«Вы можете сделать это с помощью служб или заводов» - как ..? Как это сделать - это то, о чем просит ОП ... пожалуйста, отправьте полный ответ в самом стеке потока, а не на ссылки на внешние ресурсы, ссылки могут перестать работать со временем.
TJ
2

Не могли бы вы также сделать свойство частью родительского объекта?

$scope.$parent.property = somevalue;

Я не говорю, что это правильно, но это работает.

SideFX
источник
3
Автор заявил, что NOTE: These controllers are not nested inside each other.. Если бы это были вложенные контроллеры или контроллеры, которые совместно использовали одного и того же родителя, это работало бы, но мы не можем этого ожидать.
Крис Фостер
2
Обычно это плохая практика, на $parentкоторую можно положиться, если этого можно избежать. Хорошо продуманный компонент многократного использования не должен знать о своих родителях.
Дмитрий Зайцев
2

Ах, есть немного этого нового материала в качестве другой альтернативы. Это местное хранилище, и работает там, где работает угловой. Пожалуйста. (Но на самом деле, спасибо парню)

https://github.com/gsklee/ngStorage

Определите ваши значения по умолчанию:

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

Доступ к значениям:

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

Сохраните значения

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

Не забудьте добавить ngStorage в свое приложение и $ localStorage в свой контроллер.

kJamesy
источник
1
Это решает другую проблему - постоянное хранение. Это не масштабируемое решение для рассматриваемой проблемы, поскольку оно приводит к утечке кода из-за побочных эффектов, таких как изменение объекта локального хранилища с уязвимостью конфликта имен среди других.
Дмитрий Зайцев
1

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

1) Воспользуйтесь сервисом get / set

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

 $rootScope.$on('key', function (event, data) {}); // to get the value
Рохан Каваде
источник
1

Второй подход:

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>

Для более подробной информации см. Plunker:

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

Codiee
источник
1
Работает только если Ctrl2(слушатель) является дочерним контроллером Ctrl1. Контроллеры братьев и сестер должны общаться через $rootScope.
herzbube
0

Если вы не хотите оказывать услуги, вы можете сделать это так.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
thanksnote
источник
37
Я не сомневаюсь, что этот ответ работает, но я хочу отметить, что это противоречит философии AngularJS, согласно которой в коде модели / контроллера никогда не должно быть DOM-объектов.
JoeCool
3
-1 потому что связь контроллера через DOM - плохая практика, на мой взгляд.
Крис Фостер
3
@ChrisFoster: просто потому, что молоток продается как «инструмент», это не значит, что его нельзя использовать как вес бумаги. Я уверен, что для каждого фреймворка или инструмента всегда найдутся разработчики, которым нужно «согнуть» список «лучших практик».
Андрей V
5
@AndreiV - плохая аналогия, нет недостатка в использовании молотка в качестве веса бумаги. Такое плохое поведение имеет явные недостатки и может легко привести к спагетти-коду. Приведенный выше код хрупок, поскольку теперь он зависит от того, где находится ваш контроллер в DOM, и его очень сложно протестировать. Использование службы является лучшей практикой по определенной причине, поскольку она не привязывает вашу реализацию к вашему шаблону. Я согласен, что разработчикам часто приходится сгибать список лучших практик, но не тогда, когда существует четкая, распространенная, более модульная практика, которая работает лучше.
Крис Фостер
-1

Помимо $ rootScope и сервисов, существует чистое и простое альтернативное решение для расширения угла добавления общих данных:

в контроллерах:

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

Эти свойства принадлежат «угловому» объекту, отделены от областей и могут использоваться совместно в областях и службах.

1 преимущество в том, что вам не нужно вводить объект: они доступны где угодно сразу после вашего определения!

williamjxj
источник
2
Это похоже на наличие глобальных переменных по всему windowобъекту ... Если вы собираетесь загрязнять angular, почему бы просто не пойти дальше и не загрязнить объект окна ...
TJ