Angular: почему мой компонент приложения не обнаруживает наблюдаемые изменения, вызванные моим обработчиком ошибок?

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

//error-service.ts

@Injectable({
  providedIn: 'root',
})
export class ErrorService implements ErrorHandler {
  error = new Subject<Error>();

  handleError(error: any): void {
    console.error('ERROR', error);
    this.error.next(error);
  }
}

Этот ErrorService зарегистрирован как глобальный обработчик ошибок для модуля приложения.

//app.module.ts

@NgModule({
  providers: [{ provide: ErrorHandler, useClass: ErrorService }],
  //...
})
export class AppModule {}

ErrorService внедряется в компонент приложения.

//app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  error = this.errorService.error;

  constructor(private errorService: ErrorService) {}
}

Шаблон компонента приложения отображает страницу ошибки, если есть ошибка, в противном случае отображает выход маршрутизатора.

//app.component.html

<app-error-page *ngIf = "error | async"></app-error-page>
<router-outlet *ngIf = "!(error | async)"></router-outlet>

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

Если я отправлю новую ошибку в тему ErrorService.error из другого компонента, а не из глобального обработчика ошибок (ErrorService.handleError()), тогда это сработает — компонент приложения повторно отображает свой шаблон и показывает страницу ошибки.

Попробуйте использовать объект behaviorSubject или replaySubject вместо обычной темы.

MikeOne 14.04.2023 19:58
Тестирование функциональных 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
0
1
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

У вас есть два разных экземпляра ErrorService в вашем приложении.

  • Эта строка providers: [{ provide: ErrorHandler, useClass: ErrorService }], настраивает поставщика в AppModule с именем зависимости ErrorHandler, который использует класс ErrorService для создания этой зависимости.

  • А вот эта другая строчка в сервисе @Injectable({ providedIn: 'root'}), чем-то похожа на добавление другого провайдера в rootModule с этим конфигом { provide: ErrorService , useClass: ErrorService }

Таким образом, вы получаете 2 провайдера в вашем rootModule, которые используют класс ErrorService, каждый с другим именем InjectionToken или зависимостью.

providers: [
  { provide: ErrorHandler, useClass: ErrorService },
  { provide: ErrorService , useClass: ErrorService }
],
  • Когда приложение инициализируется и ищет зависимость с именем ErrorHandler, оно выбирает первого провайдера из списка, создает экземпляр ErrorService и использует его в качестве основного ErrorHandler приложения.
  • Но когда вы вводите ErrorService в конструктор вашего AppComponent, вы запрашиваете зависимость, настроенную с этим именем. Таким образом, система DI в этом случае выберет второго поставщика и внедрит другой экземпляр ErrorService.

IMO для вашего случая самое простое решение — вручную добавить второго провайдера в AppModule, который повторно предоставляет существующий ErrorHandler под псевдонимом ErrorService.

providers: [
  { provide: ErrorHandler, useClass: ErrorService },
  { provide: ErrorService, useExisting: ErrorHandler }, // <---
],

Таким образом, когда какой-либо элемент запрашивает ErrorService, этот провайдер вернет уже созданный экземпляр службы для ErrorHandler.

В этом случае я также уберу { providedIn: 'root' } в сервисе, чтобы лучше отразить поведение, ожидаемое от сервиса.

Ваше здоровье

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

Первая проблема с рассматриваемым кодом заключается в том, что

providers: [{ provide: ErrorHandler, useClass: ErrorService }],

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

Однако этой проблемы на самом деле не существовало в моем исходном коде — я представил эту проблему при упрощении кода, чтобы задать этот вопрос.

Строка выше должна быть изменена на

providers: [{ provide: ErrorHandler, useExisting: ErrorService }],

так что реализация ErrorHandler использует тот же ErrorService, который уже зарегистрирован как сервис.

Даже с этим исправлением оно не работает — подписка на Subject<Error> в компоненте приложения получает новые ошибки, но представление не перерисовывается, поскольку Angular не обнаруживает, что что-то изменилось. Поэтому мне пришлось явно сообщить Angular об изменениях, используя NgZone.run().

//app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  error?: Error;

  constructor(
    private errorService: ErrorService,
    private changeDetectorRef: ChangeDetectorRef,
    private ngZone: NgZone
  ) {}

  ngOnInit(): void {
    this.errorService.error.subscribe((error) => {
      this.error = error;
      this.ngZone.run(() => {
        this.changeDetectorRef.detectChanges();
      });
    });
  }
}

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