How to avoid multiple loading in angular loading interceptor
In angular each http call loading spinner and tried implement only one loading for initial page loading. How to solve this
You are working on an existing Angular application.
The application makes HTTP calls to a REST API from lot of various components.
You want to show a custom spinner for every HTTP request. Since this is an existing application, there are lot of places where calls to REST API are made. And changing code one by one at every places is not a feasible option.
So, you would like to implement an abstract solution which would solve this problem.
The code could be written simpler, without creating new observable and storing requests in memory. The below code also uses RxJS 6 with pipeable operators:
import { Injectable } from '#angular/core';
import {
HttpRequest,
HttpHandler,
HttpInterceptor,
HttpResponse
} from '#angular/common/http';
import { finalize } from 'rxjs/operators';
import { LoadingService } from '#app/services/loading.service';
import { of } from 'rxjs';
#Injectable()
export class LoadingInterceptor implements HttpInterceptor {
private totalRequests = 0;
constructor(private loadingService: LoadingService) { }
intercept(request: HttpRequest<any>, next: HttpHandler) {
this.totalRequests++;
this.loadingService.setLoading(true);
return next.handle(request).pipe(
finalize(res => {
this.totalRequests--;
if (this.totalRequests === 0) {
this.loadingService.setLoading(false);
}
})
);
}
}
Add this interceptor service into your module providers:
#NgModule({
// ...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: LoadingInterceptor, multi: true }
]
})
export class AppModule { }
Here's an example of the LoadingService implementation:
#Injectable()
export class LoadingService {
private isLoading$$ = new BehaviorSubject<boolean>(false);
isLoading$ = this.isLoading$$.asObservable();
setLoading(isLoading: boolean) {
this.isLoading$$.next(isLoading);
}
}
And here's how you'd use the LoadingService in a component:
#Component({
selector: 'app-root',
template: `
<ng-container *ngIf="loadingService.isLoading$ | async">
<i class="loading"></i>
</ng-container>
<router-outlet></router-outlet>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
constructor(public loadingService: LoadingService) {}
}
Related
I have repetitive problem in angular but I had search a lot about this problem and use all of Technics that answer in stackoverflow and... .
my problem is in my loader component when I subscribe over than one.
this is my loader component
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, OnChanges, AfterViewInit, OnInit } from '#angular/core';
import { Subject, BehaviorSubject } from 'rxjs';
import { LoaderService } from './loader.service';
#Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'app-loader',
templateUrl: './loader.component.html',
styleUrls: ['./loader.component.scss']
})
export class LoaderComponent implements OnInit {
isLoading: BehaviorSubject<boolean>=this.loaderService.isLoading;
constructor(private loaderService: LoaderService, private changeDetector: ChangeDetectorRef) {
}
color = 'accent';
mode = 'indeterminate';
value = 50;
ngOnInit(): void {
}
}
and this is my service loader component
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class LoaderService {
constructor() { }
isLoading: BehaviorSubject<boolean> = new BehaviorSubject(false);
count=0;
show(): void {
debugger
console.log(`show`+this.count++)
this.isLoading.next(true);
}
hide(): void {
debugger
console.log(`hide`+this.count++)
this.isLoading.next(false);
}
}
and this is my interceptor loader
import { Injectable } from '#angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoaderService } from './loader/loader.service';
#Injectable()
export class LoaderInterceptor implements HttpInterceptor {
constructor(public loaderService: LoaderService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loaderService.show();
return next.handle(req).pipe(
finalize(() => {this.loaderService.hide(); })
);
}
}
my error message is
"
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: [object Object]'. Current value: 'ngIf: true'.
at viewDebugError (core.js:17871)
at expressionChangedAfterItHasBeenCheckedError (core.js:17859)
at checkBindingNoChanges (core.js:18059)
at checkNoChangesNodeInline (core.js:27635)
at checkNoChangesNode (core.js:27624)
at debugCheckNoChangesNode (core.js:28228)
at debugCheckDirectivesFn (core.js:28156)
at Object.updateDirectives (loader.component.html:2)
at Object.debugUpdateDirectives [as updateDirectives] (core.js:28145)
at checkNoChangesView ("
please help me to solve it.it's my big problem :-(
I was changing "behavior subject" to observable. subscribe data in loading page and used angular change detector in life cycle.Now, the problem is solve and work correctly
I'm not exactly sure where the 'ngIf' statement is being used, but an alternative might be instead to use css to hide the loader when not in use. E.g.
<div #myLoader [style.display]="isLoading ? 'block' : 'none'>...
To avoid it put a default value for your isLoading property (false for example), and wait the ngOnInit or ngAfterViewInit to change the property in the component.
Tried to replicate your code in a standalone stackblitz instance https://stackblitz.com/edit/angular-multisub with multiple subscriptions for loaderService.
Works without any problem.
Could you fork the above instance and modify to reproduce the same.
I am using Angular Universal. I have created a PlatformService to detect which platform I am currently working on.
/* platform.service.js */
import { Injectable, Inject, PLATFORM_ID } from '#angular/core';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
#Injectable({
providedIn: 'root'
})
export class PlatformService {
constructor(
#Inject(PLATFORM_ID) private platformId: Object
) {
this.platformId; // this is coming out undefined
}
isBrowser() {
return isPlatformBrowser(this.platformId);
}
isServer() {
return isPlatformServer(this.platformId);
}
}
I am creating a BaseComponent for common handling of my route binded components.
/* base.component.ts */
import { Component, OnInit, Inject } from '#angular/core';
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#Component({
selector: 'app-base',
template: '',
})
export class BaseComponent implements OnInit {
protected platformService: PlatformService;
constructor() {
this.platformService = InjectorHolderService.injector.get(PlatformService);
console.log(this.platformService);
}
}
Since this component will be inherited by many components, I didn't want to pass the PlatformService through super(). So I decided to go with creating an Injector.
/* app.module.ts */
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#NgModule({ ... })
export class AppModule {
constructor() {
InjectorHolderService.injector = Injector.create({
providers: [
{
provide: PlatformService,
useClass: PlatformService,
deps: [], // I think i need to put something here, but not sure.
}
]
});
}
}
And a service which can hold all the injected module for future use.
/* injector-holder.service.ts */
import { Injectable, Injector } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class InjectorHolderService {
static injector: Injector;
}
But #Inject(PLATFORM_ID) private platformId: Object is giving out undefined, because of which I am not able to detect the platform.
What am I missing here? or If there is a better approach to achieve the above functionality.
Please let me know if you guys need to see any other file.
I am not sure whether the following approach is good or bad, currently, it is the only thing working for me. Would love to hear any new approach to it.
Since PlatformService needed #Inject(PLATFORM_ID) which is provided only from AppModule, the new Injector I created was not able to find any value for #Inject(PLATFORM_ID) and hence undefined.
So, instead of using class PlatformService in Injector, now I am using PlatformService's instantiated object and hence was able to access everything fine in BaseComponent.
Modified my AppModule like following:
/* app.module.ts */
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#NgModule({ ... })
export class AppModule {
constructor(
private platformService: PlatformService,
) {
InjectorHolderService.injector = Injector.create({
providers: [
{
provide: PlatformService,
useValue: this.platformService, // notice the change of key, using value not class
deps: [],
}
]
});
}
}
Will try to add a minimal repo to recreate this issue and share with you guys, If anyone wants to explore more.
I am trying to use Nodejs/Express as my back end for producing data from a database. I currently have an api route setup so that a database query will result in its directory. So if I visit localhost:3000/api currently I will see the following:
{"status":200,"data":[{"Issuer_Id":1,"Data_Id":2,"Data_Name":"Name 1"},{"Issuer_Id":2,"Data_Id":14,"Data_Name":"Name 2"},{"Issuer_Id":2,"Data_Id":1,"Data_Name":"Name 3"}],"message":null}
This leads me to believe I have everything setup correctly on the back end.
Now how do I get this data to display on my Angular front end?
I have been through hours of tutorials and this is what I have come up with:
nav.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../../data.service';
import { Series } from '../../data.service';
import {Observable} from 'rxjs/Rx';
#Component({
selector: 'app-fixed-nav',
templateUrl: './fixed-nav.component.html',
styleUrls: ['./fixed-nav.component.css']
})
export class FixedNavComponent implements OnInit{
serieses: Series[] ;
constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.getSeries().subscribe((serieses: Series[]) => this.serieses = serieses);
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from'#angular/common/http';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/toPromise';
export class Series {
Issuer_Id: number;
Data_Id: number;
Data_Name: string;
}
#Injectable()
export class DataService {
constructor(private _http: Http) {}
getSeries(): Observable<Series[]> {
return this._http.get("http://localhost:3000/api/")
.map((res: Response) => res.json());
}
}
app.module.ts
import { Form1Module } from './modules/form1/form1.module';
import { FixedNavModule } from './modules/fixed-nav/fixed-nav.module';
import { HeaderModule } from './modules/header/header.module';
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component';
import { HttpClientModule } from '#angular/common/http';
import { HttpModule } from '#angular/http';
import { DataService } from './data.service';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpModule,
HttpClientModule,
HeaderModule,
FixedNavModule,
Form1Module,
NgbModule.forRoot()
],
providers: [DataService],
bootstrap: [AppComponent]
})
export class AppModule { }
What do I need to enter in the nav.component.html to see the results?
Also note that when I refresh my angular page on lcoalhost:4200 I can see that the GET request is hitting the /apiu/ on the 3000 express server.
I am trying to help with best practices which might help get the intended result. I will amend this answer as we troubleshoot and hopefully arrive at the right answer.
So in your dataServices service I wanted to point out a couple things. Angular recommends we use the httpClient and not http and warn that http will soon be depreciated. I am fairly new to angular myself and have only ever used httpClient and have gotten great results so I recommend using that. I think this means that the promise that you are returned is changed too. Namely, you pust use a .pipe method inorder to use rxjs operators like map on the result. So this is what your dataService file would look like:
import { Injectable } from '#angular/core';
import { HttpClient } from'#angular/common/http';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/toPromise';
export class Series {
Issuer_Id: number;
Data_Id: number;
Data_Name: string;
}
#Injectable()
export class DataService {
constructor(private _http: HttpClient) {}
getSeries(): Observable<Series[]> {
return this._http.get<Series[]>("http://localhost:3000/api/")
.pipe(
map((res) => {
console.log(res);
return <Series[]> res
})
)
}
}
Note that I have imported map in a different rxjs/operators.
In actuality you dont even need to pipe or map the return since you have already declared the type of return in the get method of _http. HttpClient will cast the return into a Series[] for you so this one liner: return this._http.get("http://localhost:3000/api/") would work. I've written the code how it is however to console.log the return that your getting.
In the comments, could you tell me what is logged?
I am unable to correct your code I am providing my own setup Works for Me
In server.js
module.exports.SimpleMessage = 'Hello world';
Now in App.js
var backend = require('./server.js');
console.log(backend.SimpleMessage);
var data = backend.simpleMessage
In index html include App.js
<script src = '/App.js'></script>
alert(simpleMessage)
And you should get 'hello world'
I am attempting to use Server-Side Rendering in Angular (v4) to allow for better SEO.
Things work as expected until I add resolve on my route. Adding resolve causes HTML title to retain it's initial value when viewing source.
My Module:
import {
Injectable,
ModuleWithProviders,
NgModule
} from '#angular/core';
import {
ActivatedRouteSnapshot,
Resolve,
Router,
RouterModule,
RouterStateSnapshot
} from '#angular/router';
import {
Observable
} from 'rxjs/Rx';
import {
ArticleComponent
} from './article.component';
import {
Article,
ArticlesService,
UserService,
SharedModule
} from '../shared';
#Injectable()
export class ArticleResolver implements Resolve < Article > {
constructor(
private articlesService: ArticlesService,
private router: Router,
private userService: UserService
) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): any {
return this.articlesService.get(route.params['slug'])
.catch((err) => this.router.navigateByUrl('/'));
}
}
const articleRouting: ModuleWithProviders = RouterModule.forChild([{
path: 'article/:slug',
component: ArticleComponent,
resolve: {
article: ArticleResolver
},
data: {
preload: true
}
}]);
#NgModule({
imports: [
articleRouting,
SharedModule
],
declarations: [
ArticleComponent
],
providers: [
ArticleResolver
]
}) export class ArticleModule {}
My Component:
import {
Component,
OnInit
} from '#angular/core';
import {
ActivatedRoute,
Router,
} from '#angular/router';
import {
Title,
Meta
} from '#angular/platform-browser';
import {
AppComponent
} from '../app.component';
import {
Article,
} from '../shared';
#Component({
selector: 'article-page',
templateUrl: './article.component.html'
})
export class ArticleComponent implements OnInit {
article: Article;
constructor(
private route: ActivatedRoute,
private meta: Meta,
private title: Title
) {}
ngOnInit() {
this.route.data.subscribe(
(data: {
article: Article
}) => {
this.article = data.article;
}
);
this.title.setTitle(this.article.title);
}
}
I am new to Angular SSR so any guidance is greatly appreciated.
Instead of subscribing to route data, retrieve your results from the snapshot like this:
this.route.snapshot.data['article']
You also might need to register ArticlesService in your providers for the module.
As a side note, this import:
import {
Observable
} from 'rxjs/Rx';
is an RxJS antipattern. Please use the following import instead:
import {Observable} from 'rxjs/Observable';
I found that my primary service was referencing a secondary service that was attempting to return an authentication token from window.localStorage.
Attempting to access the client storage caused Angular SSR to omit the generation of source code for my component.
Thanks #Adam_P for helping me walk through it!
I want to create a service to get data from .json file once and share it to multiple subscribers. But now with my solution number of requests to get data from .json file equals to a number of a subscibers for my service.
getconfig.service.ts
import {Injectable} from 'angular2/core';
import {Http, Response} from "angular2/http";
#Injectable()
export class ConfigService {
config: any;
http: Http;
constructor(http: Http) {
this.http = http;
console.log('Inside service');
this.config = this.http.get('/config.json');
}
}
robotui.component.ts
...
import {ConnectionService} from '../services/connection.service';
#Component({
...
providers: [HTTP_PROVIDERS, ConfigService, ConnectionService]
...
})
constructor(private _configService: ConfigService) {
this._configService.config.subscribe((observer) => {
console.log('Subscribe in RobotUI component', JSON.parse(observer._body));
});
}
actual.photo.component.ts
import {Component} from 'angular2/core';
import {ConfigService} from '../services/getconfig.service';
#Component({
...
providers: [ConfigService]
})
export class ActualPhotoComponent {
constructor(private _configService: ConfigService) {
this._configService.config.subscribe((observer) => {
console.log('Subscribe in ActualPhoto component', JSON.parse(observer._body));
});
}
}
When i run it in my console i see:
So, there is get request for each subscibers. I want a solution when i get config.json file only once, save this info in a service and share it with multiple subscibers.
That's because
#Component({
...
providers: [ConfigService] //<--- this creates service instance per component
})
To share data among controllers/components and to create single instance only, you have to inject your service into bootstrap function.
import {ConfigService } from './path to service';
bootstrap('AppCompoent',[configService]) //<----Inject here it will create a single instance only
In subscribing component,
robotui.component.ts
...
import {ConfigService} from '../services/getconfig.service'; //<----- Note this line here....
import {ConnectionService} from '../services/connection.service';
#Component({
...
... // No providers needed anymore
...
})
constructor(private _configService: ConfigService) {
this._configService.config.subscribe((observer) => {
console.log('Subscribe in RobotUI component', JSON.parse(observer._body));
});
}
actual.photo.component.ts
import {Component} from 'angular2/core';
import {ConfigService} from '../services/getconfig.service';
#Component({
...
... // No providers needed anymore...
})
export class ActualPhotoComponent {
constructor(private _configService: ConfigService) {
this._configService.config.subscribe((observer) => {
console.log('Subscribe in ActualPhoto component', JSON.parse(observer._body));
});
}
}
This is what you should do.