当 ngIf 为 false 时构建 Angular4 ng-content

Posted

技术标签:

【中文标题】当 ngIf 为 false 时构建 Angular4 ng-content【英文标题】:Angular4 ng-content gets built when ngIf is false 【发布时间】:2017-12-09 07:53:30 【问题描述】:

我对新的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.component.html) 的 my-wrapper 组件中:

<ng-container *ngIf="false">
    <ng-content></ng-content>
</ng-container>

我总是看到console.log() 输出。我猜,my-component 被构建然后存储起来,直到 *ngIfmy-wrapper 中变为 true

目的是构建一个通用的“列表项+详细信息”组件。假设我有一个 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>

我的包装器.component.html:

<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
    <ng-content></ng-content>
</div>

有没有办法告诉 Angular,在需要添加到页面之前忽略转入的内容?类似于 AngularJS 中的情况。

【问题讨论】:

我建议不要在包装器上添加条件,而是将条件添加到子组件以停止执行 ***.com/questions/41652919/… 【参考方案1】:

通过这样做:

<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>

【讨论】:

我认为这不适用于我的情况。目的是:我有一个概览数据列表,在*ngFor-loop (my-wrapper) 中呈现。对于每个概览项,我都有第二个“详细信息”组件 (my-component),一旦我切换父项 (*ngIf),它应该加载自己的数据(通过概览项的 ID)。如果我尝试您的方法,所有细节都会立即触发,从而导致数百次昂贵的 API 调用。我想我可以将my-component 更多地集成到my-wrapper 中,但这会使其不那么通用,这令人失望。【参考方案2】:

根据@nsinreal 的评论,我找到了答案。我觉得有点深奥,所以我想在这里发布它:

答案是使用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] 并不是真正需要的,它会隐藏孩子的“原始”模板,直到它决定加载完成。只要确保,不要把它放在*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>

我不确定,为什么它需要如此复杂的“变通办法”,而以前在 AngularJS 中很容易做到这一点。

【讨论】:

【参考方案3】:

这是因为 Ng 内容发生在构建时,当你传递内容时,它实际上并没有被 ngIf 指令删除或重新创建。它只是被移动并且组件被实例化。

【讨论】:

【参考方案4】:

我最近也遇到了这个问题,但选择了与当前接受的解决方案不同的解决方案。

解决方案 (TL;DR)

(解决方案适用于 AngularDart;我认为它在 Angular 中很相似)

使用结构指令;教程链接如下。

代替:

<my-wrapper>
  <my-contents></my-contents>
</my-wrapper>

你的用法变成:

<div *myWrapper>
  <my-contents></my-contents>
</div>

这是以下内容的简写(在 AngularDart 中;我认为 Angular 使用 &lt;ng-template&gt;

<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) 方法;您将失去创建 &lt;template&gt; 的星号简写,但您将获得组件的全部功能。

我的问题

我的团队拥有一个应用程序,该应用程序是一个更大的网络应用程序的一部分,其他应用程序由其他团队拥有。我们的组件假设它们可以注入一个MyAppConfiguration 对象,但这个对象只能在加载异步请求后才能注入。在我们的应用中,这不是问题:我们有一个“shell”组件,它在配置加载之前将所有内容隐藏在 ngIf 后面。

问题是当其他团队想要引用我们的组件时。我们不希望他们每次都重复“等待直到加载配置”逻辑,所以我尝试创建一个可以像这样使用的包装器组件:

<my-app-wrapper>
  <my-app-component></my-app-component>
</my-app-wrapper>

包装器注入一个服务对象并将其内容隐藏在ngIf 后面,直到服务说配置已加载。

就像问题海报一样,我发现ng-content 方法不能按预期工作:虽然内容正确地从 DOM 中隐藏了,但 Angular 仍然实例化导致依赖注入失败的组件。

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

【讨论】:

以上是关于当 ngIf 为 false 时构建 Angular4 ng-content的主要内容,如果未能解决你的问题,请参考以下文章

离子 - 当标签 <form> 中有 *ngIf 时无法获取离子输入的值

Angular 5 自定义指令触发包装到 *ngIf=false 中的元素

链接 ngIf 和动画转换

当 Angular 4 未在 NgIf 中呈现时,删除验证

当模板中有ngIf时如何识别Angular2组件是不是已完全加载(包括ViewChilds)

*ngIf-else 不显示 else 结果