如何将父组件中声明的 ng-template 中的变量传递给子组件 component/directive?
How to pass variables from ng-template declared in parent component to a child component/directive?
所以我想知道是否有办法传递 ng-template 并生成它的所有内容以包含插值中使用的变量?
另外,我还是 angular 的新手,所以除了删除 html 元素外,我还需要担心删除其他任何内容吗?
最后会有一个 link 到 stackblitz.com 的 repo,其中将包含下面显示的所有代码。
以下是我的 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();
}
}
这是显示我的问题的回购 link:
https://stackblitz.com/edit/popupproblem
首先让我们考虑一下如何将上下文传递给嵌入式视图。您写道:
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}}
为什么 UI 没有更新?
Angular 变更检测机制依赖于视图树。
AppComponent_View
/ \
ChildComponent_View EmbeddedView
|
SubChildComponent_View
我们看到有两种视图:组件视图和嵌入视图。 TemplateRef
(ng-template) 表示嵌入式视图。
当 Angular 想要更新 UI 时,它只需通过该视图两次检查绑定。
现在让我们提醒一下我们如何通过底层创建嵌入式视图API:
TemplateRef.createEmbeddedView
ViewContainerRef.createEmbeddedView
它们之间的主要区别是前者简单地创建 EmbeddedView,而后者创建 EmbeddedView 并将其添加到Angular变化检测树。这样嵌入式视图就成为变更检测树的一部分,我们可以看到更新的绑定。
是时候查看您的代码了:
this.body.createEmbeddedView(this.viewContainer._view.context).rootNodes.forEach(appendElementToPopup);
很明显您使用的是第一种方法。这意味着您必须自己处理更改检测:手动调用 viewRef.detectChanges()
或附加到树。
简单的解决方案可以是:
const view = this.body.createEmbeddedView({});
view.detectChanges();
view.rootNodes.forEach(appendElementToPopup);
但它只会检测一次更改。我们可以在每个 Popup.ngDoCheck()
挂钩上调用 detectChanges
方法,但是 Angular 本身使用了一种更简单的方法。
const view = this.viewContainer.createEmbeddedView(this.body);
view.rootNodes.forEach(appendElementToPopup);
我们使用了第二种创建嵌入视图的方法,这样模板将由 Angular 本身自动检查。
I'm still new to angular so besides removing the html element do I
need to worry about removing anything else?
我认为我们也应该在关闭弹出窗口时销毁嵌入视图。
removePopup() {
this.viewContainer.clear();
...
}
所以我想知道是否有办法传递 ng-template 并生成它的所有内容以包含插值中使用的变量?
另外,我还是 angular 的新手,所以除了删除 html 元素外,我还需要担心删除其他任何内容吗?
最后会有一个 link 到 stackblitz.com 的 repo,其中将包含下面显示的所有代码。
以下是我的 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();
}
}
这是显示我的问题的回购 link: https://stackblitz.com/edit/popupproblem
首先让我们考虑一下如何将上下文传递给嵌入式视图。您写道:
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}}
为什么 UI 没有更新?
Angular 变更检测机制依赖于视图树。
AppComponent_View
/ \
ChildComponent_View EmbeddedView
|
SubChildComponent_View
我们看到有两种视图:组件视图和嵌入视图。 TemplateRef
(ng-template) 表示嵌入式视图。
当 Angular 想要更新 UI 时,它只需通过该视图两次检查绑定。
现在让我们提醒一下我们如何通过底层创建嵌入式视图API:
TemplateRef.createEmbeddedView
ViewContainerRef.createEmbeddedView
它们之间的主要区别是前者简单地创建 EmbeddedView,而后者创建 EmbeddedView 并将其添加到Angular变化检测树。这样嵌入式视图就成为变更检测树的一部分,我们可以看到更新的绑定。
是时候查看您的代码了:
this.body.createEmbeddedView(this.viewContainer._view.context).rootNodes.forEach(appendElementToPopup);
很明显您使用的是第一种方法。这意味着您必须自己处理更改检测:手动调用 viewRef.detectChanges()
或附加到树。
简单的解决方案可以是:
const view = this.body.createEmbeddedView({});
view.detectChanges();
view.rootNodes.forEach(appendElementToPopup);
但它只会检测一次更改。我们可以在每个 Popup.ngDoCheck()
挂钩上调用 detectChanges
方法,但是 Angular 本身使用了一种更简单的方法。
const view = this.viewContainer.createEmbeddedView(this.body);
view.rootNodes.forEach(appendElementToPopup);
我们使用了第二种创建嵌入视图的方法,这样模板将由 Angular 本身自动检查。
I'm still new to angular so besides removing the html element do I need to worry about removing anything else?
我认为我们也应该在关闭弹出窗口时销毁嵌入视图。
removePopup() {
this.viewContainer.clear();
...
}