AngularJS:从 ng-repeat 动态分配控制器

Posted

技术标签:

【中文标题】AngularJS:从 ng-repeat 动态分配控制器【英文标题】:AngularJS: dynamically assign controller from ng-repeat 【发布时间】:2012-12-06 07:44:24 【问题描述】:

我正在尝试为包含的模板动态分配控制器,如下所示:

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

但 Angular 抱怨 panel 未定义。

我猜panel 尚未定义(因为我可以在模板中回显panel)。

我见过很多人将ng-controller 设置为如下变量的示例:ng-controller="template.ctrlr"。但是,如果不创建重复的并发循环,我无法弄清楚如何在 ng-controller 需要时让 panel 的值可用。

附:我还尝试在我的模板中设置ng-controller="panel"(认为到那时它一定已经解决了),但没有骰子。

【问题讨论】:

一个plunker会很有帮助。 对不起,什么是“plunker”?试着用谷歌搜索它,看到了关于棒球的东西…… 我的错,应该链接:plnkr.co。关于这个问题,我很好奇您在panels 中的内容是这些字符串还是函数? plunker 可以帮助我们更快地找到导致问题的原因。 哦,看起来像 jsfiddle。当我在上午工作时我会检查一下并尝试设置一个(看起来真的很复杂?)。 panel 的值是匹配控制器名称的字符串:$scope.sidepanels = ["Alerts","Subscriptions"]; Main() controller. ***.com/questions/21204371/… 【参考方案1】:

您的问题是 ng-controller 应该指向控制器本身,而不仅仅是带有控制器名称的字符串。

因此您可能希望将 $scope.sidepanels 定义为带有指向控制器的指针的数组,可能是这样的:

$scope.sidepanels = [Alerts, Subscriptions];

这是关于 js fiddle http://jsfiddle.net/ADukg/1559/ 的工作示例

但是,当您可能想在 ngRepeat 中设置控制器时,我发现所有这些情况都很奇怪。

【讨论】:

澄清一下,我在 ngRepeat 中动态分配控制器的原因是因为每个面板都做了不同的事情:Panel1 是警报(警报逻辑在警报控制器中); Panel2 是订阅(订阅逻辑在订阅控制器中。每个面板处理的信息非常不同。再次感谢! 此解决方案要求定义选项卡配置并可能呈现链接以激活它们的(控制器)范围具有对每个控制器的引用。如果它们是全局函数,这很容易,但我认为这不是最佳实践。 AFAIK,不可能将控制器注入其他控制器或 TabConfigService,所以我不知道如何完成。 如果控制器是使用规范的 "angular.module('app.controllers', []).controller('Alerts', ...)" 样式定义的,你能让这种方法工作吗? @enigment 我在下面写了一个不需要将控制器放在全局范围内的解决方案。在我的文章中,控制器可以通过服务访问另一个控制器(即使用 Angular 的依赖注入设置)。 @cjerdonek:可以使用自定义指令:***.com/questions/21204371/…【参考方案2】:

要在模板中动态设置控制器,有对与控制器关联的构造函数 函数的引用会很有帮助。控制器的构造函数是传递给 Angular 的 module API 的 controller() 方法的函数。

这样做会有所帮助,因为如果传递给ngController 指令的字符串不是注册控制器的名称,那么ngController 会将字符串视为要在当前范围内评估的表达式。此范围表达式需要评估为控制器构造函数

例如,假设 Angular 在模板中遇到以下情况:

ng-controller="myController"

如果没有注册名为 myController 的控制器,那么 Angular 将在当前包含的控制器中查看 $scope.myController。如果作用域中存在这个key,并且对应的值是一个控制器构造函数,那么就会使用这个控制器。

ngController 文档在其对参数值的描述中提到了这一点:“全局可访问构造函数的名称或在当前范围内计算为构造函数的表达式的名称。” Angular 源代码中的代码 cmets 更详细地说明了这一点 here in src/ng/controller.js

默认情况下,Angular 无法轻松访问与控制器关联的构造函数。这是因为当您使用 Angular 的 module API 的 controller() 方法注册控制器时,它会将您传递给私有变量的构造函数隐藏起来。您可以在$ControllerProvider source code 中看到这一点。 (这段代码中的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'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);
        ;
    );
]);

以下是使用服务注册控制器的示例:

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

【讨论】:

看起来不错,但我如何获得testCtrlFactory?你能把它弄得一团糟吗?谢谢。 要在组件中使用testCtrlFactory服务,您只需在组件的requires数组中包含'testCtrlFactory',就像使用任何服务一样。【参考方案3】:

我相信你遇到了这个问题,因为你是这样定义你的控制器的(就像我习惯做的那样):

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

如果是这种情况,您不能简单地使用对 ControllerX 的引用,因为控制器实现(或“类”,如果您想这样称呼它)不在全局范围内(而是存储在应用程序 @ 987654325@)。

我建议您使用模板而不是动态分配控制器引用(甚至手动创建它们)。

控制器

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

模板(模拟)

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

【讨论】:

嗨,bmleite。感谢您的答复!实际上我这样声明控制器:function Main( $scope , $filter ) $scope.sidepanels = [ Alerts , Subscriptions ]; &lt;div ng-controller="Panel1Ctrl"&gt; 也错过了我想要的重点,即动态ng-repeat 中分配控制器。 顺便说一句,使用答案中的方法定义控制器是否有好处?我只是用另一种方式做的,因为教程就是这样做的。 在这种情况下,您可以从全局范围 (window) 直接访问实现。对我来说这很糟糕,这意味着任何第三方脚本都可以访问(和更改?)它。关于 dynamically 分配控制器,通过 dynamically 分配模板,您也“动态”分配控制器,只是以更结构化的方式(即通过将模板从 ' alert.html' 到 'subscription.html' 您还将控制器从 AlertCtrl 更改为 SubscriptionCtrl) 不像直接在 ng-repeat 中指定控制器那样优雅的 IMO,但它可以工作,但有一点需要注意。我的代码中的控制器有一个 onLoad 方法,指定为 ng-include 标记的 onload 属性。我希望它在选项卡加载时触发,但它不会,因为它是在控制器的范围内定义的,它只存在于 ng-include 的 div 内。您可以将 onLoad 方法设为全局(与控制器一起做不好),或将它们添加到选项卡规范中,与 SET 的答案存在相同的问题。难住了。我原来的 Plunk 是here。 哦!我第一次没看到!实际上可以访问模板内的面板对象。这解决了我的问题。【参考方案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】:

好的,我认为这里最简单的解决方案是在文件模板上明确定义控制器。假设你有一个数组:

$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。

【讨论】:

以上是关于AngularJS:从 ng-repeat 动态分配控制器的主要内容,如果未能解决你的问题,请参考以下文章

删除动态添加的值到 ng-repeat(angularjs)

ng-repeat 中表单的 AngularJs 动态名称

在 angularjs 中使用 ng-repeat 创建动态表

如何使用 ng-repeat 使 bxSlider 在 AngularJS 中工作?

AngularJS ng-repeat 用于动态子 json 数组

如何使用ng-repeat使bxSlider在AngularJS中工作?