使用 ng-controller 注入所需的依赖项

Injecting required dependencies with ng-controller

使用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 的自定义指令或重构我们的代码以使用继承范围之外,是否有任何开箱即用的方法可以做到这一点?

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

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

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

.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

在某些地方,我对状态和指令都使用了控制器,看起来与您正在尝试做的类似。

您可以定义一个重新使用您的控制器和模板的指令。它将您想要从状态中设置的内容添加为作用域上可用的参数:

.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

答案似乎是"no out of the box"方式。受到回复的启发,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 能让这些小块成为 public API 的一部分)。

ngComponent 是一种 "light-weight" 指令,可以在您的标记中声明,而无需实际构建指令。您可能会更进一步,但在某些时候您越界了,无论如何都需要编写指令。