AngularJS: affecter dynamiquement le contrôleur à partir de ng-repeat

j'essaye d'affecter dynamiquement un controller pour le template inclus comme ceci:

<section ng-repeat="panel in panels">
    <div ng-include="'path/to/file.html'" ng-controller="{{panel}}"></div>
</section>

mais Angular se plaint que {{panel}} n'est pas défini.

je suppose que {{panel}} n'est pas défini encore (parce que je peux echo {{panel}} dans le modèle).

j'ai vu beaucoup d'exemples de personnes paramètre ng-controller égale à une variable comme ceci: ng-controller="template.ctrlr". Mais, sans créer une boucle concurrente dupliquée, Je ne peux pas comprendre comment avoir la valeur de {{panel}} disponible lorsque ng-controller en a besoin.

P. S. j'ai aussi essayé la configuration de ng-controller="{{panel}}" dans mon modèle (pensant qu'il devait être résolu d'ici là), mais pas de dés.

22
demandé sur jacob 2012-12-19 05:21:03

5 réponses

votre problème est que ng-controller devrait pointer vers controller lui-même, pas seulement une chaîne avec le nom du controller.

donc vous pourriez vouloir définir $scope.panneaux latéraux sous forme de tableau avec des pointeurs vers des contrôleurs, quelque chose comme ça, peut-être:

$scope.sidepanels = [Alerts, Subscriptions];

Voici l'exemple de travail sur js fiddle http://jsfiddle.net/ADukg/1559/

cependant, je trouve très étrange toute cette situation quand vous pourriez vouloir mettre en place des contrôleurs dans ngRepeat.

16
répondu SET 2012-12-19 04:18:38

pour définir dynamiquement un controller dans un template, il est utile d'avoir une référence à constructeur fonction associée à un contrôleur. La fonction constructeur pour un contrôleur de la fonction que vous passer dans la controller() méthode Angulaire module API.

cela aide parce que si la chaîne passe à ngController la directive n'est pas le nom d'un contrôleur enregistré, puis ngController traite la chaîne comme expression à évaluer sur la portée actuelle. Cette expression scope doit être évaluée à un controller constructeur.

par exemple, dites rencontres angulaires dans un modèle:

ng-controller="myController"

Si aucun contrôleur avec le nom myController est enregistrée, puis Angulaire vais regarder $scope.myController dans le contrôleur contenant le courant. Si cette clé existe dans le champ d'application et la valeur correspondante est un contrôleur constructeur, puis le contrôleur sera utilisé.

Ceci est mentionné dans le ngController documentation dans sa description de la valeur du paramètre: "Nom d'une fonction de constructeur globalement accessible ou d'une expression qui sur la portée actuelle évalue à une fonction de constructeur."Les commentaires de Code dans l'angle de code source sort cela plus en détail ici src/ng/controller.js.

par défaut, Angular ne rend pas facile l'accès au constructeur associé à un contrôleur. C'est parce que lorsque vous enregistrez un controller en utilisant le controller() méthode Angulaire module API, il cache le constructeur que vous passez dans une variable privée. Vous pouvez le voir ici dans le $ControllerProvider source code. (Le controllers variable dans ce code est une variable privée à $ControllerProvider.)

Ma solution à ce problème est de créer un générique service d'assistance appelé registerController pour enregistrer les contrôleurs. Ce service expose le contrôleur et le contrôleur constructeur lors de l'enregistrement d'un contrôleur. Cela permet d'utiliser le contrôleur à la fois de façon normale et dynamique.

Voici le code que j'ai écrit pour un registerController service:

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

// Define a registerController service that creates a new controller
// in the usual way.  In addition, the service registers the
// controller's constructor as a service.  This allows the controller
// to be set dynamically within a template.
appServices.config(['$controllerProvider', '$injector', '$provide',
  function ($controllerProvider, $injector, $provide) {
    $provide.factory('registerController',
      function registerControllerFactory() {
        // Params:
        //   constructor: controller constructor function, optionally
        //     in the annotated array form.
        return function registerController(name, constructor) {
            // Register the controller constructor as a service.
            $provide.factory(name + 'Factory', function () {
                return constructor;
            });
            // Register the controller itself.
            $controllerProvider.register(name, constructor);
        };
    });
}]);

Voici un exemple de l'utilisation du service pour enregistrer un contrôleur:

appServices.run(['registerController',
  function (registerController) {

    registerController('testCtrl', ['$scope',
      function testCtrl($scope) {
        $scope.foo = 'bar';
    }]);

}]);

le code ci-dessus enregistre le contrôleur sous le nom testCtrl, et il expose également le contrôleur de constructeur comme un service appelé testCtrlFactory.

Maintenant, vous pouvez utiliser le contrôleur dans un modèle de la manière habituelle--

ng-controller="testCtrl"

ou dynamiquement--

ng-controller="templateController"

pour que ce dernier fonctionne, vous devez avoir ce qui suit dans votre champ d'application actuel:

$scope.templateController = testCtrlFactory
6
répondu cjerdonek 2014-07-12 22:33:48

je crois que tu vas avoir ce problème parce que vous êtes à la définition de vos contrôleurs comme ça (tout comme j'ai l'habitude de faire):

app.controller('ControllerX', function() {
    // your controller implementation        
});

Si c'est le cas, vous ne pouvez pas simplement utiliser des références à ControllerX parce que l'implémentation du controller (ou 'Class', si vous voulez l'appeler comme ça) n'est pas sur la portée globale (au lieu de cela, elle est stockée sur l'application $controllerProvider).

je vous suggérerais d'utiliser des gabarits au lieu d'assigner dynamiquement des références de controller (ou même créer manuellement).

Contrôleurs

var app = angular.module('app', []);    
app.controller('Ctrl', function($scope, $controller) {
    $scope.panels = [{template: 'panel1.html'}, {template: 'panel2.html'}];        
});

app.controller("Panel1Ctrl", function($scope) {
    $scope.id = 1;
});
app.controller("Panel2Ctrl", function($scope) {
    $scope.id = 2;
});

Templates (se moque)

<!-- panel1.html -->
<script type="text/ng-template" id="panel1.html">
  <div ng-controller="Panel1Ctrl">
    Content of panel {{id}}
  </div>
</script>

<!-- panel2.html -->
<script type="text/ng-template" id="panel2.html">
  <div ng-controller="Panel2Ctrl">
    Content of panel {{id}}
  </div>
</script>

View

<div ng-controller="Ctrl">
    <div ng-repeat="panel in panels">
        <div ng-include src="panel.template"></div>        
    </div>
</div>

jsFiddle:http://jsfiddle.net/Xn4H8/

5
répondu bmleite 2012-12-19 15:12:03

une autre façon est de ne pas utiliser ng-repeat, mais une directive pour les compiler en existence.

HTML

<mysections></mysections>

la Directive

angular.module('app.directives', [])
    .directive('mysections', ['$compile', function(compile){
        return {
            restrict: 'E',
            link: function(scope, element, attrs) {
                for(var i=0; i<panels.length; i++) {
                    var template = '<section><div ng-include="path/to/file.html" ng-controller="'+panels[i]+'"></div></section>';
                    var cTemplate = compile(template)(scope);

                    element.append(cTemplate);
                }
            }
        }
    }]);
5
répondu Brenton 2014-04-22 10:24:15

Ok je pense que la solution la plus simple ici est de définir explicitement le controller sur le template de votre fichier. Disons que u ont un tableau:

$scope.widgets = [
      {templateUrl: 'templates/widgets/aWidget.html'},
      {templateUrl: 'templates/widgets/bWidget.html'},
];

Puis sur votre fichier html:

<div ng-repeat="widget in widgets">
    <div ng-include="widget.templateUrl"></div>
</div>

Et la solution aWidget.html:

<div ng-controller="aWidgetCtrl">
   aWidget
</div>

bWidget.html:

<div ng-controller="bWidgetCtrl">
   bWidget
</div>

Simple que cela! Vous venez de définir le nom du contrôleur dans votre modèle. Puisque vous définissez les controllers comme dit bmleite:

app.controller('ControllerX', function() {
// your controller implementation        
});

c'est la meilleure solution de contournement que je pouvais venir. Le seul problème ici est que si u a comme 50 controllers, u devra les définir explicitement sur chaque template, mais je suppose que u a dû le faire de toute façon puisque vous avez un ng-repeat avec controller réglé à la main.

-1
répondu Alex Arvanitidis 2014-11-05 11:21:02