Динамически загружать и условно показывать компонент в Angular

В моем приложении 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 добиться того, чего я хочу (динамически загружать и условно показывать компоненты)?

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
0
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я регулярно занимаюсь разработкой 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 и того, что происходит здесь в фоновом режиме.

Mike Twain 10.07.2024 08:22

«Или есть другой способ Angular добиться того, чего я хочу (динамически загружать и условно показывать компоненты)?». Вот что делает мой ответ.

jaheraho 10.07.2024 11:06

да, ты прав в этом. Думаю, я принимаю ваш ответ и благодарю вас за ваши усилия.

Mike Twain 10.07.2024 12:08

Другие вопросы по теме