web-dev-qa-db-fra.com

Pouvez-vous passer des paramètres à un contrôleur AngularJS lors de la création?

J'ai un contrôleur chargé de communiquer avec une API pour mettre à jour les propriétés d'un utilisateur, nom, email, etc. Chaque utilisateur a un 'id' qui est transmis par le serveur lorsque la page de profil est affichée. 

J'aimerais transmettre cette valeur au contrôleur AngularJS afin qu'il sache quel est le point d'entrée de l'API pour l'utilisateur actuel. J'ai essayé de passer la valeur dans ng-controller. Par exemple:

function UserCtrl(id, $scope, $filter) {

$scope.connection = $resource('api.com/user/' + id)

et dans le HTML

<body ng-controller="UserCtrl({% id %})">

{% id %} affiche l'identifiant envoyé par le serveur. mais je reçois des erreurs. 

Quelle est la bonne façon de passer une valeur dans un contrôleur lors de sa création?

276
nickponline

Remarques:

Cette réponse est ancienne. Ceci est juste une preuve de concept sur la façon dont le résultat souhaité peut être atteint. Cependant, il se peut que ce ne soit pas la meilleure solution selon certains commentaires ci-dessous. Je n'ai aucune documentation pour soutenir ou rejeter l'approche suivante. Veuillez vous référer à certains des commentaires ci-dessous pour une discussion plus approfondie sur ce sujet.

Réponse originale:

J'ai répondu à ceci à Oui, vous pouvez absolument le faire en utilisant ng-init et une simple fonction init.

En voici un exemple sur plunker

HTML 

<!DOCTYPE html>
<html ng-app="angularjs-starter">
  <head lang="en">
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
    <script src="app.js"></script>
  </head>  
  <body ng-controller="MainCtrl" ng-init="init('James Bond','007')">
    <h1>I am  {{name}} {{id}}</h1>
  </body>
</html>

JavaScript

var app = angular.module('angularjs-starter', []);

app.controller('MainCtrl', function($scope) {

  $scope.init = function(name, id)
  {
    //This function is sort of private constructor for controller
    $scope.id = id;
    $scope.name = name; 
    //Based on passed argument you can make a call to resource
    //and initialize more objects
    //$resource.getMeBond(007)
  };


});
357
Jigar Patel

Je suis très en retard et je ne sais pas si c'est une bonne idée, mais vous pouvez inclure le $attrs injectable dans la fonction du contrôleur, ce qui permet d'initialiser le contrôleur à l'aide des "arguments" fournis sur un élément, p. Ex.

app.controller('modelController', function($scope, $attrs) {
    if (!$attrs.model) throw new Error("No model for modelController");

    // Initialize $scope using the value of the model attribute, e.g.,
    $scope.url = "http://example.com/fetch?model="+$attrs.model;
})

<div ng-controller="modelController" model="foobar">
  <a href="{{url}}">Click here</a>
</div>

Encore une fois, je ne sais pas si c'est une bonne idée, mais cela semble fonctionner et constitue une autre alternative.

141
Michael Tiller

Cela fonctionne aussi.

Javascript:

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

app.controller('MainCtrl', function($scope, name, id) {
    $scope.id = id;
    $scope.name = name;
    // and more init
});

Html:

<!DOCTYPE html>
<html ng-app="angularApp">
  <head lang="en">
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
    <script src="app.js"></script>
    <script>
       app.value("name", "James").value("id", "007");
    </script>
  </head>
  <body ng-controller="MainCtrl">
    <h1>I am  {{name}} {{id}}</h1>
  </body>
</html>
39
Jonghee Park

Comme @akonsu et Nigel Findlater le suggèrent, vous pouvez lire l’URL où URL est index.html#/user/:id avec $routeParams.id et l’utiliser dans le contrôleur. 

votre application: 

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

app.config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/:type/:id', {templateUrl: 'myView.html', controller: 'myCtrl'});
}]);

le service de ressources

app.factory('MyElements', ['$resource', function($resource) {
     return $resource('url/to/json/:type/:id', { type:'@type', id:'@id' });
}]);

le controlle 

app.controller('MyCtrl', ['$scope', '$routeParams', 'MyElements', function($scope, $routeParams, MyElements) {
    MyElements.get({'type': $routeParams.type, "id": $routeParams.id }, function(Elm) {
        $scope.Elm = Elm;
    })
}]);

alors, Elm est accessible dans la vue en fonction de id

15
François Romain

La vue ne doit pas dicter config

Dans Angular, le modèle ne doit jamais dicter la configuration, ce qui est fondamentalement ce que les gens souhaitent lorsqu'ils souhaitent transmettre des arguments aux contrôleurs à partir d'un fichier de modèle. Cela devient une pente glissante. Si les paramètres de configuration sont codés en dur dans les modèles (par exemple, à l'aide d'une directive ou d'un attribut d'argument de contrôleur), vous ne pouvez plus réutiliser ce modèle à des fins autres que son utilisation unique. Bientôt, vous voudrez réutiliser ce modèle, mais avec une configuration différente et maintenant, pour ce faire, vous devrez soit pré-traiter les modèles afin d’injecter des variables avant qu’elles ne soient transmises à angular, ou bien utiliser des directives massives pour générer des données géantes des blocs HTML afin de réutiliser tout le code HTML du contrôleur, à l'exception du div wrapper et de ses arguments. Pour les petits projets, ce n'est pas grave. Pour quelque chose de grand (où angular excelle), ça devient vite moche.

L'alternative: Modules

Ce type de configuration est ce que les modules ont été conçus pour gérer. Dans de nombreux tutoriels angulaires, les utilisateurs disposent d'un seul module pour l'ensemble de leur application, mais le système est réellement conçu et prend en charge de nombreux petits modules, chacun enveloppant de petites parties de l'application totale. Idéalement, les contrôleurs, modules, etc. seraient déclarés dans des fichiers séparés et assemblés dans des fragments spécifiques réutilisables. Lorsque votre application est conçue de cette manière, vous récupérez beaucoup de choses en plus des arguments simples du contrôleur.

L'exemple ci-dessous contient 2 modules, réutilisant le même contrôleur, mais chacun avec leurs propres paramètres de configuration. Les paramètres de configuration sont transmis via l'injection de dépendance à l'aide de module.value. Cela correspond à la manière angulaire car nous avons les éléments suivants: injection de dépendance du constructeur, code de contrôleur réutilisable, modèles de contrôleur réutilisables (le contrôleur div peut facilement être inclus avec ng-include), système facilement testable par unité sans HTML, et enfin réutilisable modules comme véhicule pour coudre les pièces ensemble.

Voici un exemple:

<!-- index.html -->
<div id="module1">
    <div ng-controller="MyCtrl">
        <div>{{foo}}</div>
    </div>
</div>
<div id="module2">
    <div ng-controller="MyCtrl">
        <div>{{foo}}</div>
    </div>
</div>
<script>
    // part of this template, or a JS file designed to be used with this template
    angular.element(document).ready(function() {
        angular.bootstrap(document.getElementById("module1"), ["module1"]);
        angular.bootstrap(document.getElementById("module2"), ["module2"]);
    });
</script>

<!-- scripts which will likely in be in their seperate files -->
<script>
    // MyCtrl.js
    var MyCtrl = function($scope, foo) {
    $scope.foo = foo;
    }

    MyCtrl.$inject = ["$scope", "foo"];

    // Module1.js
    var module1 = angular.module('module1', []);
    module1.value("foo", "fooValue1");
    module1.controller("MyCtrl", MyCtrl);

    // Module2.js file
    var module2 = angular.module('module2', []);
    module2.value("foo", "fooValue2");
    module2.controller("MyCtrl", MyCtrl);
</script>

Voyez-le en action: jsFiddle .

14
Nucleon

Il semble que la meilleure solution pour vous est en réalité une directive. Cela vous permet de conserver votre contrôleur, mais de définir des propriétés personnalisées pour celui-ci.

Utilisez ceci si vous avez besoin d'accéder aux variables de la portée d'encapsulation:

angular.module('myModule').directive('user', function ($filter) {
  return {
    link: function (scope, element, attrs) {
      $scope.connection = $resource('api.com/user/' + attrs.userId);
    }
  };
});

<user user-id="{% id %}"></user>

Utilisez ceci si vous n'avez pas besoin d'accéder aux variables dans la portée d'encapsulation:

angular.module('myModule').directive('user', function ($filter) {
  return {
    scope: {
      userId: '@'
    },
    link: function (scope, element, attrs) {
      $scope.connection = $resource('api.com/user/' + scope.userId);
    }
  };
});

<user user-id="{% id %}"></user>
8
btesser

Si ng-init ne permet pas de passer des objets dans $scope, vous pouvez toujours écrire votre propre directive. Alors voici ce que j'ai eu:

http://jsfiddle.net/goliney/89bLj/

Javasript:

var app = angular.module('myApp', []);
app.directive('initData', function($parse) {
    return function(scope, element, attrs) {
        //modify scope
        var model = $parse(attrs.initData);
        model(scope);
    };
});

function Ctrl1($scope) {
    //should be defined
    $scope.inputdata = {foo:"east", bar:"west"};
}

Html:

<div ng-controller="Ctrl1">
    <div init-data="inputdata.foo=123; inputdata.bar=321"></div>
</div>

Mais mon approche ne peut que modifier des objets, qui sont déjà définis par le contrôleur.

8
Sergey Goliney

J'ai trouvé utile de passer des variables de $ routeProvider.

Par exemple, vous utilisez un contrôleur MyController pour plusieurs écrans, en passant une variable très importante "mySuperConstant" à l'intérieur.

Utilisez cette structure simple:

Router:

$routeProvider
            .when('/this-page', {
                templateUrl: 'common.html',
                controller: MyController,
                mySuperConstant: "123"
            })
            .when('/that-page', {
                templateUrl: 'common.html',
                controller: MyController,
                mySuperConstant: "456"
            })
            .when('/another-page', {
                templateUrl: 'common.html',
                controller: MyController,
                mySuperConstant: "789"
            })

MyController:

    MyController: function ($scope, $route) {
        var mySuperConstant: $route.current.mySuperConstant;
        alert(mySuperConstant);

    }
7
Dmitri Algazin

Vous pouvez le faire lors de la configuration des itinéraires, par exemple.

 .when('/newitem/:itemType', {
            templateUrl: 'scripts/components/items/newEditItem.html',
            controller: 'NewEditItemController as vm',
            resolve: {
              isEditMode: function () {
                return true;
              }
            },
        })

Et plus tard l'utiliser comme 

(function () {
  'use strict';

  angular
    .module('myApp')
    .controller('NewEditItemController', NewEditItemController);

  NewEditItemController.$inject = ['$http','isEditMode',$routeParams,];

  function NewEditItemController($http, isEditMode, $routeParams) {
    /* jshint validthis:true */

    var vm = this;
    vm.isEditMode = isEditMode;
    vm.itemType = $routeParams.itemType;
  }
})();

Donc, ici, lorsque nous avons configuré la route que nous avons envoyée: itemType et que nous la récupérons plus tard à partir de $ routeParams.

6
ssaini

Il existe un autre moyen de transmettre des paramètres à un contrôleur en injectant $ routeParams dans votre contrôleur, puis en utilisant les paramètres url décrits ici Quel est le moyen le plus concis de lire les paramètres de requête dans AngularJS?

4
Nigel Findlater

Cette question est ancienne mais j'ai longtemps lutté pour essayer de trouver une réponse à ce problème qui répondrait à mes besoins et je ne la trouvais pas facilement. Je crois que ma solution suivante est bien meilleure que celle actuellement acceptée, peut-être parce qu'angular a ajouté des fonctionnalités depuis la première question posée. 

Réponse courte, l’utilisation de la méthode Module.value vous permet de transmettre des données à un constructeur de contrôleur.

Voir mon plunker ici

Je crée un objet de modèle, puis je l'associe au contrôleur du module, en le référençant avec le nom 'modèle'

HTML/JS

  <html>
  <head>
    <script>
      var model = {"id": 1, "name":"foo"};

      $(document).ready(function(){
        var module = angular.module('myApp', []);
        module.value('model', model);
        module.controller('MyController', ['model', MyController]);
        angular.bootstrap(document, ['myApp']);
      });

      function confirmModelEdited() {
        alert("model name: " + model.name + "\nmodel id: " + model.id);
      }
    </script>

  </head>
  <body >
      <div ng-controller="MyController as controller">
        id: {{controller.model.id}} <br>
        name: <input ng-model="controller.model.name"/>{{controller.model.name}}
        <br><button ng-click="controller.incrementId()">increment ID</button>
        <br><button onclick="confirmModelEdited()">confirm model was edited</button>
    </div>
  </body>

</html>

Le constructeur de mon contrôleur accepte alors un paramètre avec le même identifiant 'model' auquel il peut ensuite accéder. 

Manette

function MyController (model) {
  this.model = model;
}

MyController.prototype.incrementId = function() {
  this.model.id = this.model.id + 1;
}

Remarques: 

J'utilise l'initialisation manuelle de bootstrapping , ce qui me permet d'initialiser mon modèle avant de l'envoyer à angular. Cela fonctionne beaucoup mieux avec le code existant, car vous pouvez attendre pour configurer vos données pertinentes et ne compiler que le sous-ensemble angulaire de votre application à la demande lorsque vous le souhaitez.

Dans le plunker, j'ai ajouté un bouton pour alerter les valeurs de l'objet modèle qui avait été initialement défini en javascript et passé à angular, juste pour confirmer que angular référençait réellement l'objet modèle, plutôt que de le copier et de le copier. 

Sur cette ligne:

module.controller('MyController', ['model', MyController]);

Je passe l'objet MyController dans la fonction Module.controller, plutôt que de le déclarer en tant que fonction inline. Je pense que cela nous permet de définir beaucoup plus clairement notre objet contrôleur, mais la documentation angulaire tend à le faire en ligne, donc je pensais qu'il valait la peine d'être clarifié.

J'utilise la syntaxe "controller as" et assigne des valeurs à la propriété "this" de MyController, plutôt que d'utiliser la variable "$ scope". Je pense que cela fonctionnerait bien avec $ scope, la tâche du contrôleur ressemblerait à quelque chose comme ça:

module.controller('MyController', ['$scope', 'model', MyController]);

et le constructeur du contrôleur aurait une signature comme celle-ci:

function MyController ($scope, model) {

Si, pour une raison quelconque, vous souhaitez, vous pouvez également associer ce modèle à la valeur d'un second module, que vous associez ensuite à une dépendance de votre module principal.

Je crois que sa solution est bien meilleure que celle actuellement acceptée car

  1. Le modèle transmis au contrôleur est en réalité un objet javascript, et non une chaîne évaluée. Il s’agit d’une véritable référence à l’objet et les modifications qui en résultent affectent d’autres références à cet objet de modèle.
  2. Angular dit que l'utilisation de ng-init par la réponse acceptée est un abus, ce que cette solution ne fait pas.

La façon dont Angular semble fonctionner dans la plupart des autres exemples que j'ai vus a le contrôleur définissant les données du modèle, ce qui n'a jamais eu de sens pour moi, il n'y a pas de séparation entre le modèle et le contrôleur, cela ne semble pas vraiment MVC à moi. Cette solution vous permet d’avoir un objet de modèle complètement séparé que vous transmettez au contrôleur. Il convient également de noter que si vous utilisez la directive ng-include, vous pouvez placer tous vos fichiers html angulaires dans un fichier séparé, en séparant complètement la vue Modèle et le contrôleur en éléments modulaires distincts.

3
O. Winter

Si vous utilisez angular-ui-router, voici la solution correcte: https://github.com/angular-ui/ui-router/wiki#resolve

En gros, vous déclarez qu'un ensemble de dépendances doit être "résolu" avant l'instanciation du contrôleur. Vous pouvez déclarer des dépendances pour chacun de vos "états". Ces dépendances sont ensuite passées dans le "constructeur" du contrôleur.

2
Juan

Je n’aimais pas vraiment les solutions proposées dans mon cas d’utilisation particulier; j’ai donc pensé publier ce que j’ai fait parce que je ne l’ai pas vue ici.

Je voulais simplement utiliser un contrôleur plus comme une directive, dans une boucle ng-repeat:

<div ng-repeat="objParameter in [{id:'a'},{id:'b'},{id:'c'}]">
  <div ng-controller="DirectiveLikeController as ctrl"></div>
</div>

Maintenant, pour accéder à la objParameter lors de la création dans chaque DirectiveLikeController (ou pour obtenir le paramètre objParameter à jour à tout moment), il me suffit d’injecter $ scope et d’appeler $scope.$eval('objParameter'):

var app = angular.module('myapp', []);
app.controller('DirectiveLikeController',['$scope'], function($scope) {
   //print 'a' for the 1st instance, 'b' for the 2nd instance, and 'c' for the 3rd.
   console.log($scope.$eval('objParameter').id); 
});

Le seul inconvénient que je constate est que le contrôleur parent doit savoir que le paramètre s'appelle objParameter.

1
JohnTD

Une façon de le faire serait d'avoir un service séparé qui puisse être utilisé comme un «navire» pour les arguments où ils sont membres de données publiques.

1
Marcin Wyszynski

Ceci est une extension de /Michael Tiller's excellente réponse . Sa réponse fonctionne pour initialiser des variables à partir de la vue en injectant l'objet $attrs dans le contrôleur. Le problème se produit si le même contrôleur est appelé à partir de $routeProvider lorsque vous naviguez par routage. Ensuite, vous obtenez l'erreur d'injecteur Unknown provider : $attrsProvider car $attrs n'est disponible pour l'injection que lorsque la vue est compilée. La solution consiste à transmettre la variable (foo) à $routeParams lors de l'initialisation du contrôleur depuis route et à $attrs lors de l'initialisation du contrôleur à partir de la vue. Voici ma solution.

De la route

$routeProvider.
        when('/mypage/:foo', {
            templateUrl: 'templates/mypage.html',
            controller: 'MyPageController',
            caseInsensitiveMatch: true,
            resolve: {
                $attrs: function () {
                    return {};
                }
            }
        });

Cela gère une URL telle que '/mypage/bar'. Comme vous pouvez le voir, foo est passé par le paramètre url et nous fournissons au $injector un objet vide pour $attrs afin d'éviter les erreurs d'injecteur.

Depuis la vue

<div ng-controller="MyPageController" data-foo="bar">

</div>

Maintenant le contrôleur

var app = angular.module('myapp', []);
app.controller('MyPageController',['$scope', '$attrs', '$routeParams'], function($scope, $attrs, $routeParams) {
   //now you can initialize foo. If $attrs contains foo, it's been initialized from view
   //else find it from $routeParams
   var foo = $attrs.foo? $attrs.foo : $routeParams.foo;
   console.log(foo); //prints 'bar'

});
0
Moses Machua

Voici une solution (basée sur la suggestion de Marcin Wyszynski) qui vous permet de transmettre une valeur à votre contrôleur mais que vous ne déclarez pas explicitement le contrôleur dans votre code HTML (ce que ng-init semble exiger) - si, par exemple, vous rendez vos modèles avec ng-view et déclarez chaque contrôleur pour la route correspondante via routeProvider.

JS

messageboard.directive('currentuser', ['CurrentUser', function(CurrentUser) {
  return function(scope, element, attrs) {
    CurrentUser.name = attrs.name;
  };
}]);

html

<div ng-app="app">
  <div class="view-container">
    <div ng-view currentuser name="testusername" class="view-frame animate-view"></div>
  </div>
</div>

Dans cette solution, CurrentUser est un service qui peut être injecté dans n’importe quel contrôleur, avec la propriété .name alors disponible. 

Deux notes:

  • un problème que j'ai rencontré est que .name est défini après le chargement du contrôleur. Par conséquent, j'ai un court délai d'attente avant de rendre le nom d'utilisateur sur l'étendue du contrôleur. Existe-t-il un moyen judicieux d’attendre que le nom .name soit défini sur le service?

  • cela semble être un moyen très facile d'obtenir un utilisateur actuel dans votre application Angular avec toute l'authentification conservée en dehors de Angular. Vous pouvez avoir un before_filter pour empêcher les utilisateurs non connectés d'accéder au code HTML où votre application Angular est démarrée, et dans ce code HTML, vous pouvez simplement interpoler le nom de l'utilisateur connecté et même son ID si vous souhaitez interagir avec les détails de l'utilisateur via les demandes http de votre application angulaire. Vous pouvez autoriser les utilisateurs non connectés à utiliser l'application Angular avec un "utilisateur invité" par défaut. Tout conseil sur les raisons pour lesquelles cette approche serait mauvaise serait le bienvenu - il est trop facile d’être sensé!)

0
Ollie H-M

Non ce n'est pas possible. Je pense que vous pouvez utiliser ng-init comme hack http://docs.angularjs.org/api/ng.directive:ngInit .

0
SunnyShah