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

Також не вдається.

ПРИМІТКА. Ці контролери не вкладаються один в одного.


Існує багато способів, але найкращий спосіб - це кутові годинники. Завжди, коли ми використовуємо фреймворк, це найкращий спосіб використовувати власні методи для роботи, не забувайте про це
pejman

Я вважаю цей блог дуже корисним Блог
Black Mamba

Відповіді:


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)
    • Прив’язка до функції, яка повертає примітив (рядок)
    • Прив’язка до властивості об’єкта
    • Двостороння прив’язка до властивості об'єкта

5
У цьому випадку - як би область "Ctrl2" знала ", коли sharedProperties.getProperty () змінює значення?
OpherV

5
Якщо ви хотіли, щоб ваш інтерфейс оновлювався щоразу, коли змінюється властивість, ви можете змінити bothйого як функцію, і воно буде викликано / повторно оцінено під час процесу кутового дайджесту. Дивіться цей приклад для прикладу. Крім того, якщо ви прив'язуєте до властивості об'єкта, ви можете використовувати його безпосередньо у вашому представленні, і він оновлюватиметься, коли дані будуть змінені, як у цьому прикладі .
Gloopy

11
Якщо ви хочете виявити та реагувати на зміни в контролері, можливим є додавання getProperty()функції до області та використання $ range. $ Watch, як у цьому прикладі . Сподіваюся, ці приклади допоможуть!
Gloopy

1
Тут є проблема, оскільки послуги повинні бути без громадянства. Зберігання майна всередині послуги неправильно (але зручно). Я почав використовувати $ cacheFactory для читання та запису даних. Я використовую майже ідентичний сервіс, як Gloopy, але замість того, щоб зберігати стан у сервісі, він зараз знаходиться в кеші. Спочатку створіть службу кешування: angular.module ('CacheService', ['ng']) .factory ('CacheService', функція ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Включіть у свій app.js, вставте його в сервіс, використовуйте його так: поверніть CacheService.get (ключ); або CacheService.put (ключ, значення);
Йордан Папалео

4
Намагаючись зрозуміти, як і чому використовується ця відповідь .serviceзамість .factoryописаного в кутових документах. Чому ця відповідь проголосується так високо, коли документація використовує інший метод?
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-книги: Повна книга про AngularJS", що n-моделі AngularJS, які створюються в контролерах як голі дані, є НЕПРАВНИМ!

Елемент діапазону $ повинен бути створений так:

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 (найкращий ПРАКТИКА) рекомендує DOM (html-документ) містити дзвінки як

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

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

Але у вашому випадку ви не хочете вкладених областей застосування, але є аналогічний аспект, щоб отримати об’єкти від служб до контролерів.

Скажімо, у вас є служба "Фабрика", а в просторі повернення є об'єктA, який містить objectB, який містить objectC.

Якщо з вашого контролера ви хочете отримати об'єктC у вашу область, помилка сказати:

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

Це не працює ... Натомість використовуйте лише одну крапку.

$scope.neededObjectInController = Factory.ObjectA;

Потім у DOM ви можете викликати objectC від objectA. Це найкраща практика, пов'язана з фабриками, і найголовніше, це допоможе уникнути несподіваних та непридатних помилок.


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
Дмитрі, ви маєте рацію, але кутові хлопці з моєї точки зору просто змінили концепцію, яку я мав між службами (фасадами) та фабриками .... о добре ....
Хуан Замора,

1
І виправте мене, якщо я помиляюся, сервіси мають на меті повернути щось, що може бути об'єктом чи цінністю. Заводи призначені для створення об'єктів. Фасад, який насправді являє собою сукупність функціональних можливостей, які щось повертають, - це те, що я думав, служби де. У тому числі виклик функціональних можливостей на заводах. Знову ж таки, я потрапляю в основне поняття про те, що це для мене, а не те, що є насправді з кутової точки зору. (Анотація Factory dofactory.com/net/ab абстракт-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; 

});

чим це відрізняється від використання послуги?
допатраман

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>


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

Сподіваюся, що ця практична та чиста відповідь комусь може бути корисною.


3

Ви можете це зробити на службах або на фабриках. Вони по суті однакові за деякими основними відмінностями. Я знайшов це пояснення на thinkter.io найпростішим для наслідування. Простий, до речі і ефективний.


1
"Ви могли це зробити на службах або на фабриках" - Як ..? Як це зробити, те, що просить ОП ... Будь ласка, опублікуйте повну відповідь у самому стаковому потоці, а не посилаючись на зовнішні ресурси, посилання можуть знижуватися понаднормово.
TJ

2

Чи не могли ви також зробити власність частиною батьківського діапазону?

$scope.$parent.property = somevalue;

Я не кажу, що це правильно, але це працює.


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 у свій контролер.


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


1
Працює лише в тому випадку, якщо Ctrl2(слухач) є дочірнім контролером Ctrl1. Контролери з братами мають спілкуватися через $rootScope.
herzbube

0

Якщо ви не хочете надавати послуги, то можете зробити так.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;

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 вигода від того, що вам не доведеться вводити об'єкт: вони доступні в будь-якому місці відразу після вашого визначення!


2
Це як би мати глобальні змінні по всьому windowоб’єкту ... Якщо ви збираєтесь забруднювати кутові, чому б просто не йти вперед і забруднювати об’єкт вікна ...
TJ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.