В моем приложении Angular у меня есть розетка маршрутизатора, а сбоку у меня есть несколько контейнеров предварительного просмотра, показывающие компоненты предварительного просмотра, куда направляется соответствующий основной компонент и который отображается в розетке маршрутизатора при нажатии на эти поля, в то время как затем отображается предыдущий основной компонент. загружен в теперь уже бесплатный контейнер предварительного просмотра.
По сути, он работает так, как задумано, но у меня есть один особый случай, когда я нажимаю кнопку приглашения члена семьи, тогда в представлении должен появиться #leftThirdContainer (для этого используется *ngIf) с соответствующим компонентом предварительного просмотра, показанным в leftThirdContainer. Я делаю это с помощью Observable, который проверяет текущий маршрут, и всякий раз, когда он соответствует «/private/invite», это должно быть правдой. Но кажется, что даже когда я жду запуска события маршрутизатора, leftThirdContainer не определен, и поэтому компонент не отображается в представлении. Только при обновлении компонент отображается правильно в leftThirdContainer.
вот мой HTML:
<button mat-flat-button color = "primary" (click) = "inviteFamilyMember()" class = "invite-button" *ngIf = "!(showThirdPreviewContainer$ | async)">invite</button>
<div class = "container">
<div class = "column column-1">
<div class = "row row-1" (click) = "loadComponents('leftFirst')">
<ng-container #leftFirstContainer></ng-container>
</div>
<div class = "row row-2" (click) = "loadComponents('leftSecond')">
<ng-container #leftSecondContainer></ng-container>
</div>
<div class = "row row-3" (click) = "loadComponents('leftThird')" *ngIf = "showThirdPreviewContainer$ | async">
<ng-container #leftThirdContainer></ng-container>
</div>
</div>
<div class = "column column-2">
<router-outlet></router-outlet>
</div>
</div>
и вот класс машинописного текста:
export class PrivateComponent implements AfterViewInit {
@ViewChild('leftFirstContainer', { read: ViewContainerRef }) leftFirstContainer: ViewContainerRef;
@ViewChild('leftSecondContainer', { read: ViewContainerRef }) leftSecondContainer: ViewContainerRef;
@ViewChild('leftThirdContainer', { read: ViewContainerRef }) leftThirdContainer: ViewContainerRef;
showThirdPreviewContainer$: Observable<boolean>;
private destroyRef = inject(DestroyRef);
constructor(
private privateComponentService: PrivateComponentSerivce,
private router: Router,
) {
this.showThirdPreviewContainer$ = this.router.events.pipe(
filter(event => event instanceof NavigationStart || event instanceof NavigationEnd),
map((event: NavigationStart | NavigationEnd) => event.url === '/private/invite'),
startWith(this.router.url === '/private/invite'),
takeUntilDestroyed(this.destroyRef)
);
}
ngAfterViewInit() {
if (this.privateComponentService.getRefreshTriggered()) {
this.privateComponentService.setRefreshTriggered(false);
this.loadPreviewComponents(false);
} else {
this.initComponentContainer()
}
}
loadComponents(containerType: ContainerTypes): void {
const currentComponentContainer = this.privateComponentService.getCurrentComponentContainer();
const newComponentContainer = this.privateComponentService.mapContainerComponents(containerType, currentComponentContainer);
if (newComponentContainer?.main) {
this.router.navigate([newComponentContainer.main]).then(() => this.setPreviewComponents(newComponentContainer));
}
}
inviteFamilyMember(): void {
this.router.navigate([MainContainerRoutes.Invite]).then(() => {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
takeUntilDestroyed(this.destroyRef)
).subscribe((event) => {
this.loadPreviewComponents(true);
});
});
}
private initComponentContainer() {
const currentComponentContainer = this.privateComponentService.getCurrentComponentContainer();
this.router.navigate([currentComponentContainer.main]).then(() => this.setPreviewComponents(currentComponentContainer));
}
private loadPreviewComponents(privateInviteClicked: boolean) {
const currentComponentContainer = this.privateComponentService.getCurrentComponentContainer();
if (privateInviteClicked) {
const newComponentContainer = this.privateComponentService.mapContainerForInviteComponent(currentComponentContainer);
this.setPreviewComponents(newComponentContainer);
} else {
this.setPreviewComponents(currentComponentContainer);
}
}
private setPreviewComponents(componentContainer: ContainerComponents) {
if (this.leftFirstContainer) this.leftFirstContainer.clear();
if (this.leftSecondContainer) this.leftSecondContainer.clear();
if (this.leftThirdContainer) this.leftThirdContainer.clear();
if (componentContainer?.leftFirst && componentContainer?.leftSecond && this.leftFirstContainer && this.leftSecondContainer) {
const leftFirstComponentRef = this.leftFirstContainer.createComponent(COMPONENTS[componentContainer.leftFirst]);
const leftSecondComponentRef = this.leftSecondContainer.createComponent(COMPONENTS[componentContainer.leftSecond]);
if (this.leftThirdContainer && componentContainer?.leftThird) {
const leftThirdComponentRef = this.leftThirdContainer.createComponent(COMPONENTS[componentContainer.leftThird]);
}
this.privateComponentService.setCurrentComponentContainer(componentContainer);
}
}
}
Я знаю, что могу дождаться загрузки компонента с помощью setTimeout (это сработает), но это кажется неправильным. Поэтому я хочу знать, что-то не так с моим кодом и пониманием Angular и почему он не работает должным образом. Или есть другой способ Angular добиться того, чего я хочу (динамически загружать и условно показывать компоненты)?
Я регулярно занимаюсь разработкой Angular и могу показать, как бы я это решил. Работа с @ViewChild может быть необходима в некоторых случаях, но на первый взгляд я не вижу здесь необходимости. Скажи мне, когда я ошибаюсь!
Я не могу сказать, почему это не работает. Вы можете создать stackblitz, чтобы я мог погрузиться в него глубже — если вы хотите придерживаться своей текущей реализации.
Мой подход немного другой, более «родной» угловой, без ViewChild.
Вот мой псевдокод. Примечание. Мне нравится использовать новые операторы условных шаблонов, такие как @if. (Это всего лишь синтаксический сахар. Вы также можете использовать широко используемую директиву *ngIf="something".)
родительский-компонент.ts
export class ParentComponent {
protected showThirdPreviewContainer$: Observable<boolean> = of(true).pipe();
protected container: Map = {
leftFirst: undefined,
leftSecond: undefined,
leftThird: undefined,
};
private privateComponentService = inject(PrivateComponentService);
loadComponents(containerKey: string): void {
this.container[containerKey] =
this.privateComponentService.getCurrentComponentContainer(containerKey);
}
}
родительский-компонент.html
<!-- ✅ list all boxes her, and put dynamic loader in them -->
@if (container['leftFirst']; as leftFirst) {
<div><b>click again!</b></div>
<!-- ✅ here the magic happens! -->
<div>
loaded:
<app-dynamic-loader [type] = "leftFirst"></app-dynamic-loader>
</div>
} @else { click me for random component }
</div>
<div
class = "row row-2"
(click) = "loadComponents('leftSecond')"
style = "width:150px;height:100px;background-color:lightblue;"
>
@if (container['leftSecond']; as leftSecond) {
<div><b>click again!</b></div>
<div>
loaded:
<app-dynamic-loader [type] = "leftSecond"></app-dynamic-loader>
</div>
} @else { click me for random component }
</div>
<!-- etc. -->
динамический-container.comComponent.ts
export class DynamicComponent { // ✅ <-- this does the job!
@Input
type: ContainerType;
}
динамический-загрузчик.компонент.html
@if (typeStr() === "ComponentA") {
<app-component-a></app-component-a>
} @else if (typeStr() === "ComponentB") {
<app-component-b></app-component-b>
} @else if (typeStr() === "ComponentC") {
<app-component-c></app-component-c>
} @else if (typeStr() === "ComponentD") {
<app-component-d></app-component-d>
} @else if (typeStr() === "ComponentE") {
<app-component-e></app-component-e>
} @else {
<p style = "color:red;">invalid component-type: {{ typeStr() }}</p>
}
Если мой ответ — то, что вы ищете, я был бы рад, если бы вы его приняли. Если нет, дайте мне знать.
Демо-версия Stackblitz (это весело!)
«Или есть другой способ Angular добиться того, чего я хочу (динамически загружать и условно показывать компоненты)?». Вот что делает мой ответ.
да, ты прав в этом. Думаю, я принимаю ваш ответ и благодарю вас за ваши усилия.
спасибо за идею, я изменил ее на простой родительский-дочерний компонент и условно показывал компоненты. Но все же я хотел знать, почему моя ссылка на представление остается неопределенной, когда я пытаюсь создать компонент, просто для моего понимания Angular и того, что происходит здесь в фоновом режиме.