Как программно добавить компонент в Angular.Dart?

Я хотел бы динамически построить дерево компонентов на основе некоторой информации, полученной от вызовов AJAX.

Как программно добавить компонент в DOM изнутри другого компонента? У меня есть <outer-comp> и я хотел бы, исходя из некоторой логики, вставить <inner-comp>. Следующий код просто вставляет элементы <inner-comp></inner-comp> в DOM, а не фактическое представление <inner-comp>.

@NgComponent(
  selector: 'outer-comp',
  templateUrl: 'view/outer_component.html',
  cssUrl: 'view/outer_component.css',
  publishAs: 'outer'
)
class AppComponent extends NgShadowRootAware {      
  void onShadowRoot(ShadowRoot shadowRoot) {
    DivElement inner = shadowRoot.querySelector("#inner");
    inner.appendHtml("<inner-comp></inner-comp>");
  }
}

Обновление: мне удалось правильно отобразить внутренний компонент следующим образом, но я все еще не уверен, что это правильный способ:

class AppComponent extends NgShadowRootAware {
  Compiler compiler;
  Injector injector;
  AppComponent(this.compiler, this.injector);

  void onShadowRoot(ShadowRoot shadowRoot) {
    DivElement inner = shadowRoot.querySelector("#inner");
    inner.appendHtml("<inner-comp></inner-comp>");    
    BlockFactory template = compiler(inner.nodes);
    var block = template(injector);
    inner.replaceWith(block.elements[0]); 
  }

}


person Michal Pietrusinski    schedule 06.12.2013    source источник
comment
не могли бы вы показать, как вы вызываете свой конструктор AppComponent? как создать новый компилятор и новый инжектор?   -  person Daniel Robinson    schedule 21.04.2014
comment
@ 0xor1 нет необходимости вызывать конструктор - для этого нужна инъекция зависимостей ;-)   -  person Draško Kokić    schedule 26.04.2014
comment
так как бы вы достигли этого в коде, который не был в классе NgComponent? скажем, в основном методе вы хотели вызвать document.body.appendHtml('<my-comp></my-comp>'), как бы вы тогда получили angular, чтобы скомпилировать это?   -  person Daniel Robinson    schedule 27.04.2014


Ответы (5)


Это было бы правильным использованием блочного API.

class AppComponent extends NgShadowRootAware {
  Compiler compiler;
  Injector injector;
  Scope scope;
  DirectiveMap directives;

  AppComponent(this.compiler, this.injector, this.scope, this.directives);

  void onShadowRoot(ShadowRoot shadowRoot) {
    DivElement inner = shadowRoot.querySelector("#inner");
    inner.appendHtml("<inner-comp></inner-comp>");    
    BlockFactory template = compiler([inner], directives);
    Scope childScope = scope.$new();
    Injector childInjector = 
        injector.createChild(new Module()..value(Scope, childScope));
    template(childInjector, [inner]);
  }
}

Кроме того, если вам когда-нибудь понадобится перекомпилировать внутренний шаблон, убедитесь, что вы делаете childScope.$destroy() на предыдущем childScope.

person pavelgj    schedule 06.12.2013
comment
Спасибо за очень быстрый ответ, ваше решение работает отлично. - person Michal Pietrusinski; 07.12.2013
comment
@pavelgi: этот код отлично работает в angular 0.9.4. Но в angular 0.9.6 компилятору (...) требуется второй параметр (директивы DirectiveMap). Что такое DirectiveMap и что я должен передать в функцию? - person long.luc; 19.02.2014
comment
@pavelgj: Не могли бы вы обновить свой ответ, чтобы он соответствовал изменениям, внесенным в angular 0.9.9? - person Michal Pietrusinski; 17.03.2014
comment
как вы обновляете аргументы конструктора AppComponent? - person Daniel Robinson; 21.04.2014
comment
Вы просто объявляете любые необходимые аргументы — механизм внедрения зависимостей позаботится о том, чтобы предоставить вам правильные значения. - person Michal Pietrusinski; 23.04.2014

API изменился в AngularDart 0.9.9:

  • BlockFactory теперь называется ViewFactory.
  • scope.$new теперь выглядит как scope.createChild(scope.context)
  • Injector.createChild(modules) теперь требует список модулей (вместо одного)

В AngularDart 0.10.0 внесены следующие изменения:

  • NgShadowRootAware не является ShadowRootAware
  • ..value() теперь ..bind(., toValue: .)

Итак, код pavelgj теперь выглядит так:

class AppComponent extends ShadowRootAware {
  Compiler compiler;
  Injector injector;
  Scope scope;
  DirectiveMap directives;

  AppComponent(this.compiler, this.injector, this.scope, this.directives);

  void onShadowRoot(ShadowRoot shadowRoot) {
    DivElement inner = shadowRoot.querySelector("#inner");
    inner.appendHtml("<inner-comp></inner-comp>");    
    ViewFactory template = compiler([inner], directives);
    Scope childScope = scope.createChild(scope.context);
    Injector childInjector = 
        injector.createChild([new Module()..bind(Scope, toValue: childScope)]);
    template(childInjector, [inner]);
    }
  }
person Stephan Rauh    schedule 26.03.2014
comment
Я думаю, что вы случайно просто скопировали и вставили ответ @pavelgj. - person Ruslan Stelmachenko; 13.04.2014
comment
Ой! Ты прав. Надеюсь, мне удалось исправить код - кажется, я снова избавился от своей проверенной версии. В любом случае, ng-include делает что-то подобное, поэтому всегда полезно взглянуть на реализацию NgIncludeDirective. - person Stephan Rauh; 13.04.2014
comment
А как насчет ng-repeat? Когда я попытался добавить что-то вроде ‹inner-comp ng-repeat='rows in ctrl.list'›‹/inner-comp›, директива NgRepeat не сработала. Даже конструктор NgRepeat не будет вызываться. Через некоторое время я начал подозревать, что это не моя вина. Как вы думаете, должен ли ng-repeat работать, или в примере Павла не хватает важной инициализации? - person Stephan Rauh; 02.05.2014

Приведенные выше примеры кода работают дольше из-за изменений в библиотеке Angular Dart. В частности, ViewFactory.call, который больше не использует инжектор, но принимает Scope и DirectiveInjector. Я пытался адаптировать то, что выше, и я очень близок к этому. Компонент отображается, но ни одна из привязок не заменяется (например, я вижу {{cmp.value}}.

Вот код, который я использую. Я думаю, что проблема здесь в том, что DirectiveInjector имеет значение null.

void main() {
  IBMModule module = new IBMModule();
  AngularModule angularModule = new AngularModule();

  Injector injector = applicationFactory()
  .addModule(module)
  .run();

  AppComponent appComponent = injector.get(AppComponent);
  appComponent.addElement("<brazos-input-string label='test'/>");
}

@Injectable()
class AppComponent {
  NodeValidator validator;
  Compiler _compiler;
  DirectiveInjector _injector;
  DirectiveMap _directiveMap;
  NodeTreeSanitizer _nodeTreeSanitizer;
  Scope _scope;

  AppComponent(this._injector, this._compiler, this._directiveMap, this._scope, this._nodeTreeSanitizer) {
    validator = new NodeValidatorBuilder.common()
                      ..allowCustomElement("BRAZOS-INPUT-STRING")
                      ..allowHtml5()
                      ..allowTemplating();
  }

  void addElement(String elementHTML) {
    DivElement container = querySelector("#container");
    DivElement inner = new DivElement();
    inner.setInnerHtml(elementHTML, validator: validator);
    ViewFactory viewFactory = _compiler.call([inner], _directiveMap);
    Scope childScope = _scope.createChild(new PrototypeMap(_scope.context));
    if (_injector == null) {
      print("injector is null");
    }
    View newView = viewFactory.call(childScope, _injector);
    container.append(inner);
    newView.nodes.forEach((node) => inner.append(node));
  }
}


class IBMModule extends Module {
  IBMModule() {
    bind(BrazosInputStringComponent);
    bind(BrazosTextAreaComponent);
    bind(BrazosButtonComponent);
    bind(ProcessDataProvider, toImplementation: ActivitiDataProvider);
    bind(AppComponent);
  }
}
person David Parish    schedule 09.10.2014

Наконец-то я заставил это работать, но не был доволен необходимостью добавить таймер:

@Injectable()
class AppComponent{
  NodeValidator validator;
  Compiler _compiler;
  DirectiveInjector _directiveInjector;
  DirectiveMap _directiveMap;
  NodeTreeSanitizer _nodeTreeSanitizer;
  Injector _appInjector;
  Scope _scope;

  AppComponent(this._directiveInjector, this._compiler, this._directiveMap, this._nodeTreeSanitizer, this._appInjector, this._scope) {
    validator = new MyValidator();
  }

  void addElement(String id, String elementHTML) {
    DivElement container = querySelector(id);
    DivElement inner = new DivElement();
    container.append(inner);
    Element element = new Element.html(elementHTML, validator: validator);
    ViewFactory viewFactory = _compiler.call([element], _directiveMap);
    if (_scope != null) {
      Scope childScope = _scope.createProtoChild();
      View newView = viewFactory.call(childScope, _directiveInjector);
      newView.nodes.forEach((node) => inner.append(node));
      Timer.run(() => childScope.apply());
    } else {
      print("scope is null");
    }
  }
}
person David Parish    schedule 05.11.2014
comment
Я думаю, что new Future(() => childScope.apply()); будет немного лучше, чем scheduleMicroTask(() => childScope.apply()); (хотя может и не сработать). - person Günter Zöchbauer; 05.11.2014
comment
@GünterZöchbauer Я считаю, что правильным было бы запланировать это как микрозадачу. Почему вы думаете, что это не сработает? - person Austin Cummings; 24.12.2014
comment
ScheduleMicrotask может выполнить его слишком рано. - person Günter Zöchbauer; 24.12.2014

ИЗМЕНИТЬ

Пакет http://pub.dartlang.org/packages/bwu_angular содержит этот декоратор/директиву как bwu-safe-html

------

Я использую пользовательскую директиву для этого

@NgDirective(
  selector: '[my-bind-html]'
)
class MyBindHtmlDirective {
  static dom.NodeValidator validator;

  dom.Element _element;
  Compiler _compiler;
  Injector _injector;
  DirectiveMap _directiveMap;

  MyBindHtmlDirective(this._element, this._injector, this._compiler, this._directiveMap) {
    validator = new dom.NodeValidatorBuilder.common()
        ..allowHtml5()
        ..allowImages();
  }

  @NgOneWay('my-bind-html')
  set value(value) {
    if(value == null) {
      _element.nodes.clear();
      return;
    }
    _element.setInnerHtml((value == null ? '' : value.toString()),
                                             validator: validator);
    if(value != null) {
      _compiler(_element.childNodes, _directiveMap)(_injector, _element.childNodes);
    }
  }
}

Его можно использовать как

my-bind-html='ctrl.somehtml'

Проблема Angular
Я создал задачу, чтобы включить эту функцию в Angulars ng-bind-html https://github.com/angular/angular.dart/issues/742 (отклонено)

person Günter Zöchbauer    schedule 17.03.2014
comment
Вопрос закрыт. Эта функциональность не будет добавлена ​​в Angular по соображениям безопасности. - person Günter Zöchbauer; 19.03.2014