当 ngIf 为 false 时构建 Angular4 ng-content
Angular4 ng-content gets built when ngIf is false
我对新的 ng-content
嵌入有疑问。
假设我有一个组件 my-component
,它的 ngOnInit()
函数在加载时执行一些繁重的操作(目前,只有 console.log()
)。
我有一个包装器,它通过嵌入显示内容 (my-wrapper.component.html
)。
<ng-content></ng-content>
如果我这样设置环境,日志语句不显示:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
我假设 my-wrapper
组件未构建,因此内容被忽略。
但是如果我尝试将逻辑移动到 my-wrapper
组件中,就像这样 (my-wrapper.component.html
):
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
我总是看到 console.log()
输出。我猜,my-component
被构建然后存储起来,直到 *ngIf
变成 true
在 my-wrapper
.
中
目的是构建通用 "list-item + detail" 组件。假设我有一个包含 N 个概览元素 (my-wrapper
) 的列表,这些元素在 *ngFor
循环中呈现。这些元素中的每一个都有自己的详细信息组件 (my-component
),一旦我决定显示特定项目的更多信息,它就应该加载自己的数据。
overview.html:
<ng-container *ngFor="let item of items">
<my-wrapper>
<my-component id="item.id"></my-component>
</my-wrapper>
</ng-container>
我的-wrapper.component.html:
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
<ng-content></ng-content>
</div>
有没有办法告诉 Angular,在有必要添加到页面之前忽略嵌入的内容?类似于 AngularJS.
中的情况
通过这样做:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
您没有调用 MyComponent
组件,因为 *ngIf
是 false
。这意味着,不调用它就不是在实例化它,因此不会通过它的 ngOnInit
。这就是为什么您没有收到控制台日志的原因。
通过这样做:
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
您在组件内部,您只是限制在模板中呈现的内容,但您已经实例化了组件,因此,您通过了 ngOnInit
并完成了控制台日志。
如果你想限制某些东西(使用选择器或 ng-content
甚至 div
的组件调用)直到你有一些可用的数据,你可以执行以下操作:
datasLoaded: Promise<boolean>;
this.getData().subscribe(
(data) => {
this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
}
);
在您的模板中:
<ng-container *ngIf="datasLoaded | async">
// stuff here
</ng-container>
或者:
<my-component *ngIf="datasLoaded | async">
// Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
根据@nsinreal 的评论我找到了答案。我觉得它有点深奥,所以我在这里 post 尝试:
答案是使用 ng-template
和 *ngTemplateOutlet
。
在 my-wrapper
组件中,像这样设置模板 (my-wrapper.component.html
):
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
<ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>
请注意,[hidden]
并不是真正必要的,它会隐藏子项的 "raw" 模板,直到它决定完成加载。请确保,不要将它放在 *ngIf
中,否则 *ngTemplateOutlet
永远不会被触发,导致什么都不会发生。
要设置 detailRef
,请将其放入组件代码 (my-wrapper.component.ts
):
import { ContentChild, TemplateRef } from '@angular/core';
@Component({ ... })
export class MyWrapperComponent {
@ContentChild(TemplateRef) detailRef;
...
}
现在,您可以像这样使用包装器:
<my-wrapper>
<ng-template>
<my-component></my-component>
</ng-template>
</my-wrapper>
我不确定,为什么它需要这么复杂 "workarounds",以前在 AngularJS.
中这样做很容易
这是因为 Ng 内容发生在构建时,当您传递内容时,它实际上并没有被删除或用 ngIf 指令重新创建。它只是被移动,组件被实例化。
我最近也遇到了这个问题,但最终选择了与当前接受的解决方案不同的解决方案。
解决方案 (TL;DR)
(针对 AngularDart 的解决方案;我认为它在 Angular 中是相似的)
使用结构指令;下面链接的教程。
而不是:
<my-wrapper>
<my-contents></my-contents>
</my-wrapper>
您的用法变为:
<div *myWrapper>
<my-contents></my-contents>
</div>
以下是 shorthand(在 AngularDart 中;我认为 Angular 使用 <ng-template>
)
<template myWrapper>
<div>
<my-contents></my-contents>
</div>
</template>
MyWrapper
指令逻辑类似于 NgIf
,只是它有自己的计算条件的逻辑。以下两个教程都解释了如何创建类似 NgIf
的指令以及如何使用特殊的微语法(例如 *myWrapper="myInput: expression"
)将您自己的输入传递给它。请注意,微语法不支持输出 (@Output
),但您可以通过使用作为函数的输入来模拟输出。
警告:因为这只是一个指令,所以它不应该做比在适当的时候实例化一个模板 ref 并可能指定一些 DI 提供者更复杂的事情。例如,我会避免在指令中尝试应用样式或实例化复杂的组件树。如果我想创建一个列表组件,我可能会采用另一个答案中描述的 @ContentChild(TemplateRef)
方法;您将失去创建 <template>
的星号 shorthand,但您将获得组件的全部功能。
我的问题
我的团队拥有一个应用程序,它是一个更大的网络应用程序的一部分,其他应用程序由其他团队拥有。我们的组件假设它们可以注入一个 MyAppConfiguration
对象,但这个对象只能在加载异步请求后才能注入。在我们的应用程序中,这不是问题:我们有一个“shell”组件,它在加载配置之前将所有内容隐藏在 ngIf
后面。
问题是当其他团队想要引用我们的组件时。我们不希望他们每次都重复“等待配置加载”逻辑,所以我尝试创建一个可以像这样使用的包装器组件:
<my-app-wrapper>
<my-app-component></my-app-component>
</my-app-wrapper>
包装器注入一个服务对象并将其内容隐藏在 ngIf
后面,直到服务说配置已加载。
与问题发布者一样,我发现 ng-content
方法没有按预期工作:虽然内容从 DOM 中正确隐藏,但 Angular 仍然实例化组件导致依赖注入失败。
我确定的解决方案是将包装器组件重写为结构指令。
我对新的 ng-content
嵌入有疑问。
假设我有一个组件 my-component
,它的 ngOnInit()
函数在加载时执行一些繁重的操作(目前,只有 console.log()
)。
我有一个包装器,它通过嵌入显示内容 (my-wrapper.component.html
)。
<ng-content></ng-content>
如果我这样设置环境,日志语句不显示:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
我假设 my-wrapper
组件未构建,因此内容被忽略。
但是如果我尝试将逻辑移动到 my-wrapper
组件中,就像这样 (my-wrapper.component.html
):
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
我总是看到 console.log()
输出。我猜,my-component
被构建然后存储起来,直到 *ngIf
变成 true
在 my-wrapper
.
目的是构建通用 "list-item + detail" 组件。假设我有一个包含 N 个概览元素 (my-wrapper
) 的列表,这些元素在 *ngFor
循环中呈现。这些元素中的每一个都有自己的详细信息组件 (my-component
),一旦我决定显示特定项目的更多信息,它就应该加载自己的数据。
overview.html:
<ng-container *ngFor="let item of items">
<my-wrapper>
<my-component id="item.id"></my-component>
</my-wrapper>
</ng-container>
我的-wrapper.component.html:
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
<ng-content></ng-content>
</div>
有没有办法告诉 Angular,在有必要添加到页面之前忽略嵌入的内容?类似于 AngularJS.
中的情况通过这样做:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
您没有调用 MyComponent
组件,因为 *ngIf
是 false
。这意味着,不调用它就不是在实例化它,因此不会通过它的 ngOnInit
。这就是为什么您没有收到控制台日志的原因。
通过这样做:
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
您在组件内部,您只是限制在模板中呈现的内容,但您已经实例化了组件,因此,您通过了 ngOnInit
并完成了控制台日志。
如果你想限制某些东西(使用选择器或 ng-content
甚至 div
的组件调用)直到你有一些可用的数据,你可以执行以下操作:
datasLoaded: Promise<boolean>;
this.getData().subscribe(
(data) => {
this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
}
);
在您的模板中:
<ng-container *ngIf="datasLoaded | async">
// stuff here
</ng-container>
或者:
<my-component *ngIf="datasLoaded | async">
// Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
根据@nsinreal 的评论我找到了答案。我觉得它有点深奥,所以我在这里 post 尝试:
答案是使用 ng-template
和 *ngTemplateOutlet
。
在 my-wrapper
组件中,像这样设置模板 (my-wrapper.component.html
):
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
<ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>
请注意,[hidden]
并不是真正必要的,它会隐藏子项的 "raw" 模板,直到它决定完成加载。请确保,不要将它放在 *ngIf
中,否则 *ngTemplateOutlet
永远不会被触发,导致什么都不会发生。
要设置 detailRef
,请将其放入组件代码 (my-wrapper.component.ts
):
import { ContentChild, TemplateRef } from '@angular/core';
@Component({ ... })
export class MyWrapperComponent {
@ContentChild(TemplateRef) detailRef;
...
}
现在,您可以像这样使用包装器:
<my-wrapper>
<ng-template>
<my-component></my-component>
</ng-template>
</my-wrapper>
我不确定,为什么它需要这么复杂 "workarounds",以前在 AngularJS.
中这样做很容易这是因为 Ng 内容发生在构建时,当您传递内容时,它实际上并没有被删除或用 ngIf 指令重新创建。它只是被移动,组件被实例化。
我最近也遇到了这个问题,但最终选择了与当前接受的解决方案不同的解决方案。
解决方案 (TL;DR)
(针对 AngularDart 的解决方案;我认为它在 Angular 中是相似的)
使用结构指令;下面链接的教程。
而不是:
<my-wrapper>
<my-contents></my-contents>
</my-wrapper>
您的用法变为:
<div *myWrapper>
<my-contents></my-contents>
</div>
以下是 shorthand(在 AngularDart 中;我认为 Angular 使用 <ng-template>
)
<template myWrapper>
<div>
<my-contents></my-contents>
</div>
</template>
MyWrapper
指令逻辑类似于 NgIf
,只是它有自己的计算条件的逻辑。以下两个教程都解释了如何创建类似 NgIf
的指令以及如何使用特殊的微语法(例如 *myWrapper="myInput: expression"
)将您自己的输入传递给它。请注意,微语法不支持输出 (@Output
),但您可以通过使用作为函数的输入来模拟输出。
警告:因为这只是一个指令,所以它不应该做比在适当的时候实例化一个模板 ref 并可能指定一些 DI 提供者更复杂的事情。例如,我会避免在指令中尝试应用样式或实例化复杂的组件树。如果我想创建一个列表组件,我可能会采用另一个答案中描述的 @ContentChild(TemplateRef)
方法;您将失去创建 <template>
的星号 shorthand,但您将获得组件的全部功能。
我的问题
我的团队拥有一个应用程序,它是一个更大的网络应用程序的一部分,其他应用程序由其他团队拥有。我们的组件假设它们可以注入一个 MyAppConfiguration
对象,但这个对象只能在加载异步请求后才能注入。在我们的应用程序中,这不是问题:我们有一个“shell”组件,它在加载配置之前将所有内容隐藏在 ngIf
后面。
问题是当其他团队想要引用我们的组件时。我们不希望他们每次都重复“等待配置加载”逻辑,所以我尝试创建一个可以像这样使用的包装器组件:
<my-app-wrapper>
<my-app-component></my-app-component>
</my-app-wrapper>
包装器注入一个服务对象并将其内容隐藏在 ngIf
后面,直到服务说配置已加载。
与问题发布者一样,我发现 ng-content
方法没有按预期工作:虽然内容从 DOM 中正确隐藏,但 Angular 仍然实例化组件导致依赖注入失败。
我确定的解决方案是将包装器组件重写为结构指令。