将@ ViewChildren / @ ContentChildren与路由器插座结合使用

时间:2019-07-09 14:53:31

标签: angular typescript

我正在尝试构建它:

┌--------------------------------------------┐
| -Page 1-   -Page 2-   -Page 3-             |
├----------┬---------------------------------┤
| Link 1   | <router-outlet></router-outlet> |
| Link 2   |                                 |
| Link 3   |                                 |
|          |                                 |
|          |                                 |

左侧的链接列表取决于页面。

典型页面如下:

<div>
  <app-component-1 appAnchor anchorTitle="Link 1"></app-component-1>
  <app-component-2 appAnchor anchorTitle="Link 2"></app-component-2>
  <app-component-3 appAnchor anchorTitle="Link 3"></app-component-3>
</div>

有一些与appAnchor相关的指令@Input() anchorTitle: string。我想自动捕捉它们并更新左侧菜单。

当我尝试通过router-outlet查询这些元素时,就会出现问题。

所以我尝试了:

@ViewChildren(AnchorDirective)
viewChildren: QueryList<AnchorDirective>;

@ContentChildren(AnchorDirective)
contentChildren: QueryList<AnchorDirective>;

ngAfterContentInit(): void {
  console.log(`AppComponent[l.81] contentChildren`, this.contentChildren);
}

ngAfterViewInit(): void {
  console.log(`AppComponent[l.85] viewChildren`, this.viewChildren);
}

但是我总是得到一些空的QueryList

  

QueryList {dirty:false,_results:Array(0),changes:EventEmitter,length:0,last:undefined,...}

我也尝试过:

@ContentChildren(AnchorDirective, {descendants: true})
@ContentChildren(AnchorDirective, {descendants: false})

最后,我尝试在最后一刻使用以下元素查询元素:

<router-outlet (activate)="foo()"></router-outlet>

注意:我不希望子组件通过服务将数据发送到父组件,因为它会导致某些ExpressionChangedAfterItHasBeenCheckedErroris considered as a bad practice。我真的更希望直接从父组件查询链接。

编辑:这是显示问题的stackBlitz。如果在组件内部使用@ViewChildren,则一切正常。但是,如果从根组件使用@ViewChildren,它将失败。

2 个答案:

答案 0 :(得分:2)

(activate)="handleActivate($event)"选项将不起作用,因为router-outlet元素尚未初始化。在这种情况下,$event确实是组件实例,但是在这里并没有帮助

ViewChildrenContentChildren似乎不适用于router-outlet。我不认为他们会这么做,但是在您的StackBlitz演示中对其进行了很好的测试

您将不得不使用服务,这是一种标准的服务方式,并且是迄今为止最灵活的。您将可以使用ChangeDetectorRef.detectChanges()解决ExpressionChangedAfterItHasBeenCheckedError,或者更好的是,使用BehaviorSubject,然后next的值。在您的模板中,使用async管道进行订阅,您不会收到这些错误

答案 1 :(得分:2)

关于ContentChildren的工作方式正在进行here的讨论,它与您的问题密切相关。

并且在this comment中对此进行了解释;

  

在Angular内容投影和查询中,它表示“原样的内容   写在模板中”而不是“在DOM中可见的内容”

  

这种心理模型的结果是Angular只看   模板中的子节点(而不是   呈现的DOM)。

类似于上述问题;通过router-outlet激活的组件不会被视为ViewChildrenContentChildren,它们只是DOM子级,对于View和Content查询而言,这没有任何意义。 (截至今天)无法用ViewChildViewChildrenContentChildContentChildren

来查询它们

我建议您解决问题的方法是结合使用activate事件和RouterOutletcomponent属性来实现所需的行为。这样的

如下定义路由器出口;

<router-outlet #myRouterOutlet="outlet" (activate)='onActivate()'></router-outlet>

并按如下所述使用它;

@ViewChild("myRouterOutlet", { static: true }) routerOutlet: RouterOutlet;
nbViewChildren: number;
links = [];

onActivate(): void {
  setTimeout(() => {
    const ancList: QueryList<AnchorDirective> = (this.routerOutlet.component as any).children;

    this.nbViewChildren = ancList.length;
    this.links = ancList.map(anc => anc.anchorTitle);
  })
}

这是一个工作示例,具有改进的https://stackblitz.com/edit/angular-lopyp1

输入

还请注意,setTimeout是必需的,因为onActivate在组件生命周期尚未开始或结束的路由过程中被触发。 setTimeout确保组件生命周期已完成以及组件和基础查询已准备就绪。