AngularJS: affecte dynamiquement le contrôleur de ng-repeat

J'essaie d'attribuer dynamiquement un contrôleur pour un modèle inclus de la manière suivante:

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

Mais Angular se plaint que {{panel}} est indéfini.

Je suppose que {{panel}} n'est pas encore défini (car je peux écho sur {{panel}} dans le modèle).

J'ai vu beaucoup d'exemples de personnes définissant ng-controller égal à une variable comme: ng-controller="template.ctrlr" . Mais, sans créer une boucle concurrente en double, je ne peux pas comprendre comment avoir la valeur de {{panel}} disponible lorsque ng-controller besoin.

PS J'ai également essayé de définir ng-controller="{{panel}}" dans mon modèle (en pensant qu'il doit être résolu d'ici là), mais pas de dés.

Votre problème est que le ng-controller doit pointer vers le contrôleur lui-même, et non seulement le cordage avec le nom du contrôleur.

Vous voudrez peut-être définir $ scope.sidepanels comme tableau avec des pointeurs vers les 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 lorsque vous voudrez configurer les contrôleurs dans ngRepeat.

Pour configurer dynamiquement un contrôleur dans un modèle, il permet d'avoir une référence à la fonction constructeur associée à un contrôleur. La fonction de constructeur pour un contrôleur est la fonction que vous passez à la méthode du controller() de l' API du module Angular.

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

Par exemple, disons que Angular rencontre ce qui suit dans un modèle:

 ng-controller="myController" 

Si aucun contrôleur avec le nom myController n'est enregistré, Angular examinera $scope.myController dans le contrôleur contenant actuellement. Si cette clé existe dans la portée et que la valeur correspondante est un constructeur de contrôleur, le contrôleur sera utilisé.

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

Par défaut, Angular ne permet pas d'accéder facilement au constructeur associé à un contrôleur. C'est parce que lorsque vous enregistrez un contrôleur à l'aide de la méthode du controller() de l' API du module Angular, il cache le constructeur que vous le transmettez dans une variable privée. Vous pouvez le voir ici dans le code source $ ControllerProvider . (La variable de controllers dans ce code est une variable privée à $ControllerProvider .)

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

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

 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 d'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 constructeur du contrôleur en tant que service appelé testCtrlFactory .

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

 ng-controller="testCtrl" 

Ou de façon dynamique

 ng-controller="templateController" 

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

 $scope.templateController = testCtrlFactory 

Je crois que vous avez ce problème parce que vous définissez vos contrôleurs comme celui-ci (comme je l'ai l'habitude de faire):

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

Si tel est le cas, vous ne pouvez pas simplement utiliser des références à ControllerX car la mise en œuvre du contrôleur (ou 'Classe', si vous voulez l'appeler) n'est pas sur la portée globale (à la place, elle est stockée sur la application $controllerProvider ).

Je vous suggère d'utiliser des modèles au lieu d'attribuer dynamiquement des références de contrôleur (ou même de les 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; }); 

Modèles (maquettes)

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

Vue

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

JsFiddle: http://jsfiddle.net/Xn4H8/

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

HTML

 <mysections></mysections> 

Directif

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

Ok, je pense que la solution la plus simple ici est de définir explicitement le contrôleur sur le modèle de votre fichier. Disons que vous avez un tableau:

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

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

Aussi simple que cela! Vous définissez simplement le nom du contrôleur dans votre modèle. Puisque vous définissez les contrôleurs comme bmleite dit:

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

Alors c'est la meilleure solution que je pourrais proposer. Le seul problème ici est que si vous disposez de 50 contrôleurs, vous devrez les définir explicitement sur chaque modèle, mais je suppose que vous deviez le faire de toute façon, car vous avez une ng-repeat avec le contrôleur réglé à la main.