简体   繁体   English

将@ViewChildren/@ContentChildren 与 router-outlet 结合使用

[英]Use @ViewChildren/@ContentChildren in combination with router-outlet

I am trying to build this :我正在尝试构建这个:

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

The list of links on the left depends on the page.左侧的链接列表取决于页面。

A typical page looks like this :典型的页面如下所示:

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

There are some directives appAnchor associated to @Input() anchorTitle: string .有一些指令appAnchor@Input() anchorTitle: string相关联。 I want to automagically capture them and update the left menu.我想自动捕获它们并更新左侧菜单。

The problem comes when I try to query those elements through the router-outlet .当我尝试通过router-outlet查询这些元素时,问题就出现了。

So I tried :所以我试过:

@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);
}

but I always get some empty QueryList :但我总是得到一些空的QueryList

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

I also tried :我也试过:

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

Finally, I tried to query the elements at the last moment with :最后,我尝试在最后时刻查询元素:

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

Note : I don't want the children component to send data to the parent component via a service because it causes some ExpressionChangedAfterItHasBeenCheckedError and is considered as a bad practice .注意:我不希望子组件通过服务将数据发送到父组件,因为它会导致一些ExpressionChangedAfterItHasBeenCheckedError并且被认为是一种不好的做法 I would really prefer to query the links directly from the parent component.我真的更喜欢直接从父组件查询链接。

EDIT : Here is a stackBlitz showing the problem.编辑:这是一个显示问题的stackBlitz If a @ViewChildren is used inside the component, everything works.如果在组件内部使用@ViewChildren ,则一切正常。 But if the @ViewChildren is used from the root component, it fails.但是如果从根组件使用@ViewChildren ,它就会失败。

There is an on ongoing discussion here about how ContentChildren works, and it is closely related to your problem.有一个正在进行的讨论,在这里了解如何ContentChildren工作,而且是密切相关的您的问题。

And in this comment it is explained that:并在此评论中解释说:

In Angular content projection and queries it means "content as it was written in a template" and not "content as visible in the DOM"在 Angular 内容投影和查询中,它的意思是“在模板中编写的内容”而不是“在 DOM 中可见的内容”

and

The consequence of this mental model is that Angular only looks at the nodes that are children in a template (and not children in the rendered DOM).这种心理模型的结果是 Angular 只查看模板中的子节点(而不是渲染 DOM 中的子节点)。

Similar to the issue above, components activated via router-outlet are not considered as ViewChildren or ContentChildren .与上述问题类似,通过router-outlet激活的组件不被视为ViewChildrenContentChildren They are just DOM children, and that doesn't mean anything to angular in terms of View and Content queries.它们只是 DOM 子级,这并不意味着在视图和内容查询方面有任何角度。 There is no way (as of today) that they an be queried with ViewChild or ViewChildren or ContentChild or ContentChildren没有办法(截至今天)使用ViewChildViewChildrenContentChildContentChildren查询它们

My suggestion to solve your issue is to use a combination of activate event and the component property of RouterOutlet to achieve the desired behavior.我解决您的问题的建议是结合使用activate事件和RouterOutletcomponent属性来实现所需的行为。 As such:像这样:

Define router-outlet as follows:定义 router-outlet 如下:

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

And use it as follows:并按如下方式使用它:

@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);
  })
}

Here is a working demo with improved typing: https://stackblitz.com/edit/angular-lopyp1这是一个改进打字的工作演示: https : //stackblitz.com/edit/angular-lopyp1

Also note that setTimeout is required because onActivate is fired during routing where the component life cycle hasn't started or finished yet.还要注意setTimeout是必需的,因为onActivate在组件生命周期尚未开始或结束的路由期间被触发。 setTimeout ensures that the component lifecycle has completed, and that the component and underlying queries are ready as well. setTimeout确保组件生命周期已完成,并且组件和底层查询也已准备就绪。

The (activate)="handleActivate($event)" option won't work, as the router-outlet elements won't have been initialized yet. (activate)="handleActivate($event)"选项将不起作用,因为router-outlet元素尚未初始化。 The $event in that case is indeed the component instance, but it's not really helpful here这种情况下的$event确实是组件实例,但在这里并没有真正的帮助

The ViewChildren , ContentChildren don't seem to work for router-outlet . ViewChildrenContentChildren似乎不适用于router-outlet I didn't think they would, but tested it a good bit with your StackBlitz demo我不认为他们会,但是使用您的 StackBlitz 演示对其进行了很好的测试

You'll have to use a service, which is the standard way of doing it, and by far the most flexible.您必须使用服务,这是标准的做法,也是迄今为止最灵活的方式。 You will be able to get around the ExpressionChangedAfterItHasBeenCheckedError with ChangeDetectorRef.detectChanges(), or better still, use a BehaviorSubject, and next the values from that.您将可以绕过ExpressionChangedAfterItHasBeenCheckedError与ChangeDetectorRef.detectChanges(),或者更好的是,使用BehaviorSubject,以及next从这些值。 In your template subscribe using async pipe, and you won't get those errors在您的模板中使用async管道订阅,您将不会收到这些错误

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM