AngularJS: Comment passer des variables entre contrôleurs?

326

J'ai deux contrôleurs angulaires:

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
}

Je ne peux pas l'utiliser à l' Ctrl1intérieur Ctrl2car il n'est pas défini. Mais si j'essaye de le passer comme ça…

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

J'ai une erreur. Est-ce que quelqu'un sait comment faire ça?

Faire

Ctrl2.prototype = new Ctrl1();

Échoue également.

REMARQUE: ces contrôleurs ne sont pas imbriqués les uns dans les autres.

dopatraman
la source
Il existe de nombreuses façons, mais la meilleure est la montre angulaire. Toujours quand nous utilisons un framework est la meilleure façon d'utiliser ses propres méthodes pour faire du travail, n'oubliez pas cela
pejman
J'ai trouvé ce blog très utile Blog
Black Mamba

Réponses:

503

Une façon de partager des variables entre plusieurs contrôleurs consiste à créer un service et à l'injecter dans n'importe quel contrôleur où vous souhaitez l'utiliser.

Exemple de service simple:

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

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

Utilisation du service dans un contrôleur:

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

Ceci est très bien décrit dans ce blog (Leçon 2 et plus en particulier).

J'ai trouvé que si vous souhaitez vous lier à ces propriétés sur plusieurs contrôleurs, cela fonctionne mieux si vous vous liez à la propriété d'un objet au lieu d'un type primitif (booléen, chaîne, nombre) pour conserver la référence liée.

Exemple: var property = { Property1: 'First' };au lieu de var property = 'First';.


MISE À JOUR: Pour (espérons-le) clarifier les choses, voici un violon qui montre un exemple de:

  • Liaison à des copies statiques de la valeur partagée (dans myController1)
    • Liaison à une primitive (chaîne)
    • Liaison à la propriété d'un objet (enregistrée dans une variable de portée)
  • Liaison aux valeurs partagées qui mettent à jour l'interface utilisateur à mesure que les valeurs sont mises à jour (dans myController2)
    • Liaison à une fonction qui renvoie une primitive (chaîne)
    • Liaison à la propriété de l'objet
    • Liaison bidirectionnelle à la propriété d'un objet
Gloopy
la source
5
Dans ce cas - comment la portée de Ctrl2 "saurait" lorsque sharedProperties.getProperty () change de valeur?
OpherV
5
Si vous souhaitez que votre interface utilisateur soit mise à jour à chaque fois que la propriété change, vous pouvez changer bothpour être une fonction et elle sera appelée / réévaluée pendant le processus de résumé angulaire. Voir ce violon pour un exemple. De plus, si vous vous liez à la propriété d'un objet, vous pouvez l'utiliser directement dans votre vue et il se mettra à jour lorsque les données seront modifiées comme dans cet exemple .
Gloopy
11
Si vous souhaitez détecter et réagir aux modifications de votre contrôleur, une option consiste à ajouter la getProperty()fonction à la portée et à utiliser $ scope. $ Watch comme dans cet exemple . J'espère que ces exemples vous aideront!
Gloopy
1
Il y a un problème ici car les services devraient être apatrides. Stocker une propriété à l'intérieur d'un service est faux (mais pratique). J'ai commencé à utiliser $ cacheFactory pour lire et écrire des données. J'utilise un service presque identique à Gloopy mais au lieu de stocker l'état dans le service, il est maintenant dans le cache. Créez d'abord un service de cache: angular.module ('CacheService', ['ng']) .factory ('CacheService', function ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Incluez-le dans votre app.js, injectez-le dans le service, utilisez-le comme ceci: return CacheService.get (key); ou CacheService.put (clé, valeur);
Jordan Papaleo
4
Essayer de comprendre comment et pourquoi cette réponse utilise .serviceau lieu de .factorycomme décrit dans la documentation angulaire. Pourquoi cette réponse est-elle votée si haut alors que la documentation utilise une méthode différente?
pspahn
44

J'aime illustrer des choses simples par des exemples simples :)

Voici un Serviceexemple très simple :


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

Et ici le jsbin

Et voici un Factoryexemple très simple :


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

Et ici le jsbin


Si c'est trop simple, voici un exemple plus sophistiqué

Voir également la réponse ici pour des commentaires sur les meilleures pratiques

Dmitri Zaitsev
la source
1
Oui je suis d'accord avec toi. Essayez toujours de simplifier les choses.
Evan Hu
Quel est l'intérêt de déclarer var _dataObj = {};lorsque vous lui renvoyez une référence directe ..? Ce n'est pas privé . Dans le premier exemple, vous pouvez le faire this.dataObj = {};et dans le second, return { dataObj: {} };c'est une déclaration de variable inutile IMHO.
TJ
@TJ Il s'agit de partager cette variable avec d'autres composants. Il s'agit d'un exemple de base illustrant le concept de partage. La variable EST privée à l'intérieur du bloc, puis vous l'exposez comme variable publique en utilisant le modèle révélateur. De cette façon, il y a une séparation des responsabilités entre la détention de la variable et son utilisation.
Dmitri Zaitsev
@DmitriZaitsev vous dites des "exemples simples" mais à moins que vous montriez correctement comment utiliser l'état privé, vous ne faites que confondre les gens. Il n'y a pas d'état privé dans votre exemple tant que vous renvoyez une référence directe.
TJ
@TJ Je ne vois rien de déroutant. Une variable privée peut être exposée par un module. N'hésitez pas à écrire une meilleure réponse.
Dmitri Zaitsev
26

--- Je sais que cette réponse n'est pas pour cette question, mais je veux que les gens qui lisent cette question et veulent gérer des services tels que les usines pour éviter tout problème ----

Pour cela, vous devrez utiliser un service ou une usine.

Les services sont la MEILLEURE PRATIQUE pour partager des données entre des contrôleurs non imbriqués.

Une très très bonne annotation sur ce sujet concernant le partage de données est de savoir comment déclarer des objets. J'ai été malchanceux parce que je suis tombé dans un piège AngularJS avant de lire à ce sujet et j'étais très frustré. Alors laissez-moi vous aider à éviter ce problème.

J'ai lu dans le "ng-book: Le livre complet sur AngularJS" que les modèles AngularJS ng créés dans les contrôleurs en tant que données nues sont faux!

Un élément $ scope doit être créé comme ceci:

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

Et pas comme ça:

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

En effet, il est recommandé (MEILLEURE PRATIQUE) pour le DOM (document html) de contenir les appels comme

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

Ceci est très utile pour les contrôleurs imbriqués si vous voulez que votre contrôleur enfant puisse changer un objet du contrôleur parent ....

Mais dans votre cas, vous ne voulez pas d'étendues imbriquées, mais il existe un aspect similaire pour obtenir des objets des services vers les contrôleurs.

Disons que vous avez votre service 'Factory' et dans l'espace de retour il y a un objectA qui contient objectB qui contient objectC.

Si à partir de votre contrôleur vous voulez OBTENIR l'objectC dans votre portée, c'est une erreur de dire:

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

Cela ne fonctionnera pas ... Utilisez plutôt un seul point.

$scope.neededObjectInController = Factory.ObjectA;

Ensuite, dans le DOM, vous pouvez appeler objectC à partir de objectA. Il s'agit d'une meilleure pratique liée aux usines et, plus important encore, elle aidera à éviter les erreurs inattendues et non capturables.

AFP_555
la source
2
Je pense que c'est une bonne réponse, mais elle est assez difficile à digérer.
pspahn
17

Solution sans créer de service, en utilisant $ rootScope:

Pour partager des propriétés entre les contrôleurs d'application, vous pouvez utiliser Angular $ rootScope. Il s'agit d'une autre option pour partager des données, en les mettant à la disposition des utilisateurs.

Les services préférés pour partager certaines fonctionnalités entre les contrôleurs sont les services, pour lire ou modifier une propriété globale, vous pouvez utiliser $ rootscope.

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

Utilisation de $ rootScope dans un modèle (Accéder aux propriétés avec $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
la source
5
Vous utilisez des variables de portée globale à ce stade, ce qui s'écarte de l'idée d'AngularJS de définir localement tout dans ses différentes structures. L'ajout d'un fichier de variable globale permettrait d'obtenir la même chose et de faciliter la recherche de l'emplacement d'origine de la variable. De toute façon, pas suggéré.
Organiccat
4
@Organiccat - Je comprends votre préoccupation et c'est pourquoi j'ai déjà mentionné que la voie préférée sera les services, sans aucun doute là-dessus. Mais il y a aussi cette possibilité angulaire. C'est à vous de décider comment vous voulez gérer vos globaux. J'avais un scénario où cette approche fonctionnait le mieux pour moi.
Sanjeev
8

L'échantillon ci-dessus a fonctionné comme un charme. Je viens de faire une modification au cas où j'aurais besoin de gérer plusieurs valeurs. J'espère que ça aide!

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

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});
Juan Zamora
la source
1
J'ai également créé un exemple à l'aide d'un service pour partager des données entre différents contrôleurs. J'espère que vous avez aimé les gars. jsfiddle.net/juazammo/du53553a/1
Juan Zamora
1
Même si cela fonctionne, c'est généralement la syntaxe de .factory. Un .servicedoit être utilisé "si vous définissez votre service comme un type / classe" selon docs.angularjs.org/api/auto/service/$provide#service
Dmitri Zaitsev
1
Dmitri, vous avez raison, mais les gars angulaires de mon point de vue, ont juste changé un peu le concept que j'avais entre les services (façades) et les usines .... eh bien ....
Juan Zamora
1
Et corrigez-moi si je me trompe, les services sont destinés à renvoyer quelque chose qui peut être un objet ou une valeur. Les usines sont destinées à créer des objets. Une façade qui est en fait une collection de fonctionnalités qui retournent quelque chose, c'est ce que je pensais que les services où. Y compris invoquer les fonctionnalités des usines. Encore une fois, je vais entrer dans la notion de base de ce que c'est pour moi et non de ce qui est réellement du point de vue angulaire. (Abstract Factory dofactory.com/net/abstract-factory-design-pattern ) et une approche d'adaptateur est ce que je vais exposer en tant que service
Juan Zamora
1
Vérifiez le modèle d'adaptateur ici dofactory.com/net/adapter-design-pattern
Juan Zamora
6

J'ai tendance à utiliser des valeurs, heureux pour quiconque de discuter de pourquoi c'est une mauvaise idée ..

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

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

Injectez ensuite la valeur selon un service.

Définissez dans ctrl1:

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

et accès depuis ctrl2:

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

});
Chilledflame
la source
en quoi est-ce différent de l'utilisation d'un service?
dopatraman
5

L'exemple suivant montre comment passer des variables entre les contrôleurs de la fratrie et effectuer une action lorsque la valeur change.

Exemple de cas d'utilisation: vous avez un filtre dans une barre latérale qui modifie le contenu d'une autre vue.

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

Je voudrais contribuer à cette question en soulignant que la façon recommandée de partager des données entre les contrôleurs, et même des directives, consiste à utiliser des services (usines) comme cela a déjà été souligné, mais je voudrais également fournir un exemple pratique de la façon de procéder.

Voici le plongeur de travail: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Tout d'abord, créez votre service , qui aura vos données partagées :

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

Ensuite, injectez-le simplement sur vos contrôleurs et récupérez les données partagées sur votre portée:

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

Vous pouvez également le faire pour vos directives , cela fonctionne de la même manière:

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

J'espère que cette réponse pratique et claire pourra être utile à quelqu'un.

Fedaykin
la source
3

Vous pouvez le faire avec des services ou des usines. Ils sont essentiellement les mêmes à part pour certaines différences fondamentales. J'ai trouvé cette explication sur thinkster.io la plus simple à suivre. Simple, précis et efficace.

Noahdecoco
la source
1
"Vous pourriez le faire avec des services ou des usines" - Comment ..? Comment le faire est ce que l'OP demande ... veuillez poster une réponse complète dans stackoverflow lui-même plutôt que de créer un lien vers des ressources externes, les liens peuvent tomber au fil du temps.
TJ
2

Ne pourriez-vous pas également intégrer la propriété au parent des étendues?

$scope.$parent.property = somevalue;

Je ne dis pas que c'est vrai mais ça marche.

SideFX
la source
3
L'auteur a déclaré que NOTE: These controllers are not nested inside each other.. S'il s'agissait de contrôleurs imbriqués ou de contrôleurs partageant le même parent, cela fonctionnerait, mais nous ne pouvons pas nous y attendre.
Chris Foster
2
C'est généralement une mauvaise pratique de s'en remettre $parentsi cela peut être évité. Un composant réutilisable bien conçu ne devrait pas connaître ses parents.
Dmitri Zaitsev
2

Ah, ayez un peu de ces nouveaux trucs comme une autre alternative. C'est localstorage, et fonctionne là où angulaire fonctionne. De rien. (Mais vraiment, merci le gars)

https://github.com/gsklee/ngStorage

Définissez vos valeurs par défaut:

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

Accédez aux valeurs:

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

Stockez les valeurs

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

N'oubliez pas d'injecter ngStorage dans votre application et $ localStorage dans votre contrôleur.

kJamesy
la source
1
Cela résout un problème différent: le stockage persistant. Ce n'est pas une solution évolutive pour le problème en question car cela rend votre code percé avec des effets secondaires tels que la modification de l'objet de stockage local avec une vulnérabilité de conflit de noms entre autres.
Dmitri Zaitsev
1

Il y a deux façons de faire ça

1) Utilisez le service get / set

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

 $rootScope.$on('key', function (event, data) {}); // to get the value
Rohan Kawade
la source
1

Deuxième approche:

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>

Pour plus de détails, voir plunker:

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

Codiee
la source
1
Fonctionne uniquement si Ctrl2(l'écouteur) est un contrôleur enfant de Ctrl1. Les contrôleurs frères doivent communiquer via $rootScope.
herzbube
0

Si vous ne voulez pas faire de service, vous pouvez le faire.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
remerciement
la source
37
Je ne doute pas que cette réponse fonctionne, mais je tiens à noter que cela va à l'encontre de la philosophie d'AngularJS de ne jamais avoir d'objets DOM dans votre code modèle / contrôleur.
JoeCool
3
-1 parce que la communication du contrôleur via le DOM est une mauvaise pratique, à mon avis.
Chris Foster
3
@ChrisFoster, juste parce qu'un marteau est vendu comme un "outil", cela ne signifie pas qu'il ne peut pas être utilisé comme poids de papier. Je suis sûr que pour chaque framework ou outil, vous trouverez toujours des développeurs qui ont besoin de "plier" la liste des "meilleures pratiques".
Andrei V
5
@AndreiV - Mauvaise analogie, il n'y a aucun inconvénient à utiliser un marteau comme grammage du papier. Une mauvaise pratique comme celle-ci présente des inconvénients évidents et peut facilement conduire à du code spaghetti. Le code ci-dessus est fragile car il dépend maintenant de l'emplacement de votre contrôleur dans le DOM et est très difficile à tester. L'utilisation d'un service est une meilleure pratique pour une raison, car elle ne lie pas votre implémentation à votre modèle. Je conviens que les développeurs doivent souvent modifier la liste des meilleures pratiques, mais pas lorsqu'il existe une meilleure pratique claire, commune et plus modulaire qui fonctionne mieux.
Chris Foster
-1

Outre $ rootScope et les services, il existe une solution alternative propre et simple pour étendre angulaire pour ajouter les données partagées:

dans les contrôleurs:

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

Ces propriétés appartiennent à un objet «angulaire», séparé des étendues, et peuvent être partagées dans des étendues et des services.

1 avantage de ne pas avoir à injecter l'objet: ils sont accessibles n'importe où immédiatement après votre définition!

williamjxj
la source
2
C'est comme avoir des variables globales sur tout l' windowobjet ... Si vous allez polluer angulaire, pourquoi ne pas simplement aller de l'avant et polluer l'objet fenêtre ...
TJ