当 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 变成 truemy-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 组件,因为 *ngIffalse。这意味着,不调用它就不是在实例化它,因此不会通过它的 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),但您可以通过使用作为函数的输入来模拟输出。

Tutorial for Angular

Tutorial for AngularDart

警告:因为这只是一个指令,所以它不应该做比在适当的时候实例化一个模板 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 仍然实例化组件导致依赖注入失败。

我确定的解决方案是将包装器组件重写为结构指令。