AngularJS Composite Components

2019-08-14 03:46发布

问题:

I'm trying to create a component that contains multiple child components. I'm creating a collection of directives that other developers on my team can use to create custom search forms. For example, they would create markup like:

<search-panel name="myCustomSearch">
    <search-field name="dates" type="dateRange"></search-field>
    <search-field name="categories" type="categorySelector" data="{{categories}}"></search-field>
</search-panel>

I have a search panel directive and several search field directives. The search panel acts as a container and the search fields each provides a specific type of field (date range, type ahead, category selector, etc). Each search field has a value property in it's scope and I am trying to figure out a way to have the parent search panel directive have a property in it's scope that contains a key, value collection of all of the child search fields.

I've got both directives rendering correctly, but I'm not sure how to either make the search-panel aware of/have access to all the child components.

回答1:

You can require in search-field directive a search-panel controller using require:'^searchPanel'

Then in link function you'll have a link to that controller, so those directives could add themselves into some array (I assuemm each search field has its isolated scope):

link: function(scope, elem, attrs, spCtrl) {
  spCtrl.fields.push({name: attrs.name, scope: scope});
}

(ofcourse, you could add not whole scope, but some object, then watch for changes and update value field of that object.



回答2:

The basic idea here is to create a common controller between the two and link them up in the directive linking.

When you create a directive you can pass the controllers into the fourth parameter as such:

app.directive('myDirective', function(){
  return {
    scope: true,
    link: function postLink( scope, element, attrs, ctrls ){
      //check ctrls for common link
    }
  }
}

An excellent example created in the angularui/bootstrap project is the Tabs Directive which has a common controller linking them up as an example on where to get started.

Hope this helps.



回答3:

There is a good stackoverflow answer here by Mark Rajcok:

AngularJS directive controllers requiring parent directive controllers?

with a link to this very clear jsFiddle: http://jsfiddle.net/mrajcok/StXFK/

<div ng-controller="MyCtrl">
    <div screen>
        <div component>
            <div widget>
                <button ng-click="widgetIt()">Woo Hoo</button>
            </div>
        </div>
    </div>
</div>

JavaScript is in the jsFiddle.



回答4:

My implementation of composite component (multiple transcluding directives) is based on the following idea:

  • capture child component $transclude with one directive
  • output this child component with another directive

Live demo http://nickholub.github.io/angular-composite-component/#/

Demo source code https://github.com/nickholub/angular-composite-component

Directive source code https://github.com/nickholub/angular-composite-component/blob/master/app/directive/angular-composite-component.js

<div cs-composite>
    <div cs-section="header">
        Composite Component Header
    </div>
    <div cs-section="footer">
        Composite Component Footer
        <div>Random Value: {{randomValue}}</div>
        <div>Percentage: {{percentage}}%</div>
    </div>
</div>

Directive that captures content

.directive('csSection', function () {
    return {
      transclude: 'element',
      priority: 100,
      require: '^csComposite',
      link: function (scope, element, attrs, ctrl, $transclude) {
        var directiveTransclude = {
          id: attrs.csSection,
          transclude: $transclude,
          element: element
        };

        ctrl.registerTransclude(directiveTransclude);
      }
    };
  })

Directive that outputs content

.directive('csTransclude', function () {
    return {
      transclude: true,
      link: function (scope, element, attrs) {
        var id = attrs.csTransclude;
        var directiveTransclude = scope.transcludes[id];
        if (directiveTransclude) {
          var selectedScope = scope.$new();
          directiveTransclude.transclude(selectedScope, function (clone) {
            element.append(clone);
          });
        }
      }
    };
  })