Как вызвать функцию родственного компонента в маршрутизаторе-розетке и дождаться ее завершения в Angular

#angular #rxjs #observable

Вопрос:

В настоящее время я разрабатываю веб-приложение, состоящее из последовательности длинных форм. У меня есть глобальный компонент, шаблон которого выглядит следующим образом :

 <app-header></app-header>
<div class="main-container">
  <router-outlet></router-outlet>
</div>
<app-navigation></app-navigation>
 

Таким образом, у меня есть несколько страниц по очереди внутри розетки маршрутизатора и 2 компонента, которые не меняются, в частности компонент навигации, который состоит из кнопок, позволяющих перемещаться туда и обратно между различными экранами/формами.

Цель для меня состояла в том, чтобы иметь возможность вызывать функцию компонента страницы/формы внутри розетки маршрутизатора из компонента навигации для запуска проверки формы и обязательного вызова веб-службы, а затем запускать навигацию.

Я сделал это с помощью трех вещей: интерфейса, который должна реализовать каждая страница/формы и который выглядит следующим образом :

 export interface FormValidation {
  executeValidation();
}
let subscription;
export function validationEventSubscriber(action: Subject<any>, handler: () => void, off: boolean = false) {
  if (off amp;amp; subscription) {
    subscription.unsubscribe();
  } else {
    subscription = action.subscribe(() => handler());
  }
}
 

Служба для управления формами, содержащая это :

 export class FormsService {

  //...other properties...

  validationSubject = new Subject();

  constructor() { }

  executeValidation() {
    this.validationSubject.next();
  }
  
  //...other functions...
}
 

И сервис для управления навигацией, который вызывает Router.navigateByUrl в зависимости от различных бизнес-правил.

Затем в моем навигационном компоненте у меня есть метод, который вызывает executeValidation :

   //function called when clicking the button to go to the next page
  nextPage() {
    this.formsService.executeValidation();
  }
 

И, наконец, в компоненте «Страницы» у меня есть это :

   constructor(...) {
    this.executeValidation = this.executeValidation.bind(this);
    validationEventSubscriber(this.formsService.validationSubject, this.executeValidation);
  }

  executeValidation() {
    
    //...Validation...

    this.navigationService.nextPage();
  }
 

Этот рабочий процесс работает так, как ожидалось, я могу запустить executeValidation функцию страниц из компонента навигации, но чего я изо всех сил пытаюсь добиться, так это сделать проверку асинхронной.

Что меня здесь беспокоит, так это то, что я вызываю NavigationService внутреннюю часть страницы, и мне нужно делать это на каждой странице, которая копирует/вставляет одну и ту же строку кода в каждый executeValidation метод. Я бы предпочел сделать это изнутри NavigationComponent , но мне пришлось бы дождаться завершения проверки внутри компонента страницы, и вот с этим я борюсь.

Если у кого-нибудь есть идея о том, как решить эту проблему, я весь внимание !

Ответ №1:

У меня, как правило, чистая бизнес-логика ограничена услугами, а все, что связано с частью «просмотр», разделено на компоненты, включая навигацию через маршрутизатор.

Итак, если эта философия подходит вам, я бы поступил именно так.

Создайте службу myService , которая предоставляет следующие возможности

 private _validate$ = new Subject<bool>();
public validate$ = this._validate$.asObservable();
public executeValidation() {
  this._validate.next(true);
}
 

myService в вводится в app-navigation компонент и во все компоненты, которые загружаются в router-outlet .

Затем вы можете создать другой validationService , в котором вы определяете один или несколько методов проверки, которые вызывают веб-сервисы, которые фактически выполняют задание.

validationService выглядело бы так

 private _validationResult$ = new Subject<bool>();
public validationResult$ = this_validationResult$.asObservable();
// each form may have its own validation method
// the input of each validation method can be typed via an interface or let any as here
public validateFormX(input: any) {
   // assume validation is a post web service exposed by a remote server
   this.http.post("validationX_url", input).pipe(
     // when the response comes we notify it using _validationResult$
     tap({
      next: resp => this._validationResult$.next(resp),
      error: err => this._validationResult$.error(err)
     }) 
   )
}
 

Мы вводим validationService их во все компоненты, которые загружаются в router-outlet . Каждый из этих компонентов, таким образом, должен подписаться как на myService.validate$ это, так и validationService.validationResult$ на подобное

 // when myService.validate$ notifies, then we launch the validation logic
this.myService.validate$.subscribe(
  // assume each Component in the router-outlet is able to create the input for validation
  const input = buildValidationInput();
  () => this.validationService.validateFormX(input)
)

this.validationService.validationResult$.subscribe(
  validationResp => {
    // depending on the validation response navigate to the corresponding page
    this.router.navigate(....)
  }
)
 

Если вы будете следовать этому подходу, у вас будет преимущество в том, что большая часть логики в сервисах, которые вы можете протестировать, намного проще, чем компоненты.

Комментарии:

1. Выглядит многообещающе ! Мне нужно сделать еще кое-что, но я окончательно попробую ваше решение и отчитаюсь. Спасибо!