Проект построен в Angular Verison:17. Мы создали loading.comComponent.ts и loader.service.ts и привязали их в app.comComponent.html и в файле Progress.interceptor.ts, как вы можете видеть в коде. Загрузчик работал хорошо в версии Angular 14, но после обновления до Angular 17 он перестал работать. А Angular версии 17 был обновлен автономным компонентом, и мы обновили код согласно версии 17. Загрузчик должен работать при каждой навигации по маршрутизатору и вызове API.
Также появляется ошибка «Невозможно прочитать свойства неопределенного значения (чтение «применить»)» после каждого вызова API.
loading.comComponent.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { LoaderService } from '../../service';
import { Subscription } from 'rxjs';
import { LoaderState } from '../../models/loader.model'
import { CommonModule } from '@angular/common';
@Component({
selector: 'loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.css'],
standalone: true,
imports: [CommonModule],
providers:[LoaderService]
})
export class LoadingComponent implements OnInit, OnDestroy {
show = false;
subscription: Subscription;
constructor(private loaderSvc: LoaderService) { }
ngOnInit() {
this.subscription = this.loaderSvc.loaderState.subscribe((state: LoaderState) => {
this.show = state.show; //setTimeout(() => this.show = state.show);
})
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
loading.comComponent.html
<div *ngIf = "show">
<div class = "loader-overlay" style = "position: relative; top: 45vh;">
<div class = "spinner">
<div class = "bounce1"></div>
<div class = "bounce2"></div>
<div class = "bounce3"></div>
</div>
</div>
app.comComponent.html
<loading></loading>
<router-outlet></router-outlet>
app.comComponent.ts
import { AfterViewInit, Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd, RouterModule } from '@angular/router';
import { LoadingComponent } from './shared/components';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
standalone: true,
imports: [RouterModule, NgIdleKeepaliveModule, LoadingComponent],
providers: [{ provide: WINDOW, useValue: {} }]
constructor(public loaderSVC: LoaderService){}
ngOnInit() {
this.router.events.subscribe((evt) => {
this.loaderSVC.startLoading();
// console.info("start")
if (!(evt instanceof NavigationEnd)) {
return;
}
setTimeout(() => {
this.loaderSVC.stopLoading();
// console.info("stop")
});
})
loader.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { LoaderState } from '../models/loader.model';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
private loaderSubject = new Subject<LoaderState>();
loaderState = this.loaderSubject.asObservable();
constructor() { }
startLoading() {
this.loaderSubject.next(<LoaderState>{show: true});
}
stopLoading() {
this.loaderSubject.next(<LoaderState>{show: false});
}
}
Progress.interceptor.ts
import {Injectable} from '@angular/core';
import {HttpRequest, HttpHandler, HttpEvent, HttpInterceptor} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/finally';
import { LoaderService } from '../service';
@Injectable()
export class ProgressInterceptor implements HttpInterceptor {
constructor(private loaderSvc: LoaderService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (request.params.has('ignoreLoadingBar')) {
return next.handle(request.clone({ headers: request.headers.delete('ignoreLoadingBar') }));
}
this.loaderSvc.startLoading();
return next.handle(request).finally(() => this.loaderSvc.stopLoading());
}
}
main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import './icons';
import { environment } from './environments/environment';
import { AppComponent } from '@app/app.component';
import { appConfig } from '@app/app.config.server';
import { bootstrapApplication } from '@angular/platform-browser';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
app.config.server.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './routes/routes';
import { provideClientHydration, withHttpTransferCacheOptions } from '@angular/platform-browser';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';
import { provideAnimations } from '@angular/platform-browser/animations';
import { CookieService } from 'ngx-cookie-service';
import { provideNgxMask } from 'ngx-mask';
import { provideToastr } from 'ngx-toastr';
import { ProgressInterceptor, BasicAuthInterceptor } from './shared/helpers';
import { StorageService } from './shared/service';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideClientHydration(withHttpTransferCacheOptions({
includePostRequests: true
})), provideNgxMask(), {
provide: HTTP_INTERCEPTORS,
useClass: ProgressInterceptor,
multi: true
}, {
provide: HTTP_INTERCEPTORS,
useClass: BasicAuthInterceptor,
multi: true
}, provideAnimations(),
CookieService, StorageService, provideToastr({ positionClass: 'toast-top-right' }),
importProvidersFrom(ReactiveFormsModule, HttpClientModule),]
};
loading.component.ts
имеет LoaderService
в массиве поставщиков, это означает, что был создан отдельный экземпляр, предназначенный только для этого компонента. Поэтому другие выбросы не получены.
...
@Component({
selector: 'loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.css'],
standalone: true,
imports: [CommonModule],
// providers:[LoaderService] // <- changed here!
})
export class LoadingComponent implements OnInit, OnDestroy {
...
Вам нужно преобразовать progress.interceptor.ts
в interceptorFn
import { inject } from '@angular/core';
import { HttpInterceptorFn } from '@angular/common/http';
import { LoaderService } from '../service';
export const demoInterceptor: HttpInterceptorFn = (req, next) => {
const loaderSvc = inject(LoaderService);
const authToken = 'YOUR_AUTH_TOKEN_HERE';
// Pass the cloned request with the updated header to the next handler
if (req.params.has('ignoreLoadingBar')) {
// Clone the request and add the authorization header
const authReq = req.clone({
headers: request.headers.delete('ignoreLoadingBar')
});
return next(authReq);
}
this.loaderSvc.startLoading();
return next(request).finally(() => this.loaderSvc.stopLoading());
};
Наконец, в app.config.server.ts
нам нужно использовать provideHttpClient(withInterceptors([demoInterceptor]))
для настройки перехватчиков.
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './routes/routes';
import { provideClientHydration, withHttpTransferCacheOptions } from '@angular/platform-browser';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';
import { provideAnimations } from '@angular/platform-browser/animations';
import { CookieService } from 'ngx-cookie-service';
import { provideNgxMask } from 'ngx-mask';
import { provideToastr } from 'ngx-toastr';
import { ProgressInterceptor, BasicAuthInterceptor } from './shared/helpers';
import { StorageService } from './shared/service';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideClientHydration(withHttpTransferCacheOptions({
includePostRequests: true
})),
provideNgxMask(),
provideHttpClient(withInterceptors([ProgressInterceptor,
BasicAuthInterceptor])),
provideAnimations(),
CookieService,
StorageService,
provideToastr({ positionClass: 'toast-top-right' }),
importProvidersFrom(ReactiveFormsModule, HttpClientModule),
]
};
Благодарю, решение работает нормально. Большое спасибо за помощь. :)