Injecting required dependencies with ng-controller

后端 未结 3 1457
南旧
南旧 2020-12-17 03:14

Using ui.router, we have a controller for a state:

controller(\'widget\', function($repository, $stateParams){
    $scope.widget = $repository.get($statePara         


        
相关标签:
3条回答
  • 2020-12-17 03:52

    The answer seems to be "no out of the box" way. Inspired by the responses, here is what I ended up implementing.

    Usage:

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

    Implementation:

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

    I used a modified version of the code that implements uiSref (sometimes I wish angular would make these little nuggets part of the public API).

    ngComponent is kind of a "light-weight" directive that can be declared in your markup without having to actually build a directive. You could probably take it a little farther, but at some point you cross the line into needing to write a directive anyway.

    0 讨论(0)
  • 2020-12-17 03:54

    There have been some places where I've used a controller for both a state and a directive that looks to be similar to what you're trying to do.

    You could define a directive that re-uses your controller and template. It adds what you'd want set from the state as a parameter that's available on the scope:

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

    Then update your controller to look in the scope or state params:

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

    Finally, you can use it to reference a widget by a specific id:

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

    Here's a plunker with a sample: http://plnkr.co/edit/0rSfr4jt48tSyHXwgnS5?p=preview

    0 讨论(0)
  • 2020-12-17 03:56

    I don't believe there is an out-of-the-box way. ng-controller just uses normal controller instantiation, and there is no opportunity to inject anything.

    But this is an interesting "feature", which can be built, actually, relatively simply with a custom directive.

    Here's an illustrative example (disclaimer: it is definitely not tested under obscure scenarios):

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

    The usage is as you described it:

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

    And, of course, the controller can have these variables injected:

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

    plunker

    0 讨论(0)
提交回复
热议问题