ExpressionChangedAfterItHasBeenCheckedError Angular - PullRequest
1 голос
/ 03 августа 2020

Я интегрирую индикатор загрузки для своего проекта, видимость которого будет определяться централизованно в службе HTTP Interceptor. Однако после реализации я получил следующую ошибку:

 ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'null'. Current value: 'true'.

Ниже приведены мои урезанные файлы:

app.component.ts

import { SpinnerService } from '@app/services/spinner.service';
import { Subject } from 'rxjs';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})

export class AppComponent implements OnDestroy {
    title = 'My App';
    isLoading: Subject<boolean> = this.spinnerService.isLoading;

    constructor(public spinnerService: SpinnerService) {
    }

}

app.component . html

<div class="example-container" [class.example-is-mobile]="mobileQuery.matches">
    <router-outlet></router-outlet>
    <div *ngIf="isLoading | async" class="overlay">
        Loading
    </div>
</div>

spinner.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable()

export class SpinnerService {

    isLoading = new Subject<boolean>();

    constructor() {
    }

    show() {
        this.isLoading.next(true);
    }

    hide() {
        this.isLoading.next(false);
    }
}

interceptor.service.ts

import { Injectable } from '@angular/core';
import {HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse} from '@angular/common/http';
import {EMPTY, Observable, throwError} from 'rxjs';

import { environment } from '@env/environment';
import { finalize } from 'rxjs/operators';
import { SpinnerService } from '@app/services/spinner.service';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {

    // snackBar: MatSnackBar;

    constructor(public spinnerService: SpinnerService) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this.spinnerService.show();

        return next.handle(request).pipe(
            finalize(() => this.spinnerService.hide()),
        );
    }
}

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

        this.http.get(`${environment.apiUrl}/form/schema/days`)
            .subscribe(data => {
                console.log('done', data);
            });

Я не могу понять, что я здесь делаю неправильно, может ли кто-нибудь пролить свет на это?

Ответы [ 2 ]

2 голосов
/ 03 августа 2020

Ваше состояние загрузки должно быть инициализировано (по умолчанию false).

Поскольку Субъект не имеет доступа к значению при инициализации, вы можете изменить его на BehaviorSubject:

isLoading = new BehaviorSubject<boolean>(false);
1 голос
/ 03 августа 2020

В идеале вы должны вызывать источники данных внутри ловушки NgOnInit, чтобы предотвратить эту проблему. Способ решить эту проблему - сделать изменения значения загрузчика вне текущего цикла инициализации компонента с использованием оператора задержки RX JS:

isLoadingSubject = = new Subject<boolean>();
isLoading: Observable<boolean> = this.isLoadingSubject.pipe( delay(0) );

Я думаю, что это уместное чтение: https://blog.angular-university.io/angular-debugging/

Обновлено, чтобы немного распаковать во второй строке кода:

Angular обнаружение изменений функций - это процесс, который запускается:

  • события ввода (щелчок, наведение курсора мыши, нажатие клавиши и т. Д. c.)
  • Ajax запросы
  • setTimeout () и setInterval ()

Затем он проходит через дерево компонентов приложения, проверяя все привязки данных (в соответствии со стратегией CD et c).

Вы видите ошибку, когда в одном цикле обнаружения изменений Angular обнаруживает, что значение привязки изменилось уже ПОСЛЕ того, как для этой привязки была проведена проверка изменений, что вызвало проблему для Angular, поскольку он не сможет обновить шаблон соответствующим образом.

Таким образом, чтобы исправить такую ​​проблему, мы могли либо обеспечить изменение происходит до начала указанного c цикла CD (иногда мы можем добиться этого, переместив действие по изменению данных в другой обработчик жизненного цикла компонента) или отложив его - убедившись, что изменение будет выполнено в следующем цикле.

* Задержка 1026 * RX JS под капотом использует setTimeout (), который, как упоминалось выше, запускает другой собственный цикл CD. Subject - это Observable, который может многоадресно передавать нескольким Observers. Таким образом, мы перенаправляем его, чтобы включить «задержку», и, таким образом, мы резервируем цикл CD для этого c изменения.

Так что, если бы мы были педанти c - это скорее обходной путь, который работает. Но за счет другого цикла компакт-дисков, который мы здесь запускаем.

...