Angular - Структурная директива со встроенным представлением не передает дочерние элементы в ng-template - PullRequest
4 голосов
/ 22 апреля 2020

У меня есть структурная директива, которая создает встроенное представление путем поиска ссылки на шаблон с помощью ng-template. Моя проблема в том, что из этого родительского компонента (со структурной директивой) я не могу передать потомков.

Родительский компонент со структурной директивой

import { ViewChild, Component, OnInit, ElementRef } from "@angular/core";
import { TestJsonService } from "../../services/test-json.service";

@Component({
  selector: "xfr-json-renderer",
  template: `
    <template-lookup></template-lookup>
    <div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
      <div>Why can't i pass this down to the child?</div>
    </div>
  `,
  styleUrls: ["./json-renderer.component.css"],
})
export class JsonRendererComponent implements OnInit {
  @ViewChild("childTemplate") childTemplate;
  constructor(el: ElementRef, json: TestJsonService) {}

  ngOnInit(): void {}
  ngAfterViewInit() {}
}

Дочерний компонент

import { Injectable, TemplateRef, Component, ViewChild } from "@angular/core";

@Injectable()
export class TemplateStore {
  templates = new Map<string, TemplateRef<any>>();
}

@Component({
  selector: "template-lookup",
  template: `
    <ng-template #flexRow></ng-template>
    <ng-template #flexCol><xfr-flex-col>
      // I want to pass the children into here
    </xfr-flex-col></ng-template>
  `,
})
export class TemplateLookup {
  @ViewChild("flexRow") flexRowTemplate;
  @ViewChild("flexCol") flexColTemplate;

  constructor(private service: TemplateStore) {}
  ngAfterViewInit() {
    this.service.templates.set("flexRow", this.flexRowTemplate);
    this.service.templates.set("flexCol", this.flexColTemplate);
  }
}

Структурная директива

import { ViewContainerRef } from "@angular/core";
import { TemplateStore } from "./../services/composite-template.service";
import { Directive, Input } from "@angular/core";

@Directive({
  selector: "[replaceWith]",
})
export class CompositeTemplateDirective {
  @Input() replaceWith: "flex-col" | "flex-row";
  constructor(private service: TemplateStore, private view: ViewContainerRef) {}
  ngAfterViewInit() {
    this.view.createEmbeddedView(this.service.templates.get(this.replaceWith));
  }
}

Ответы [ 2 ]

0 голосов
/ 28 апреля 2020

поэтому самая прагматичная c вещь здесь заключается в том, чтобы просто поместить дочернего элемента, которого вы хотите передать в качестве дочернего элемента компонента template-lookup, и использовать ng-content ...

сделать это в родитель:

<template-lookup>
  <div>I will pass to child</div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">

</div>

и это у ребенка:

<ng-template #flexRow></ng-template>
<ng-template #flexCol>
  <xfr-flex-col>
    <ng-content></ng-content>
  </xfr-flex-col>
</ng-template>

, и это решит вашу проблему / выполнит заявленные требования.

Вы также можете рассмотреть возможность переписывания в вашем сервисе для решения проблем синхронизации между шаблонами, которые устанавливаются и получают раз и навсегда:

import { Injectable, TemplateRef } from "@angular/core";
import {ReplaySubject} from 'rxjs';
import {map, filter, distinctUntilChanged} from 'rxjs/operators';

@Injectable({providedIn: 'root'}) // provide appropriately, root for example
export class TemplateStore {
  private templates = new Map<string, TemplateRef<any>>();
  private tmpSource = new ReplaySubject<Map<string, TemplateRef<any>>>(1);

  setTemplate(key: string, template: TemplateRef<any>) {
    this.templates.set(key, template);
    this.tmpSource.next(this.templates)
  }

  getTemplate(key: string) {
    return this.tmpSource.pipe(
      map(tmpMap => tmpMap.get(key)),
      filter(tmp => !!tmp),
      distinctUntilChanged()
    )
  }
}

и вносить соответствующие изменения в директиву и дочерние компоненты. ...

export class CompositeTemplateDirective implements OnInit, OnDestroy {
  @Input() replaceWith: "flex-col" | "flex-row";
  private sub: Subscription;

  constructor(private service: TemplateStore, private viewContainer: ViewContainerRef) { }

  ngOnInit() {
    this.sub = this.service.getTemplate(this.replaceWith).subscribe(t => {
      this.viewContainer.clear()
      this.viewContainer.createEmbeddedView(t)
    })
  }

  ngOnDestroy() {
    this.sub.unsubscribe()
  }
}

export class TemplateLookup {
  @ViewChild("flexRow") flexRowTemplate;
  @ViewChild("flexCol") flexColTemplate;

  constructor(private service: TemplateStore) {}
  ngAfterViewInit() {
    this.service.setTemplate("flexRow", this.flexRowTemplate);
    this.service.setTemplate("flexCol", this.flexColTemplate);
  }
}

пример работы: https://stackblitz.com/edit/angular-ygdveu

было отмечено, что это не поддерживает вложение ... поэтому выполните следующие настройки и ты можешь вкладывать в поиске по шаблону вам нужно будет использовать модификатор SkipSelf в своем конструкторе, а также предоставить TemplateStore ... в случае отсутствия вложений это не даст никакого эффекта, SkipSelf просто скажет инжектору начать поиск для службы у родителя, а не в компоненте:

@Component({
  selector: "template-lookup",
  template: `
    <ng-template #flexRow>FLEX ROW</ng-template>
    <ng-template #flexCol>
      FLEX COL
      <div class="flex-col">
        <ng-content></ng-content>
      </div>
    </ng-template>
  `,
  providers: [TemplateStore]
})
export class TemplateLookup {
  @ViewChild("flexRow") flexRowTemplate;
  @ViewChild("flexCol") flexColTemplate;

  constructor(@SkipSelf() private service: TemplateStore) {}
  ngAfterViewInit() {
    this.service.setTemplate("flexRow", this.flexRowTemplate);
    this.service.setTemplate("flexCol", this.flexColTemplate);
  }
}

, тогда вы можете вкладывать в свои сердца содержимое так:

<template-lookup>
  <div>I can pass this to the child!</div>
  <template-lookup>NESTED</template-lookup>
  <div class="nested-content" *replaceWith="'flexCol'"></div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">

</div>

, что немного уродливо, так как вам нужно повторить компонент поиска шаблона, но он выполняет свою работу. Это работает, позволяя директиве и поиску шаблона взаимодействовать с другой копией TemplateStore, чтобы вы могли вкладывать другое содержимое.

рабочий пример этого варианта: https://stackblitz.com/edit/angular-lpner2

0 голосов
/ 28 апреля 2020

Проблема в том, что вам нужно использовать внутренний API для этого, что не самое лучшее. Я буду использовать его до тех пор, пока не останусь с той же angular версией, и буду проверять его перед каждым обновлением - тогда он будет работать стабильно.

Я смог сделать инъекцию с Angular 9, вполне уверен, что аналогичный решение (но другой внутренний API) может быть применено для других angular версий.

Главное для внедрения - где внедрить контент, в компонентах мы могли бы использовать ng-content, но здесь это не будет не работает, потому что у нас разные контексты компонентов. В этом случае мы могли бы использовать <ng-template [ngTemplateOutlet]></ng-template>, чтобы сообщить сценарию, где мы хотим выполнить инъекцию.

здесь вы можете найти живую демонстрацию: https://codesandbox.io/s/nifty-wright-335bm?file= / src / app / json -renderer. component.ts

CompositeTemplateDirective

import {NgTemplateOutlet} from '@angular/common';
import {AfterViewInit, Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {TemplateStore} from 'src/app/TemplateLookup/TemplateLookup';

@Directive({
    selector: '[replaceWith]',
})
export class CompositeTemplateDirective implements AfterViewInit {
    @Input() replaceWith: 'flex-col' | 'flex-row';

    constructor(
        private service: TemplateStore,
        private view: ViewContainerRef,
        private templateRef: TemplateRef<any>,
    ) {
    }

    public ngAfterViewInit(): void {
        const wrapper = this.service.templates.get(this.replaceWith);
        const source = this.templateRef;

        const view: any = this.view.createEmbeddedView(wrapper);

        let directive: NgTemplateOutlet;
        const nodes: Array<any> = view._lView ? view._lView : view._view && view._view.nodes ? view._view.nodes : [];
        for (const node of nodes) {
            if (typeof node !== 'object') {
                continue;
            }
            if (node instanceof NgTemplateOutlet) {
                directive = node;
            }
            if (typeof node.instance === 'object' && node.instance instanceof NgTemplateOutlet) {
                directive = node.instance;
            }
        }
        if (directive) {
            directive.ngTemplateOutlet = source;
            directive.ngOnChanges({
                ngTemplateOutlet: {
                    previousValue: null,
                    currentValue: source,
                    firstChange: true,
                    isFirstChange: () => true,
                },
            });
        }
    }
}

TemplateLookup

import {AfterViewInit, Component, Injectable, TemplateRef, ViewChild} from '@angular/core';

@Injectable()
export class TemplateStore {
    templates = new Map<string, TemplateRef<any>>();
}

@Component({
    selector: 'template-lookup',
    template: `
        <ng-template #flexRow>
            <div>
                flexRow template
            </div>
        </ng-template>
        <ng-template #flexCol>
            <div>
                <div>wrap</div>
                <ng-template [ngTemplateOutlet]></ng-template>
                <div>wrap</div>
            </div>
        </ng-template>
    `,
})
export class TemplateLookup implements AfterViewInit {
    @ViewChild('flexRow', {static: false}) flexRowTemplate;
    @ViewChild('flexCol', {static: false}) flexColTemplate;

    constructor(
        private service: TemplateStore,
    ) {
    }

    ngAfterViewInit() {
        console.log('TemplateLookup:ngAfterViewInit');
        this.service.templates.set('flexRow', this.flexRowTemplate);
        this.service.templates.set('flexCol', this.flexColTemplate);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...