【问题标题】:Angular: Get reference to parent component when being content-projectedAngular:在内容投影时获取对父组件的引用
【发布时间】:2018-07-12 06:20:54
【问题描述】:

在我的情况下,我有一个组件,如果它位于特定组件中,它的行为应该不同。所以我想通过父母来找到正确类型的组件,在简单的情况下通过依赖注入可以很好地工作:

子组件

@Component({
    selector: 'spike-child',
    template: `<div>I am the child, trying to find my parent. 
        <button (click)="log()">Test</button></div>`
})
export class ChildComponent {
    // Get Parent through dependency injection; or null if not present.
    public constructor(@Optional() private parent: ParentComponent) { }

    public log(): void {
        console.log('Parent', this.parent);
        console.log('Parent.Id', this.parent && this.parent.id);
    }
}

父组件

@Component({
    selector: 'spike-parent',
    template: `
    <div>
        <div>I am the parent of the content below, ID = {{ id }}:</div>
        <ng-content></ng-content>
    </div>`
})
export class ParentComponent {
  @Input()
  public id: number;
}

使用,有效

<spike-parent [id]="1">
  <spike-child></spike-child>
</spike-parent>

不幸的是,如果我们通过这样的内容投影再添加一个间接性,这将不再起作用:

ProjectedContent 组件

@Component({
    selector: 'spike-projected-content',
    template: '<spike-parent [id]="2"><ng-content></ng-content></spike-parent>'
})
export class ProjectedContentComponent { }

使用,不起作用

  <spike-projected-content>
    <spike-child></spike-child>
  </spike-projected-content>

显然,在运行时子级将再次位于父级内部,但现在它总是将 null 作为注入参数。我知道投影的内容保留了原始上下文(包括注入器链),这几乎总是有帮助的,但是有什么方法可以解决这种情况吗?我阅读了有关 ngComponentOutlet 的信息,看起来它可能会有所帮助,但我并没有完全了解它是如何适应的。我也没有发现任何问题可以从这种情况中采取最后一步。我想我可以查询 DOM 来获得结果,但我显然想避免这种情况并为此使用 Angular 机制。

非常感谢!

【问题讨论】:

  • 你不能从父级传递一个属性吗?数据流方向相反的感觉很奇怪
  • 我对此进行了相当多的研究。写得很好的问题。问题基本上是spike-parent 不是spike-child 的父级 - 在组件结构方面。它在 DOM 中,但这只是因为 HTMLElement 可以插入到我们/Angular 选择的任何位置。我认为它们根本不能通过 DI 在父子中引用,因为它们不在父子结构中

标签: angular


【解决方案1】:

我只能看到两种方式(DOM hacking 除外):

  1. 切换到使用模板。门票仍然开放:14935。目前无法覆盖viewContainerRef.createEmbeddedView 中的注入器,但是可以选择传递上下文信息(例如context-parameter-in-angular,或使用NgTemplateOutlet)。
  2. 使用 DI 能够找到投影的子组件并调用它们的方法。每个子组件使用公共令牌{provide: Token, useExisting: forwardRef(() =&gt; Child)} 提供自己,它允许使用@ContentChildren(Token) 并获取所有投影子组件的列表,并调用任何方法/设置器。

【讨论】:

  • forwardRef 模式对我来说是新的。很高兴知道
  • 第一个要点看起来很有趣,不幸的是我现在没有时间研究这个。但是由于您的第二点已经改善了我的情况,我会接受您的回答,稍后再回到第一个想法 - 非常感谢!!
  • 如果你想让代码更通用,这是需要的,即所有孩子都可以实现通用接口,没有令牌(或基本抽象类)你不能编写通用 @ContentChildren 查询来查找所有支持的孩子.这种模式在 anguler 问题跟踪器的某处有所描述。
【解决方案2】:

从组件的角度来看,ChildComponentParentComponent 的兄弟,它们不是父子关系。这两个组件都有一个共享父 ProjectedContentComponent

在 DOM 中,它们的 HTMLElement 表示是父/子顺序,但这与组件结构是分开的。我们可以 detatch() 任何组件的 HTML 并将其放置在 DOM 中的任何位置 - 这就是 ng-content 正在做的事情

  1. 使用共享父级 (ProjectedContentComponent) 在兄弟级之间传递引用: StackBlitz Demo

【讨论】:

  • 非常感谢,这提供了一些新的想法。这对我来说仍然是一个权衡:通过检查 DOM,我没有那个中间组件 (ParentComponent) 来了解这些。不过,在我的上下文中,这样做可能是合理的。谢谢!
  • 第二个 stackblitz 链接是 404
【解决方案3】:

为了解决眼前的问题,我现在开始查询 DOM。这感觉真的很老套,我非常感谢其他建议,但我也想与任何有类似情况的人分享我的解决方案。

注意:我的解决方案只发现如果我的组件在某个其他组件内。它没有获得对该组件实例的引用,因为在我的情况下我不需要它。

给定以下函数:

private hasParent(element: ElementRef, selector: string): boolean {
    let parent = element.nativeElement;
    while(parent = parent.parentElement) {
        if (parent.matches(selector)) { return true; }
    }

    return false;
}

我们可以找出我们的组件是否在某个特定的其他组件中,如下所示:

public constructor(element: ElementRef) {
    const isChildOfSpikeParent = this.hasParent(element, 'spike-parent');

我也尝试过类似下面的方法来消除硬编码选择器,但它在 AOT 中效果不佳,所以我放弃了:

function getSelectorOfComponent(componentType: Function): string {
    // Reading annotations, see: https://stackoverflow.com/questions/46937746/how-to-retrieve-a-components-metadata-in-angular
    const decorators: any[] = (<any>componentType).__annotations__;
    const componentDecorator = decorators && decorators.find(d => d.ngMetadataName === 'Component');

    return componentDecorator && componentDecorator.selector;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-09
    • 2020-03-21
    • 1970-01-01
    • 2019-02-23
    • 2019-03-23
    • 1970-01-01
    • 2018-01-30
    • 2023-03-23
    相关资源
    最近更新 更多