Angular 6 - Back button press trigger more than once - javascript

I have the following code to detect the back button press using angular 6.
import { Location } from '#angular/common';
export class ProductsComponent implements OnInit {
constructor( private location: Location){
this.handleBackButtonPress();
}
handleBackButtonPress() {
this.subscribed = true;
this.location.subscribe(redirect => {
if (redirect.pop === true) {
alert('this is a backbutton click');
}
});
}
}
This is working and we got alert on back button press. The problem is If we visit the same page more than once it will trigger the alert with the number of time we visited the route with the same component.
Note:
I have checked for a solution like this.location.unsubscribe(), But failed to find a function like that for location.

You just need to unsubscribe when the component is destroyed by the ngOnDestroy lifecycle hook.
import { Location } from '#angular/common';
import { SubscriptionLike } from 'rxjs';
export class ProductsComponent implements OnInit, OnDestroy {
public subscription: SubscriptionLike;
constructor(private location: Location){
this.handleBackButtonPress();
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
handleBackButtonPress() {
this.subscription = this.location.subscribe(redirect => {
if (redirect.pop === true) {
alert('this is a backbutton click');
}
});
}
}
As mentioned by briosheje in the comments the lifecycle hook does not run on browser refreshes. For that you'll need to handle the unsubscription on the document's onbeforereload event.

The problem, I analyzed here is, every time whenever constructor will run. It will call your function for sure. So you have to check whether this function has been run previously or not.
Simplest answer is
constructor( private location: Location){
const PopCalled = localStorage.getItem('PopCalled')
if(!PopCalled)
this.handleBackButtonPress();
}
handleBackButtonPress() {
this.subscribed = true;
this.location.subscribe(redirect => {
if (redirect.pop === true) {
localStorage.setItem('PopCalled', true);
alert('this is a backbutton click');
}
});
}
Basically, you have to manage the state of PopCalled its up to you which way you want to choose, as per my knowledge this is the simplest way.

Related

Refactoring a method to return a value from addEventListener in Angular

I am checking the Network Status in my Angular application from network.service.ts
// network.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs";
#Injectable()
export class NetworkStatusService {
public status: BehaviorSubject<any> = new BehaviorSubject<any>(null);
public appStatus() {
window.addEventListener('online', this.networkStatusChanged.bind(this));
window.addEventListener('offline', this.networkStatusChanged.bind(this));
}
public networkStatusChanged(): void {
this.status.next(!navigator.onLine);
}
}
In my component, I am injecting this service and in ngOnInit,
I am calling this appStatus method of the service and then subscribe to status (BehaviorSubject) to get the value.
In my component:
public ngOnInit() {
this.networkService.appStatus();
this.networkService.status.subscribe((x)=>{
console.log('status here', x);
if(x) {
// do something
}
});
}
This works and logs the boolean value whenever the application online/offline. But the problem is I would have to call this method and then subscribe & unsubscribe in pretty much every component.
I know addEventListener does not return a value but is there a way to refactor this, so that I just call appStatus() from the component and it returns a boolean value (true/false) whenever the application is offline/online?
You can just create a getter in any component where you want to call appStatus, it will return value of network status.
public get appStatus () {
return navigator.onLine
}
But if you need to listen every time BehaviorSubject emits value, u need to subscribe.
Add your listeners inside service class constructor instead of appStatus() function. This way you don't have to call it every time or from every component.
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs";
#Injectable()
export class NetworkStatusService {
public status: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor() {
window.addEventListener('online', this.networkStatusChanged.bind(this));
window.addEventListener('offline', this.networkStatusChanged.bind(this));
}
public networkStatusChanged(): void {
this.status.next(!navigator.onLine);
}
}
Now subscribe this.networkService.status from any component as you are doing currently.

How to display a navbar component after logging in without reloading the page in angular 12

My landing page does not show a navbar, but I want to display a navbar after logging in successfully. Currently, I'm able to show navbar if I do a full page reload after logging in successfully. I'm very sure that there is a better way than this approach.
app.component.html
<app-navbar></app-navbar>
<router-outlet></router-outlet>
login.component.ts
login(){
this.credentials = this.myForm.value;
if(this.credentials){
this.loginService.authenticate(this.credentials)
.subscribe(data => {
this.storageService.setLocalStorageItem('auth', JSON.stringify(data));
this.dataService.global.showNav = true;
this.sharedService.getProjectMetadata()
.subscribe(metadata => {
this.storageService.setLocalStorageItem('projectMetaData', JSON.stringify(metadata));
this.router.navigate(['/home']);
})
}, err => console.log(err));
} else {
console.log('Please enter your username and password');
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { Subject, Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { IGlobal, IMessage } from '../../Shared/interfaces';
import { MessageCallback } from '../../Shared/types';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
date: string = (new Date()).toString();
global: IGlobal = {
showNav: false,
sessionTimedOut: false,
timezone: this.date.substring(this.date.indexOf('GMT')),
projectMetaData: {
name: ''
},
isAdmin: false,
auth: {
roles: {
admin: false,
developer: false
}
}
}
private handler: Subject<IMessage> = new Subject<IMessage>();
broadcast(type: string, payload: any){
this.handler.next({type, payload});
}
subscribe(type: string, callback: MessageCallback): Subscription {
return this.handler.pipe(filter(message => message.type === type), map(message => message.payload))
.subscribe(callback);
}
}
navbar.component.html
<mat-toolbar fxLayout="row" color="primary" *ngIf='showNavbar'></mat-toolbar>
navbar.component.ts
export class NavbarComponent implements OnInit {
user: IAuth = {};
showNavbar: boolean;
progressbar: number = 0;
constructor(
private storageService: StorageService,
private dataService: DataService
) {
this.showNavbar = this.dataService.global.showNav;
}
ngOnInit(): void {
this.user = JSON.parse(this.storageService.getLocalStorageItem('auth'));
if(this.user){
this.showNavbar = true;
}
}
}
Please help me out. Your help is highly appreciated. Thank you.
The problem lies here,
once authentication is completed successfully in login() function, it is not communicated to navbar.component.ts
showNavbar in navbar.component.ts is used to display/hide navbar template.
Though dataService.global.showNav is set to true, it will not trigger change detection in navbar components. Since it is copied to `showNavbar' only in constructor.
So before login, navbar is already loaded, probably with showNavbar evaluated as false, and never recomputed until page reload.
During pagereload value is read from localStorage which provides latest value to showNavbar
I have two suggestions{S1,S2} to fix this.
S1.
1.broadcast via subject from login component about successful login status
2.And subscribe for that status in navbar component and upon subscription , control rendering of navbar template
3. Looks like as per your business logic,broadcast and subscribe functions in dataservice does that for you in IMessage type subject.
4. Consider example code below and update according to your application.
For eg:
login.component.ts
this.dataService.broadcast('authSuccess',{auth:'successful'})
in navbar.component.ts
OnInit() {
this.dataService.subscribe('authSuccess',setShowNavbar);
}
setShowNavbar() {
this.showNavbar=true;
}
S2:
This is not a clean approach and difficult for tracking, but it works for quick and dirty solutions.
navbar.component.html
<mat-toolbar fxLayout="row" color="primary" *ngIf="dataService.global.showNav"></mat-toolbar>
This case will run change detection whenever value in dataService.global.showNav is updated and evaluate ngIf accordingly.
Note: Better to add a small working proto in stackblitz/jsfiddle/codesandbox etc when posting questions in public forum. So it will be easier for everyone to identify exact problem and arrive on specific solutions quickly.

How to reload a page once using angular or javascript

I need to reload a page after routing to a new component
I have tried location.reload() option but it keeps on reloading without stoping
ngOninit(){
location.reload()
}
Expected result :
I need to reload a page only oncee
Actual result :
It is reloading many times
I don't understand your use case but there is lot of way to make what you want.
I propose you a way with localStorage.
Check if a localStorage exist. By default the key don't exist, so you create it and reload the page too.
After the refresh you check the same key (that exist now). So you don't reload the page and remove the key for the next time
ngOnInit() {
if (!localStorage.getItem('foo')) {
localStorage.setItem('foo', 'no reload')
location.reload()
} else {
localStorage.removeItem('foo')
}
}
you should save (for example) "IsLoadedBefore" boolean in a variable and check it before reloading your page. but a variable that keeps data after page reloaded! (not local ones)
localStorage is a good choice:
function reloadIfNecessary() {
var isLoadedBefore = localstorage.getItem("IsLoadedBefore");
if(isLoadedBefore=="true")
return;
}
else {
localstorage.setItem("IsLoadedBefore",true);
/*use your reload method*/
})
If you want to reload child component use this.ngOnInit(); (on child component which may be refreshed) to reload just this child component.
parent.component.ts
#ViewChild(ChildComponent) child: ChildComponent;
refreshed = false;
ngAfterViewInit() {
if (!refreshed)
this.child.refresh()
refreshed = true;
}
child.component.ts
refresh() {
this.ngOnInit()
}
you can import location
constructor(location: Location)
then call reload method to reload the page
this.location.reload()
Create a service to store the previous route. Get the previous route in your component if it has route or the route you define in condition, then reload.
import { Injectable } from '#angular/core';
import { Router, NavigationEnd } from '#angular/router';
#Injectable()
export class PreviousRouteService {
private prev: string;
private curr: string;
constructor(private router: Router) {
this.currentUrl = this.router.url;
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.prev = this.curr;
this.curr = event.url;
};
});
}
public getPreviousUrl() {
return this.prev;
}
}
Use it in your component:
ngOninit(){
if (this.previousRouteService.getPreviousUrl() {
location.reload()
}
}
Use the service as provider and instantiate it in the constructor.
constructor(private previousRouteService: PreviousRouteService){}
Here you can make use of local-storage like below
ngOnInit(){
const firstTime = localstorage.getItem('key')
if(!firstTime){
localstorage.setItem('key','loaded')
location.reload()
}else {
localStorage.removeItem('key')
}
}
In javascript, you can use
location.reload()
for page reload

Showing the loading spinner icon on all components

I have written the code to show the loading spinner on all components when any event is triggered. It works fine on a single component but the issue with it, I have to show the same loading spinner on the around multiple components when certain event is triggered. See below code:
tasks() {
this.handler.activateLoader();
this.tasksService.get(this.page, this.pageSize).subscribe(results => {
this.handler.hideLoader();
if (this.handler.handle(results)) {
return;
}
this.tasksRes = results['data'];
for (let i = 0; i < this.tasksRes.length; i++) {
if (this.tasksRes[i].status == 'In_progress' && this.tasksRes[i].eventType == 'Sync' &&
this.tasksRes[i].entityId == this.id) {
this.progressFlag = true;
break;
} else {
this.progressFlag = false;
}
}
this.length = results['totalElements'];
}, error => {
this.handler.hideLoader();
this.handler.error(error);
});
}
connect() {
let source = new EventSource('/api/v1/events/register');
source.addEventListener('message', message => {
this.tasks();
});
}
And on ngOnInit(), I have called these 2 methods as below then its working fine.
ngOnInit() {
this.tasks();
this.connect();
}
The actual requirement is when I run a particular event the button is going to be disabled and at the same time the spinner loading will come. I have achieved this one. But how to show the same spinner on multiple components so that the user can know that the task is running.
This is how I am showing the loading spinner. See below:
<span class="text-warning pull-right" *ngIf="progressFlag">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</span>
In my code, I have many components at around 17-18 where I need to show the loading spinner. If I want to show the spinner globally means I can show it on either header and footer component which is common to my entire template. Can any one provide any ideas on it.
Thanks.
Please search keyword HttpInterceptor learn details. One simple example below:
// siteHttpInterceptor.ts
import { Injectable } from '#angular/core';
import { HttpRequest, HttpInterceptor, HttpHandler, HttpEvent, HttpResponse } from '#angular/common/http';
import { throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { LoadingService } from './loading.service';
#Injectable()
export class SiteHttpInterceptor implements HttpInterceptor {
constructor(private loadingService: LoadingService){}
intercept(request: HttpRequest<any>, httpHandler: HttpHandler): Observable<any> {
/* Start loading here */
this.loadingService.startLoading();
return httpHandler.handle(request).pipe(
tap((event: HttpEvent<any>) => {
/* End loading */
this.loadingService.endLoading();
},
(err: any) => {
/* End loading */
this.loadingService.endLoading();
}),
catchError(err => {
return throwError(err);
})
);
}
}
//loading.service.ts LoadingService base on Ionic framework, you can instead it
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoadingService {
private loaders = [];
//sometimes, the request so quickly then close event earlier than open loading bar
private badLoaders = 0;
constructor(
private loadingController: LoadingController
) {
}
async startLoading() {
if (this.badLoaders > 0) {
this.badLoaders --;
} else {
await this.loadingController.create({
message: 'Loading ...',
}).then(loader => {
this.loaders.push(loader);
loader.present().then(() => {
//if it is bad loader, close
if (this.badLoaders > 0) {
this.badLoaders --;
this.endLoading();
}
});
});
}
}
endLoading() {
let loader = this.loaders.pop();
if (loader) {
loader.dismiss();
} else {
// it is mean close event earlier
this.badLoaders ++;
}
}
}
Use it then you not need manage loader handle each request method.
Put your spinner at the main component.. in most cases its the AppComponent
Then put a these on your shared service
private LoadingStatus = new Subject<boolean>();
// Observable string streams
IsLoading$ = this.LoadingStatus.asObservable();
// Service message commands
triggerLoading(status: boolean) {
this.LoadingStatus.next(mission);
}
Then at your sender component call triggerLoading(true) or triggerLoading(false) from the service and subscribe at your main component (AppComponent):
this.shareService.IsLoading$.subscribe( data => progressFlag = data )
Or Add your logic as this:
this.shareService.IsLoading$.subscribe(
data => {
if(data) {
// start loading logic here
} else {
// end loading logic here
}
}
)
Source: Angular - Component Interaction

Angular: Many components listen to key events - refactor?

In a couple of my angular components I listen to key events
#HostListener('window:keydown', ['$event']) keyInput(event: KeyboardEvent) {
if (this.isActive) {
if (event.keyCode === 27) {
// do something
}
}
}
In this case it is just one key, but I have them with more too
Anyway, I see duplicate code in my project here. So, is this acceptable or should I refactor this ? If so, what would be the preferred way?
I would create a service that other components can subscribe to when the event occurs. For example:
#Injectable()
public class KeyEventService {
private keydown$: Observable;
constructor() {
this.keydown$ = Observable.fromEvent(window, "keydown");
}
public getEscapeKeyListener() {
return this.keydown$.filter((event: KeyboardEvent) => event.keyCode === 27);
}
}
This allows you to setup the listener for the event once and then filter it to the appropriate keypress. The public methods return observables that filter on specific keys or multiple keys without having to setup a new observable. Then in your component you would use it like this:
#Component({})
public class MyComponent implements OnInit, OnDestroy {
private keyListenerSub: Subscription;
constructor(private keyEventSvc: KeyEventService) { }
ngOnInit() {
this.keyListenerSub = this.keyEventSvc.getEscapeKeyListener().subscribe(() => {
/* respond to keypress */
});
}
ngOnDestroy() {
this.keyListenerSub.unsubscribe();
}
}
This allows you to set up the listener only once and then in your components you can just take the appropriate action when the event you want occurs.

Categories

Resources