Как реализовать защиту CanDeactivate для компонента Angular, загруженного ViewContainerRef?

Я разработал свою страницу Angular с кнопками, которые ведут к тому же компоненту, который называется ParentComponent. Каждая из этих кнопок передает разные параметры запроса. У ParentComponent есть ViewContainerRef, который программно загружает дочерний компонент в зависимости от параметра запроса, передаваемого кнопками. Например, если в качестве параметра передан «cmp1», будет загружен Child1Component. Если передан «cmp2», он загрузится Child2Component. Я определил это отображение в классе констант ComponentModels.

Теперь у каждого из дочерних компонентов есть состояние IsDirty (и другие внутренние условия), которое, если оно истинно, должно отображать диалоговое окно подтверждения, когда пользователь пытается покинуть страницу или уйти со страницы. В принципе, этого механизма можно добиться, если реализовать CanDeactivate охрану, но я не знаю, где ее разместить.

Мои вопросы:

  1. Какой компонент должен реализовать защиту CanDeactivate? Это должно быть в ParentComponent или в дочерних компонентах? Насколько я понимаю, CanDeactivate работает только для тех компонентов, которые определены в модуле маршрутизации. В моем случае в модуле маршрутизатора определен только ParentComponent.
  2. Если ParentComponent реализует защиту, как он сможет получить IsDirty состояние загруженного дочернего компонента? Каждый дочерний компонент имеет разные условия, позволяющие решить, действительно ли его можно деактивировать.

Я новичок в Angular, поэтому не уверен, сложен ли мой дизайн или неправильный. Любые материалы или советы будут высоко оценены. Спасибо!

MainComponent.html

<p>This is the Main Page</p>
<button (click) = "openChild1()">Open Child 1</button>
<button (click) = "openChild2()">Open Child 2</button>

MainComponent.ts

export class MainComponent {
  constructor(private router: Router) {}

  openChild1() {
    this.router.navigate(['child', 'cmp1']);
  }

  openChild2() {
    this.router.navigate(['child', 'cmp2']);
  }
}

AppRouting.module.ts

import { MainComponent } from './components/main/main.component';
import { ParentComponent } from './components/parent/parent.component';

const routes: Routes = [
  { path: '', component: MainComponent },
  { path: 'child/:id', component: ParentComponent, canDeactivate: [CanDeactivateGuard]},
];

@NgModule({
  imports: [CommonModule],
  declarations: [],
})
export class AppRoutingModule {}

РодительскийКомпонент.html

<h1>Parent View</h1>
<div #container></div>

РодительскийКомпонент.ts

import { Component, OnInit } from '@angular/core';
import { ChildComponents } from '../../models/child-components';

@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css'],
})
export class ParentComponent implements OnInit,  {
  @ViewChild('container', { read: ViewContainerRef })
  container!: ViewContainerRef;

  constructor(
    private readonly route: ActivatedRoute,
    private readonly router: Router
  ) {}

  ngAfterViewInit() {
    this.route.paramMap.subscribe((params) => {
      let id = params.get('id');
      if (id) {
        this.loadComponent(ChildComponents[id]);
      }
    });
  }

  loadComponent(component: any) {
    this.container.createComponent(component);
  }

  canDeactivate(): boolean | Promise<boolean> {
    // should ParentComponent implement canDeactivate?
    // how can it get the IsDirty state of the loaded child component?

    return true;
  }
}

Компонент-Models.ts

import { Child1Component } from '../components/child1/child1.component';
import { Child2Component } from '../components/child2/child2.component';

export const ComponentModels: { [key: string]: any } = {
  cmp1: Child1Component,
  cmp2: Child2Component,
};

CanDeactivate.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

export interface CanComponentDeactivate {
  canDeactivate: () => boolean | Promise<boolean>;
}

@Injectable({
  providedIn: 'root'
})

export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(
    component: CanComponentDeactivate,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

Child1Component.ts

export class Child1Component implements CanComponentDeactivate {
  isDirty: boolean;

  constructor() {
  }

  canDeactivate(): boolean | Promise<boolean> {
    //is this correct?
    return this.isDirty === true;
  }
}

Child2Component.ts

export class Child1Component implements CanComponentDeactivate {
  isDirty: boolean;

  constructor() {
  }

  canDeactivate(): boolean | Promise<boolean> {
    //is this correct?
    return this.isDirty === true && conditionX && conditionY;
  }
}
Тестирование функциональных 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
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Мы получим доступ к экземпляру при запуске компонента create, поэтому вы можете просто получить доступ к функции canDeactivate из экземпляра и проверить грязное состояние.

Родитель — это место, где вызывается canDeactivate, поскольку это компонент, указанный в маршрутизации:

import { Component, OnInit } from '@angular/core';
import { ChildComponents } from '../../models/child-components';

@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css'],
})
export class ParentComponent implements OnInit,  {
  @ViewChild('container', { read: ViewContainerRef })
  container!: ViewContainerRef;
  componentRef: ComponentRef<any>; // <- changed here!

  constructor(
    private readonly route: ActivatedRoute,
    private readonly router: Router
  ) {}

  ngAfterViewInit() {
    this.route.paramMap.subscribe((params) => {
      let id = params.get('id');
      if (id) {
        this.loadComponent(ChildComponents[id]);
      }
    });
  }

  loadComponent(component: any) {
    this.componentRef = this.container.createComponent(component); // <- changed here!
  }

  canDeactivate(): boolean | Promise<boolean> {
    return this.componentRef?.instance?.canDeactivate(); // <- changed here!
  }
}

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