Как передать переменные из ng-шаблона, объявленного в родительском компоненте, в дочерний компонент / директиву? - PullRequest
0 голосов
/ 11 октября 2018

Итак, я хочу знать, есть ли способ передать ng-шаблон и сгенерировать весь его контент для включения переменных, используемых в интерполяции?

Кроме того, я все еще новичок в angular, поэтому помимо удаления элемента html мне нужно беспокоиться об удалении чего-либо еще?

В конце этого будет ссылка на репозиторий stackblitz.com, в котором будет весь код, показанный ниже.

ниже приведен мой код src / app / app.component.html, реализующий мою директиву:

<hello name="{{ name }}"></hello>
<p>
  Start editing to see some magic happen :)
</p>
<!-- popup/popup.directive.ts contains the code i used in button tag -->
<button PopupDir="" body="this is a hardcoded message that is passed to popup box"> simple 
</button>

<ng-template #Complicated="">
  <div style="background-color: red;">
    a little more complicated but simple and still doable
  </div>
</ng-template>
<button PopupDir="" [body]="Complicated">
  complicated
</button>

<ng-template #EvenMoreComplicated="">
  <!-- name and data isn't being passed i need help here--> 
  <div style="background-color: green; min-height: 100px; min-width:100px;">
    {{name}} {{data}}
  </div>
</ng-template>
<button PopupDir="" [body]="EvenMoreComplicated">
  more complicated
</button>

ниже приведен мой src / app / popup / popup.directive.ts

import { Directive, Input, TemplateRef, ViewContainerRef, HostListener } from '@angular/core'

@Directive({
  selector: 'PopupDir, [PopupDir]'
})
export class Popup {
  @Input() body: string | TemplateRef<any>;
  viewContainer: ViewContainerRef;
  popupElement: HTMLElement;

  //i dont know if i need this
  constructor (viewContainer: ViewContainerRef) {
    this.viewContainer = viewContainer;
  }

  //adds onlick rule to parent tag
  @HostListener('click')
  onclick () {
    this.openPopup();
  }

  openPopup() {
    //Pcreate pupup html programatically
    this.popupElement = this.createPopup();

    //insert it in the dom
    const lastChild = document.body.lastElementChild;
    lastChild.insertAdjacentElement('afterend', this.popupElement);
  }

  createPopup(): HTMLElement {
    const popup = document.createElement('div');
    popup.classList.add('popupbox');

    //if you click anywhere on popup it will close/remove itself
    popup.addEventListener('click', (e: Event) => this.removePopup());
    //if statement to determine what type of "body" it is
    if (typeof this.body === 'string')
    {
      popup.innerText = this.body;
    } else if (typeof this.body === 'object')
    {
      const appendElementToPopup = (element: any) => popup.appendChild(element);
      //this is where i get stuck on how to include the context and then display the context/data that is passed by interpolation in ng-template
      this.body.createEmbeddedView(this.viewContainer._view.context).rootNodes.forEach(appendElementToPopup);
    }
    return popup;
  }

  removePopup() {
    this.popupElement.remove();
  }
}

это ссылка на репо с моей проблемой: https://stackblitz.com/edit/popupproblem

1 Ответ

0 голосов
/ 11 октября 2018

Сначала давайте подумаем, как мы передаем контекст во встроенное представление.Вы писали:

this.body.createEmbeddedView(this.viewContainer._view.context)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Ваш компонент Popup размещен в представлении AppComponent, поэтому this.viewContainer._view.context будет AppComponent экземпляром.Но то, что я хочу, чтобы вы сказали:

1) Встроенное представление уже имеет доступ к области действия шаблона, где определено ng-template.

2) Если мы передаем контекст, то его следует использоватьтолько через переменные ссылки на шаблон.

this.body.createEmbeddedView(this.viewContainer._view.context)
             ||
             \/

this.body.createEmbeddedView({
  name = 'Angular';
  data = 'this should be passed too'
})
             ||
             \/

<ng-template #EvenMoreComplicated let-name="name" let-data="data">
    {{name}} {{data}}

Так что в этом случае вам не нужно передавать контекст, потому что он уже существует.

this.body.createEmbeddedView({})
             ||
             \/
<ng-template #EvenMoreComplicated>
        {{name}} {{data}}

Почему пользовательский интерфейс не обновляется?

Механизм обнаружения угловых изменений опирается на дерево представлений.

         AppComponent_View
         /                \
ChildComponent_View    EmbeddedView
        |
 SubChildComponent_View

Мы видим, что существует два вида представлений: компонентное представление и встроенное представление.TemplateRef (ng-template) представляет встроенный вид.

Когда Angular хочет обновить пользовательский интерфейс, он просто проходит через это представление две контрольные привязки.

Теперь давайте напомним, как мы можем создать встроенное представление с помощью низкоуровневого API:

  • TemplateRef.createEmbeddedView

  • ViewContainerRef.createEmbeddedView

Основное различие между ними заключается в том, что первый просто создает EmbeddedView, а второй -EmbeddedView, а также добавляет его в дерево обнаружения угловых изменений .Таким образом, встроенное представление становится частью дерева обнаружения изменений, и мы можем видеть обновленные привязки.

Пришло время увидеть ваш код:

this.body.createEmbeddedView(this.viewContainer._view.context).rootNodes.forEach(appendElementToPopup);

Должно быть ясно, что вы используете первыйподход.Это означает, что вы должны позаботиться об обнаружении изменений самостоятельно: либо позвоните viewRef.detectChanges() вручную, либо подключитесь к дереву.

Простое решение может быть:

const view = this.body.createEmbeddedView({});
view.detectChanges();
view.rootNodes.forEach(appendElementToPopup);

Пример Stackblitz

Но он обнаружит изменения только один раз.Мы могли бы вызывать метод detectChanges на каждом хуке Popup.ngDoCheck(), но есть более простой способ, который используется самим Angular.

const view = this.viewContainer.createEmbeddedView(this.body);
view.rootNodes.forEach(appendElementToPopup);

Мы использовали второй подход для создания встроенного представления, так что шаблон будет автоматическипроверено самим Angular.

Я все еще новичок в angular, поэтому помимо удаления html-элемента мне нужно беспокоиться об удалении чего-либо еще?

Я думаю, что мы должнытакже уничтожить встроенный вид при закрытии всплывающего окна.

removePopup() {
  this.viewContainer.clear();
  ...
}

Окончательный пример Stackblitz

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...