Partager des données entre les contrôleurs AngularJS

362

J'essaie de partager des données entre les contrôleurs. Le cas d'utilisation est un formulaire en plusieurs étapes, les données entrées dans une entrée sont ensuite utilisées dans plusieurs emplacements d'affichage en dehors du contrôleur d'origine. Code ci-dessous et dans jsfiddle ici .

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

Toute aide est grandement appréciée.

johnkeese
la source

Réponses:

481

Une solution simple est que votre usine retourne un objet et que vos contrôleurs fonctionnent avec une référence au même objet:

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>

Démo: http://jsfiddle.net/HEdJF/

Lorsque les applications deviennent plus grandes, plus complexes et plus difficiles à tester, vous ne souhaiterez peut-être pas exposer l'objet entier de l'usine de cette façon, mais accordez plutôt un accès limité, par exemple via des getters et des setters:

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

    var data = {
        FirstName: ''
    };

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

Avec cette approche, il appartient aux contrôleurs consommateurs de mettre à jour l'usine avec de nouvelles valeurs et de surveiller les changements pour les obtenir:

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>

Démo: http://jsfiddle.net/27mk1n1o/

tasseKATT
la source
2
La première solution a fonctionné le mieux pour moi - avait le service maintenir l'objet et les contrôleurs gérer simplement avec la référence. Merci beaucoup.
johnkeese
5
la méthode $ scope. $ watch fonctionne à merveille pour effectuer un appel de repos à partir d'une étendue et appliquer les résultats à une autre étendue
aclave1
Au lieu de renvoyer un objet comme return { FirstName: '' };ou de renvoyer un objet contenant des méthodes qui interagissent avec un objet, ne serait-il pas préférable de renvoyer une instance d'une classe ( function) de sorte que lorsque vous utilisez votre objet, il ait un type spécifique et ce n'est pas seulement un objet{}" ?
RPDeshaies
8
newValue !== oldValueAttention , la fonction d'écoute n'a pas besoin d'être vérifiée car Angular n'exécute pas la fonction si oldValue et newValue sont identiques. La seule exception à cela est si vous vous souciez de la valeur initiale. Voir: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.
@tasseKATT, ça marche bien, si je ne rafraîchis pas la page. Pouvez-vous me proposer une solution si je rafraîchis la page ???
2015
71

Je préfère ne pas l'utiliser $watchpour cela. Au lieu d'affecter l'intégralité du service à l'étendue d'un contrôleur, vous pouvez affecter uniquement les données.

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>

Vous pouvez également mettre à jour les données de service avec une méthode directe.

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
la source
Vous n'utilisez pas explicitement watch () mais en interne AngularJS le fait pour mettre à jour la vue avec de nouvelles valeurs dans les variables $ scope. En termes de performances c'est la même chose ou pas?
Mart Dominguez
4
Je ne suis pas sûr des performances de l'une ou l'autre méthode. D'après ce que je comprends, Angular exécute déjà une boucle de résumé sur la portée, donc la configuration d'expressions de surveillance supplémentaires ajoute probablement plus de travail pour Angular. Il faudrait exécuter un test de performance pour vraiment le découvrir. Je préfère simplement le transfert et le partage de données via la réponse ci-dessus à la mise à jour via les expressions $ watch.
bennick
2
Ce n'est pas une alternative à $ watch. C'est une manière différente de partager des données entre deux objets angulaires, dans ce cas des contrôleurs, sans avoir à utiliser $ watch.
bennick
1
IMO cette solution est la meilleure car elle est plus dans l'esprit de l'approche déclarative d'Angular, par opposition à l'ajout de $ watch instructions, qui semble un peu plus jQuery-esque.
JamesG
8
Pourquoi n'est-ce pas la réponse la plus votée, je me demande. Après tout, $ watch rend le code plus complexe
Shyamal Parikh
11

Il existe de nombreuses façons de partager les données entre les contrôleurs

  1. utiliser des services
  2. en utilisant les services $ state.go
  3. en utilisant des Stateparams
  4. en utilisant rootscope

Explication de chaque méthode:

  1. Je ne vais pas expliquer comme c'est déjà expliqué par quelqu'un

  2. en utilisant $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
    
  3. $stateparamfonctionne de la même manière que $state.go, vous le transmettez en tant qu'objet du contrôleur émetteur et le récupérez dans le contrôleur récepteur à l'aide de stateparam

  4. en utilisant $rootscope

    (a) envoyer des données de l'enfant au contrôleur parent

      $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) envoyer des données du parent au contrôleur enfant

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

J'ai créé une fabrique qui contrôle la portée partagée entre le modèle du chemin de l'itinéraire, afin que vous puissiez conserver les données partagées juste lorsque les utilisateurs naviguent dans le même chemin parent de l'itinéraire.

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

Ainsi, si l'utilisateur accède à un autre chemin de l'itinéraire, par exemple «/ Support», les données partagées pour le chemin «/ Client» seront automatiquement détruites. Mais, si au lieu de cela, l'utilisateur va sur les chemins "enfants", comme "/ Client / 1" ou "/ Client / liste", la portée ne sera pas détruite.

Vous pouvez voir un exemple ici: http://plnkr.co/edit/OL8of9

Oberdan Nunes
la source
À utiliser $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })si vous avez ui.router
Nuno Silva
6

Il existe plusieurs façons de partager des données entre les contrôleurs

  • Services angulaires
  • $ broadcast, $ emit, méthode
  • Communication du contrôleur parent à enfant
  • $ rootscope

Comme nous le savons, ce $rootscopen'est pas un moyen préférable pour le transfert de données ou la communication car c'est une portée mondiale qui est disponible pour toute l'application

Pour le partage de données entre les contrôleurs Angular Js, les services angulaires sont les meilleures pratiques, par exemple. .factory, .service
Pour référence

En cas de transfert de données du parent au contrôleur de l' enfant , vous pouvez données parent directement accès au contrôleur de l' enfant par le biais $scope
Si vous utilisez , ui-routervous pouvez ensuite utiliser $stateParmaspour passer des paramètres d'URL comme id, name, key, etc.

$broadcastest également un bon moyen de transférer des données entre les contrôleurs du parent vers l'enfant et $emitde transférer les données de l'enfant vers les contrôleurs parent

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

Référez-vous au lien donné pour en savoir plus sur$broadcast

ojus kulkarni
la source
4

Solution la plus simple:

J'ai utilisé un service AngularJS .

Étape 1: J'ai créé un service AngularJS nommé SharedDataService.

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

    };
    return Person;
});

Étape 2: créez deux contrôleurs et utilisez le service créé ci-dessus.

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

Étape 3: utilisez simplement les contrôleurs créés dans la vue.

<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>

Pour voir la solution de travail à ce problème, veuillez cliquer sur le lien ci-dessous

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

Fichier .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
la source
1
c'est un exemple très clair pour nous les nouveaux utilisateurs de angularJs. Comment partager / transmettre les données d'une relation parent / enfant? FirstCtrlest parent et obtient une promesse dont SecondCtrl(enfant) aura également besoin? Je ne veux pas passer deux appels API. Merci.
Chris22
1

Il y a une autre façon sans utiliser $ watch, en utilisant 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
la source
1

Il existe plusieurs façons de procéder.

  1. Événements - déjà bien expliqué.

  2. routeur ui - expliqué ci-dessus.

  3. Service - avec la méthode de mise à jour affichée ci-dessus
  4. MAUVAIS - Surveiller les changements.
  5. Une autre approche parent-enfant plutôt que d' émettre et de diffuser -

*

<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
la source
0

Je ne sais pas où j'ai choisi ce modèle, mais pour le partage de données entre les contrôleurs et la réduction de $ rootScope et $ scope, cela fonctionne très bien. Cela rappelle une réplication de données où vous avez des éditeurs et des abonnés. J'espère que cela aide.

Le service:

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

Le contrôleur qui obtient les nouvelles données, c'est-à-dire l'éditeur ferait quelque chose comme ça.

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

Le contrôleur qui utilise également ces nouvelles données, appelé l'abonné, ferait quelque chose comme ça ...

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

Cela fonctionnera pour n'importe quel scénario. Ainsi, lorsque le contrôleur principal est initialisé et obtient des données, il appelle la méthode changeData qui la diffuse ensuite à tous les abonnés de ces données. Cela réduit le couplage de nos contrôleurs entre eux.

ewahner
la source
l'utilisation d'un «modèle» qui partage l'état entre les contrôleurs semble être l'option la plus utilisée. Malheureusement, cela ne couvre pas le scénario où vous actualiseriez le contrôleur enfant. Comment récupérer les données du serveur et recréer le modèle? Et comment géreriez-vous l'invalidation du cache?
Andrei
Je pense que l'idée des contrôleurs parent et enfant est un peu dangereuse et provoque trop de dépendances les unes des autres. Cependant, lorsqu'il est utilisé en combinaison avec UI-Router, vous pouvez facilement actualiser les données sur votre "enfant", surtout si ces données sont injectées via la configuration d'état. L'invalidation du cache se produit sur l'éditeur par lequel il serait responsable de récupérer les données et d'appelersharedDataEventHub.changeData(newData)
ewahner
Je pense qu'il y a un malentendu. Par rafraîchissement, je veux dire que les utilisateurs appuient littéralement sur la touche F5 du clavier et obligent le navigateur à faire un post. Si vous êtes dans la vue enfant / détails lorsque cela se produit, il n'y a personne pour appeler "var someData = yourDataService.getSomeData ()". La question est, comment feriez-vous face à ce scénario? qui devrait mettre à jour le modèle?
Andrei
Selon la façon dont vous activez vos contrôleurs, cela peut être très simple. Si vous avez une méthode qui active alors le contrôleur qui est responsable du chargement initial des données, le chargerait et utiliserait sharedDataEventHub.changeData(newData)alors le tout enfant ou contrôleur qui dépend de ces données aurait quelque part dans leur corps de contrôleur ce qui suit: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Si vous avez utilisé l'interface utilisateur -Router cela pourrait être injecté depuis la configuration de l'état.
ewahner
Voici un exemple qui illustre le problème que j'essaie de décrire: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Si vous sortez le plunk, accédez à la page d'édition et actualisez le navigateur, il se bloquera. Qui devrait recréer le modèle dans ce cas?
Andrei
-3

Faites-le simplement (testé avec la 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
la source
5
Je pense que cela a été rejeté parce qu'il n'est pas modulaire. dummyest global pour les deux contrôleurs plutôt que partagé de manière plus modulaire via l'héritage avec $ scope ou controllerAs ou en utilisant un service.
Chad Johnson