【问题标题】:Injecting required dependencies with ng-controller使用 ng-controller 注入所需的依赖项
【发布时间】:2015-03-29 10:17:51
【问题描述】:

使用 ui.router,我们有一个状态控制器:

controller('widget', function($repository, $stateParams){
    $scope.widget = $repository.get($stateParams.id);
})

注册:

.state('widget',
       controller: 'widget',
       template: '/widgetTemplate.html'

我们遇到了一个案例,我们希望将此控制器作为模板的一部分重用:

<div ng-controller="widget" ng-include="/widgetTemplate.html"></div>

但似乎没有一种简单的方法来注入具有正确 ID 的模拟 $stateParams 对象。比如:

<div ng-controller="widget" ng-inject="{$stateParams: {id: 1234}}" ng-include="/widgetTemplate.html"></div>

除了编写增强 ng-controller 的自定义指令或重构我们的代码以利用继承的作用域之外,是否有任何开箱即用的方法来做到这一点?

【问题讨论】:

    标签: angularjs angular-ui-router


    【解决方案1】:

    我不相信有一种开箱即用的方式。 ng-controller 只是使用普通的控制器实例化,没有机会注入任何东西。

    但这是一个有趣的“功能”,实际上可以使用自定义指令相对简单地构建它。

    这是一个说明性示例(免责声明:绝对没有在晦涩的场景下进行测试):

    .directive("ngInject", function($parse, $interpolate, $controller, $compile) {
      return {
        terminal: true,
        transclude: true,
        priority: 510,
        link: function(scope, element, attrs, ctrls, transclude) {
    
          if (!attrs.ngController) {
            element.removeAttr("ng-inject");
            $compile(element)(scope);
            return;
          }
    
          var controllerName = attrs.ngController;
    
          var newScope = scope.$new(false);
    
          var locals = $parse(attrs.ngInject)(scope);
          locals.$scope = newScope;
    
          var controller = $controller(controllerName, locals);
    
          element.data("ngControllerController", controller);
    
          element.removeAttr("ng-inject").removeAttr("ng-controller");
          $compile(element)(newScope);
          transclude(newScope, function(clone){
            element.append(clone);
          });
          // restore to hide tracks
          element.attr("ng-controller", controllerName); 
        }
      };
    });
    

    用法如你所描述:

    <div ng-controller="MainCtrl">
      {{name}}
      <div ng-controller="SecondCtrl" ng-inject="{foo: name, bar: 'bar'}">
      </div>
    </div>
    

    当然,控制器可以注入这些变量:

    .controller("SecondCtrl", function($scope, foo, bar){
    });
    

    plunker

    【讨论】:

    • 我也有同样的想法——但想看看在我从头开始写东西之前是否有一种开箱即用的方法。你是怎么想出 510 作为优先级的?
    • ng-controller 为 500
    • 这个答案也适用于我遇到的ui-route 代码可重用性问题。 stackoverflow.com/questions/29023440/…
    【解决方案2】:

    在某些地方,我使用了控制器来处理状态和指令,看起来与您正在尝试执行的操作相似。

    您可以定义一个指令来重用您的控制器和模板。它将您希望从状态中设置的内容添加为范围内可用的参数:

    .directive('widget', function(){
      return {
        restrict: 'E',
        template: '<div>id in directive {{widgetId}}</div>',
        controller: 'widget',
        scope: {
          widgetId:'='
        }
      };
    })
    

    然后更新您的控制器以查看范围或状态参数:

    .controller('widget', function($scope, $stateParams){
      $scope.widgetId = $scope.widgetId || $stateParams.id;
    })
    

    最后,您可以使用它通过特定的 id 引用小部件:

    <widget widget-id="789"></widget>
    

    这是一个带有示例的 plunker:http://plnkr.co/edit/0rSfr4jt48tSyHXwgnS5?p=preview

    【讨论】:

    • 此方法有效,除非您尝试将控制器与不使用 ui.router 的模块一起使用。将没有 $stateParams 可以注入。我试图避免修改当前的控制器代码。
    • 您可以使用 $injector.has 检查 $stateParams 是否可用,但仍需修改当前控制器代码。
    【解决方案3】:

    答案似乎是“不开箱即用”的方式。受到回复的启发,here is what I ended up implementing

    用法:

    <div ng-component="test.controller({$stateParams: { id: 1}})" template="test.html"></div>
    
    <div ng-component="test.controller({$stateParams: { id: 2}})">
      <div>Transcluded Template ID: {{id}}</div>
    </div>
    

    实施:

    .directive('ngComponent', function($compile, $parse, $controller, $http, $templateCache) {
    return {
      restrict: 'A',
      transclude: true,
      scope: true,
      compile: function(tElement, tAttr) {
        return function(scope, element, attrs, ctrl, transclude) {
    
          //credit for this method goes to the ui.router team!
          var parseControllerRef = function(ref, current) {
            var preparsed = ref.match(/^\s*({[^}]*})\s*$/),
              parsed;
            if (preparsed) ref = current + '(' + preparsed[1] + ')';
            parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
            if (!parsed || parsed.length !== 4) throw new Error("Invalid component ref '" + ref + "'");
            return {
              controller: parsed[1],
              paramExpr: parsed[3] || null
            };
          };
    
          var ref = parseControllerRef(attrs.ngComponent);
          scope.$eval(ref.paramExpr);
          if(attrs.template) {
            $http.get(attrs.template, {cache: $templateCache}).then(function(result){
              var template = $compile(result.data)(scope);
              element.append(template);
            },
            function(err){
                //need error handling
            });
          }
          else {
              transclude(scope, function(clone) {
                element.append(clone);
              })
          }
    
          var locals = {
            $scope: scope
          }
    
          angular.extend(locals, scope.$parent.$eval(ref.paramExpr));
          var controller = $controller(ref.controller, locals);
          element.data("ngControllerController", controller);
    
          //future:  may even allow seeing if controller defines a "link" function or 
          //if the attrs.link parameter is a function.
          //This may be the point of demarcation for going ahead and writing a 
          //directive, though.
    
        };
       }
     };
    })
    .controller('test.controller', function($scope, $stateParams) {
        $scope.id = $stateParams.id;
    })
    

    我使用了实现 uiSref 的代码的修改版本(有时我希望 Angular 能让这些小块成为公共 API 的一部分)。

    ngComponent 是一种“轻量级”指令,可以在您的标记中声明,而无需实际构建指令。你可能会走得更远一点,但在某些时候你会越界到需要编写指令。

    【讨论】:

    • 你可以用$templateRequest代替$http
    • 对,1.3 之前不可用
    猜你喜欢
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 2011-11-22
    • 2019-12-25
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多