javascript - 如何将父组件中声明的 ng-template 中的变量传递给子组件/指令?

标签 javascript angularjs angular angular-directive ng-template

所以我想知道是否有办法传递一个 ng-template 并生成它的所有内容以包含插值中使用的变量?

此外,我对 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();
  }
}

这是显示我的问题的 repo 的链接: https://stackblitz.com/edit/popupproblem

最佳答案

首先让我们考虑一下如何将上下文传递给嵌入式 View 。你写道:

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

您的 Popup 组件托管在 AppComponent View 中,因此 this.viewContainer._view.context 将是 AppComponent实例。但我要告诉你的是:

1) 嵌入式 View 已经可以访问定义了 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}}

为什么 UI 没有更新?

Angular 变化检测机制依赖于 View 树。

         AppComponent_View
         /                \
ChildComponent_View    EmbeddedView
        |
 SubChildComponent_View

我们看到有两种 View :组件 View 和嵌入 View 。 TemplateRef(ng-template) 表示嵌入式 View 。

当 Angular 想要更新 UI 时,它只需通过该 View 两个检查绑定(bind)。

现在让我们提醒一下如何通过低级 API 创建嵌入式 View :

  • TemplateRef.createEmbeddedView

  • ViewContainerRef.createEmbeddedView

它们之间的主要区别在于前者只是创建 EmbeddedView 而后者创建 EmbeddedView 并且还将其添加到 Angular 变化检测树。通过这种方式,嵌入式 View 成为变更检测树的一部分,我们可以看到更新的绑定(bind)。

是时候查看您的代码了:

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

很明显您使用的是第一种方法。这意味着您必须自己处理变化检测:手动调用 viewRef.detectChanges() 或附加到树。

简单的解决方案可能是:

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

Stackblitz Example

但它只会检测一次变化。我们可以在每个 Popup.ngDoCheck() Hook 上调用 detectChanges 方法,但 Angular 本身使用了一种更简单的方法。

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

我们使用了第二种创建嵌入式 View 的方法,这样模板将由 Angular 本身自动检查。

I'm still new to angular so besides removing the html element do I need to worry about removing anything else?

我认为我们也应该在关闭弹出窗口时销毁嵌入式 View 。

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

Final Stackblitz Example

关于javascript - 如何将父组件中声明的 ng-template 中的变量传递给子组件/指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52750490/

相关文章:

javascript - 在 Click 事件上获取从 PHP 传递到 JS 的变量,返回所有元素变量,而不仅仅是被单击的变量

javascript - jQuery 数据表 : comparing two columns

javascript - 为什么 'this' 在此 TypeScript 片段中引用 'window'?

javascript - ngSanitize 仍然显示文本而不是 HTML

Angular2 [selected] 设置默认值不起作用?

Angular2 Animation Transitions 仅针对某些事件触发

javascript - 如何使用 HTML/JS 实现 'human' 日期范围选择下拉列表? (今天、昨天、上周……)

javascript - Mobile Safari、Chrome 和 Windows Safari 的 onLoad 问题

javascript - AngularJS $locationProvider.search() 导致未捕获对象错误

javascript - 尝试将 http 响应分配给数据源时出现 Angular 表错误