Repeat over elements provided as content

2019-08-15 03:46发布

问题:

When I create a component in Angular.dart like

library main;

import 'package:angular/angular.dart';
import 'package:di/di.dart';

class Item {
  String name;
  Item(this.name);
}

@NgComponent(
    selector: 'my-component',
    publishAs: 'ctrl',
    applyAuthorStyles: true,
    template: '''<div ng-repeat="value in ctrl.values"><span>{{value.name}}</span> - <content><content></div>'''
)

class MyComponent {
  List<Item> values = [new Item('1'), new Item('2'), new Item('3'), new Item('4')];

  MyComponent() {
    print('MyComponent');
  }
}


class MyAppModule extends Module {
  MyAppModule() {
    type(MyComponent);
  }
}

void main() {
  ngBootstrap(module: new MyAppModule());
}

and use it like

<!DOCTYPE html>

<html ng-app>
  <head>
    <meta charset="utf-8">
  </head>

  <body>

    <h3>Repeat</h3>
    <my-component>
      <div>some provided content to repeat</div>
    </my-component>

    <script type="application/dart" src="index.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

I get

I know the <content> tag isn't working that way in web components.

But is there any other way, some manipulation I can do in my component, to get the <div> provided as child element repeated?

回答1:

I solved it like

Code of <my-component>

@NgComponent(
    selector: 'my-component',
    publishAs: 'ctrl',
    template: '''<div ng-repeat="value in ctrl.values"><span ng-bind-nodes="ctrl.nodes"></span><span>something hardcoded: {{value.name}}</span></div><content id='content'></content>'''
)

class MyComponent extends NgShadowRootAware {
  List<Item> values = [new Item('1'), new Item('2'), new Item('3'), new Item('4')];

  List<dom.Node> nodes = new List<dom.Node>();

  MyComponent();

  @override
  void onShadowRoot(dom.ShadowRoot shadowRoot) {
    nodes.addAll((shadowRoot.querySelector('#content') as dom.ContentElement).getDistributedNodes());
    //nodes.forEach((n) => print(n));
    nodes.forEach((n) => n.remove());
  }
}

The component removes it's child nodes and provides them in the field nodes

the directive ng-bind-nodes adds the nodes to the element where it is applied

@NgDirective(
  selector: '[ng-bind-nodes]',
  publishAs: 'ctrlx' // conflicts with my-component
)
class NgBindNodesDirective {
  dom.Element _element;
  MyComponent _myComponent;
  Scope _scope;
  Compiler _compile;
  Injector _injector;

  NgBindNodesDirective(this._element, this._myComponent, this._scope, this._compile, this._injector);

  @NgOneWay('ng-bind-nodes') set nodes(var nodes) {
    print(nodes);
    if(nodes == null) {
      return;
    }
    _element.nodes.clear();
    nodes.forEach((dom.Node node) {
      _element.nodes.add(node.clone(true));
    });

    BlockFactory template = _compile(_element.nodes);
    Block block = template(_injector, _element.nodes);
  }
}


回答2:

I don't have an answer, and I can't test my suggestion right now, but try injecting the element, compiler, scope and blockfactory in MyComponent:

Element element;
Compiler compiler;
Injector injector;
Scope scope;

MyComponent(this.element, this.compiler, this.injector, this.scope) {
}

You can access the div as child of 'element'.

Then you don't use template of NgComponent, but instead build your own template from a string, insert the child and compile it:

String template = '''<div ng-repeat="value in ctrl.values"><span>{{value.name}}</span> - <div id="inner"><div></div>''';

void onShadowRoot(ShadowRoot shadowRoot) {
  List<DivElement> children = element.children;
  shadowRoot.appendHtml(template);    
  DivElement inner = shadowRoot.querySelector('#inner');
  inner.children.add(children);  
  BlockFactory fact = compiler([shadowRoot]);
  Scope childScope = scope.$new();
  Injector childInjector = 
      injector.createChild([new Module()
      ..value(Scope, childScope)]);
  fact(childInjector, children);
}

Maybe it gives you the right direction.