Используйте @ ViewChildren / @ ContentChildren в сочетании с роутером - PullRequest
3 голосов
/ 09 июля 2019

Я пытаюсь построить это:

┌--------------------------------------------┐
| -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), изменения: EventEmitter, длина: 0, последняя: не определено,…}

Я также попытался:

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

Наконец, я попытался в последний момент запросить элементы с помощью:

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

Примечание: я нехотите, чтобы дочерний компонент отправлял данные в родительский компонент через службу, потому что это вызывает некоторые ExpressionChangedAfterItHasBeenCheckedError, а считается плохой практикой .Я действительно предпочел бы запрашивать ссылки непосредственно из родительского компонента.

РЕДАКТИРОВАТЬ: Вот stackBlitz , показывающий проблему.Если внутри компонента используется @ViewChildren, все работает.Но если @ViewChildren используется из корневого компонента, он завершается ошибкой.

Ответы [ 2 ]

2 голосов
/ 10 июля 2019

идет постоянное обсуждение здесь о том, как ContentChildren работает, и оно тесно связано с вашей проблемой.

и в этом комментарии объясняется, что;

В угловой проекции контента и запросах это означает «контент как он был» записано в шаблоне "а не" в содержимом, как видно в DOM "

и

Следствием этой ментальной модели является то, что Angular смотрит только на узлы, которые являются дочерними в шаблоне (а не дочерними в предоставил DOM).

аналогично вопросу выше; Компоненты, активированные через router-outlet, не считаются ViewChildren или ContentChildren, они просто дочерние элементы DOM, и это ничего не значит с точки зрения запросов View и Content. На сегодняшний день нет никакого способа, чтобы их можно было запросить с помощью ViewChild или ViewChildren или ContentChild или ContentChildren

Мое предложение для решения вашей проблемы - использовать комбинацию события activate и свойства component RouterOutlet для достижения желаемого поведения. такой, что;

определить роутер-розетку следующим образом;

<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 гарантирует, что жизненный цикл компонента завершен, а компонент и базовые запросы готовы.

2 голосов
/ 09 июля 2019

Параметр (activate)="handleActivate($event)" не будет работать, так как элементы router-outlet еще не будут инициализированы.$event в этом случае действительно является экземпляром компонента, но здесь он не очень полезен

ViewChildren, ContentChildren, похоже, не работают для router-outlet.Я не думал, что они это сделают, но проверил это хорошо с вашей демонстрацией StackBlitz

Вам придется использовать сервис, который является стандартным способом и, безусловно, наиболее гибким.Вы сможете обойти ExpressionChangedAfterItHasBeenCheckedError с помощью ChangeDetectorRef.detectChanges () или, что еще лучше, использовать BehaviorSubject и next значения из этого.В вашем шаблоне подпишитесь, используя async pipe, и вы не получите этих ошибок

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...