AngularJS: динамически назначает контроллер из ng-repeat
Я пытаюсь динамически назначать контроллер для включенного шаблона следующим образом:
<section ng-repeat="panel in panels">
<div ng-include="'path/to/file.html'" ng-controller="{{panel}}"></div>
</section>
Но Angular жалуется, что {{panel}}
- undefined.
Я предполагаю, что {{panel}}
еще не определен (потому что я могу откликнуть {{panel}}
внутри шаблона).
Я видел множество примеров того, как люди устанавливают ng-controller
равным такой переменной: ng-controller="template.ctrlr"
. Но, не создавая повторяющийся контур совпадения, я не могу понять, как получить значение {{panel}}
, доступное, когда ng-controller
нуждается в нем.
P.S. Я также попытался установить ng-controller="{{panel}}"
в моем шаблоне (думая, что он должен был разрешить к тому моменту), но не кубик.
Ответы
Ответ 1
Ваша проблема в том, что ng-controller должен указывать на сам контроллер, а не только на строку с именем контроллера.
Итак, вы можете определить $scope.sidepanels как массив с указателями на контроллеры, что-то вроде этого, возможно:
$scope.sidepanels = [Alerts, Subscriptions];
Вот рабочий пример js fiddle http://jsfiddle.net/ADukg/1559/
Тем не менее, я нахожу очень странным всю эту ситуацию, когда вы можете настроить контроллеры в ngRepeat.
Ответ 2
Чтобы динамически установить контроллер в шаблон, он помогает ссылаться на функцию конструктора, связанную с контроллером. Функция-конструктор для контроллера - это функция, которую вы передаете методу controller()
Angular API модуля.
Это помогает, потому что если строка, переданная в директиву ngController
, не является именем зарегистрированного контроллера, тогда ngController
обрабатывает строку как выражение для оценки в текущем объеме. Это выражение области видимости должно оцениваться конструктором контроллера.
Например, скажем, Angular встречается в шаблоне следующее:
ng-controller="myController"
Если контроллер с именем myController
не зарегистрирован, то Angular будет выглядеть $scope.myController
в текущем контроллере. Если этот ключ существует в области и соответствующее значение является конструктором контроллера, тогда будет использоваться контроллер.
Это упоминается в документации ngController
в описании значения параметра: "Имя глобальной конструкторской функции или выражение, которое в текущей области оценивает функцию-конструктор". Комментарии кодов в исходном коде Angular более подробно описывают здесь, в src/ng/controller.js
.
По умолчанию Angular не позволяет получить доступ к конструктору, связанному с контроллером. Это связано с тем, что когда вы регистрируете контроллер с помощью метода controller()
Angular API модуля, он скрывает конструктор, который вы передаете ему в частной переменной. Вы можете увидеть это здесь, в $ControllerProvider исходный код. (Переменная controllers
в этом коде является переменной, закрытой для $ControllerProvider
.)
Мое решение этой проблемы - создать общий вспомогательный сервис под названием registerController
для регистрации контроллеров. Эта услуга предоставляет доступ к контроллеру и контроллеру при регистрации контроллера. Это позволяет использовать контроллер как обычным способом, так и динамически.
Вот код, который я написал для службы registerController
, которая делает это:
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 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);
};
});
}]);
Ниже приведен пример использования службы для регистрации контроллера:
appServices.run(['registerController',
function (registerController) {
registerController('testCtrl', ['$scope',
function testCtrl($scope) {
$scope.foo = 'bar';
}]);
}]);
Приведенный выше код регистрирует контроллер под именем testCtrl
, а также предоставляет конструктор контроллера как службу под названием testCtrlFactory
.
Теперь вы можете использовать контроллер в шаблоне либо обычным способом -
ng-controller="testCtrl"
или динамически -
ng-controller="templateController"
Для того, чтобы последние работали, вы должны иметь следующее в своей текущей области:
$scope.templateController = testCtrlFactory
Ответ 3
Я считаю, что у вас есть эта проблема, потому что вы определяете свои контроллеры как это (как и я привык):
app.controller('ControllerX', function() {
// your controller implementation
});
Если в этом случае вы не можете просто использовать ссылки на ControllerX
, потому что реализация контроллера (или "класс", если вы хотите его назвать) не входит в глобальную область (вместо этого она хранится в приложении $controllerProvider
).
Я бы предложил вам использовать шаблоны вместо динамического назначения ссылок на контроллер (или даже вручную создать их).
Контроллеры
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;
});
Шаблоны (mocks)
<!-- 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>
Просмотр
<div ng-controller="Ctrl">
<div ng-repeat="panel in panels">
<div ng-include src="panel.template"></div>
</div>
</div>
jsFiddle: http://jsfiddle.net/Xn4H8/
Ответ 4
Другой способ - не использовать ng-repeat, а директиву для их компиляции.
HTML
<mysections></mysections>
Директива
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
Хорошо. Я думаю, что самым простым решением здесь является определение контроллера явно на шаблоне вашего файла. Пусть say u имеет массив:
$scope.widgets = [
{templateUrl: 'templates/widgets/aWidget.html'},
{templateUrl: 'templates/widgets/bWidget.html'},
];
Затем в вашем html файле:
<div ng-repeat="widget in widgets">
<div ng-include="widget.templateUrl"></div>
</div>
И решение aWidget.html:
<div ng-controller="aWidgetCtrl">
aWidget
</div>
bWidget.html:
<div ng-controller="bWidgetCtrl">
bWidget
</div>
Просто так! Вы просто определяете имя контроллера в своем шаблоне. Поскольку вы определяете контроллеры, как сказал bmleite:
app.controller('ControllerX', function() {
// your controller implementation
});
тогда это лучшее обходное решение, которое я мог бы придумать. Единственная проблема здесь в том, что, если у вас есть 50 контроллеров, вам придется явно определять их на каждом шаблоне, но я думаю, вам пришлось делать это в любом случае, так как у вас есть ng-repeat с контроллером, установленным вручную.