How to load Loader on Every Route in Angular - html

Hello i want to put loader on every route link , when route link change show loader until all its component not load with api data.
Structure of Component
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
inside <router-outlet></router-outlet> i have other child component like and its data come from api.
<app-component1></app-component1>
<app-component2></app-component2>
so my problem is i cant put loader for page(route) wise if page load loader show and hide after all component load with dynamic(api) data

You can subscribe to RouterEvents to know when a route navigation is started to completed. You would also need a loader component in your AppComponent outside of your router-outlet and a flag to show/hide the loader. You can control loader visibility across the application using a LoaderService.
LoaderService
export class LoaderService {
isLoaderShown = new BehaviorSubject<boolean>(false);
constructor() { }
public showLoader() {
this.isLoaderShown.next(true);
}
public hideLoader() {
this.isLoaderShown.next(false);
}
}
You can use this LoaderService to show or hide the loader from anywhere in the application.
Update your AppComponent to add a new loader component. LoaderComponent just needs to contain a loader of your choice.
<app-header></app-header>
<app-loader *ngIf="showLoader"></app-loader>
<router-outlet></router-outlet>
<app-footer></app-footer>
Now we can control the loader display using the showLoader flag in AppComponent.
AppComponent
export class AppComponent implements OnInit {
showLoader = false;
constructor(
private loaderService: LoaderService,
private router: Router
) { }
ngOnInit() {
this.loaderService.isLoaderShown.subscribe(isLoaderShown => this.showLoader = isLoaderShown);
this.router.events.subscribe(routerEvent => {
if (routerEvent instanceof NavigationStart) {
this.loaderService.showLoader();
} else if (routerEvent instanceof NavigationEnd) {
this.loaderService.hideLoader();
} else if (routerEvent instanceof NavigationCancel) {
this.loaderService.hideLoader();
// Handle cancel
} else if (routerEvent instanceof NavigationError) {
this.loaderService.hideLoader();
// Handle error
}
});
}
}
Now, whenever a router navigation starts, we are showing the loader and hiding when navigation ends.
However, this doesn't take the API load times into consideration. For that, you could remove the this.loaderService.hideLoader(); from the NavigationEnd and add it in your API call subscription. This is the reason why we add it as a service. You can inject it onto your API Service and hide loader.
this.httpClient.get(url).subscribe(result => {
// Perform operations with the result
this.loaderService.hideLoader();
});
So, loader will be shown when navigation starts and when the API call result is available in service, loader can be hidden.

Related

Angular updating values across views

I have an Angular app that has the following:
One component has a text input and a button. The user fills in the text input and clicks the button. This updates a the URL for a router link.
The router link loads a component called view and it in turn reads the URL parameter from the router link and places that value in a service and displays it on the component so I know it worked.
So if the user type 'abc' in the text input then the router link URL would be /view/abc. So 'abc' will be displayed in the view component. Sometimes users will paste a router link like /view/def. This works to update the view component.
The part I can't get to work is to update the text input box in the other component to reflect the current value of the pasted link.
I tried using 'AfterViewChecked' to read the value from the service but that executes before the service value is updated so it is always incorrect.
These cannot bind to the same variable because this will eventually turn into a web service call and I don't want the service to be updated while the user is typing into the text input box, only when they click the button.
I'm not sure where else to look. Any searching I do just brings up data binding, but that is not my problem.
The relevant files are below but the full test sample code is on StackBlitz at https://stackblitz.com/edit/github-jwr6wj.
If you change the URL in the text input and click the button the URL display below will update. But if you paste in the pseudo URL https://github-jwr6wj.stackblitz.io/view/http%253A%252F%252Fwww.ebay.com%252F the URL displayed below will update correctly but I can't figure out how to update the text input to reflect what came in with the URL.
update.service.ts contains the URL that is the current one. This service will also load the data from a web service.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class UpdateService {
url: string = "http://www.google.com/";
constructor() { }
}
view.component.ts is where the data selected by the user will be displayed. It parses the URL parameter for the data and updates the service with it.
import { ActivatedRoute, ParamMap } from '#angular/router';
import { UpdateService } from '../update.service';
#Component({
selector: 'app-view',
templateUrl: './view.component.html',
styleUrls: ['./view.component.css']
})
export class ViewComponent implements OnInit {
constructor(public activatedRoute:ActivatedRoute, public updateService: UpdateService) { }
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) =>{
this.getUrl(paramMap);
});
}
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get("url");
if (incomingUrl == null) {
this.updateService.url = "http://www.google.com/";
} else {
this.updateService.url = decodeURIComponent(incomingUrl);
}
}
}
view.component.html
<p>URL: {{updateService.url}}</p>
toolbar.component.ts is where the user will enter they request. sourceUrl is the variable that will be updated when the user types. However I also want it to update when the page is visited via the browser URL with the correct data as part of that URL. I can send data to the view component via the router but I can't find out how to send data back to the toolbar component.
import { UpdateService } from '../update.service';
#Component({
selector: 'app-toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.css'],
})
export class ToolbarComponent implements OnInit {
sourceUrl: string = '';
constructor(private updateService: UpdateService) {}
ngOnInit(): void {
this.sourceUrl = this.updateService.url;
}
getViewUrl(): string {
return '/view/' + encodeURIComponent(this.sourceUrl);
}
}
toolbar.component.html
<div class="col-sm-12">
<input type="text" [(ngModel)]="sourceUrl" />
<a class="btn btn-primary" [routerLink]="getViewUrl()">
<span class="fa fa-eye"></span>
</a>
</div>
One way to share data between components is using a Service and Observables. Change your url in the Service to be BehaviorSubject with an initial value.
The way BehaviorSubject works is that you emit values from components to update the Observable in the Service. The BehaviorSubject behaves both as an Observer and Observable.
Essentially, an Observer is an object that listens to events, in this case, updating the URL. An Observable is an object that components listen to for updates or changes. In this case, the View Component listens to the BehaviorSubject for this update to the URL.
Service
export class UpdateService {
private url$ = new BehaviorSubject<string>('www.google.com');
public readonly url: Observable<string> = this.url$.asObservable();
constructor() {}
}
Toolbar Component
export class ToolbarComponent implements OnInit {
sourceUrl: string = '';
constructor(private updateService: UpdateService) {}
ngOnInit(): void {
this.updateService.url.subscribe((str) => {
this.sourceUrl = str;
});
}
getViewUrl(): string {
return '/view/' + encodeURIComponent(this.sourceUrl);
}
}
View Component
export class ViewComponent implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public updateService: UpdateService
) {}
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) => {
this.getUrl(paramMap);
});
}
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get('url');
if (incomingUrl == null) {
this.updateService.url.next('http://www.google.com/');
} else {
this.updateService.url.next(decodeURIComponent(incomingUrl));
}
}
}
View Component HTML
<p>URL: {{ updateService.url | async }}</p>
You are right to try with AfterViewChecked because it's just a timing issue. What you could do is have url inside updateService defined as a BehaviourSubject, so that at the moment it's updated in your view component, you see the change in the toolbar component.
Inside the service :
public url$: BehaviorSubject<string> = new BehaviorSubject("http://www.google.com/");
Inside the view component ts :
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get("url");
if (incomingUrl == null) {
this.updateService.url$.next("http://www.google.com/");
} else {
this.updateService.url$.next(decodeURIComponent(incomingUrl));
}
}
And inside the view component HTML : (you can also subscribe to the Behaviour Subject directly inside the ts)
<p>URL: {{updateService.url$ | async}}</p>
And you will also have to deal with the fact that the url is a Subject inside the toolbar component ts!
Good luck, let me know if this is not clear!

calling back end only for particular component in Angular

I have Tags Components in my project and I reused that component in other components. In my Tags component ngOnInit, I called backend to get all the existing tags. The problem I have right now is that call is applied to every other components even though the call is not needed at other components other than Edit Components. Since I only need the backend call to show existing tags just for Edit Components, I tried to move that call to Edit Components ngOninit but it didn't show me the existing tags anymore. I would be really appreciated if I can get any help or suggestion on how to fix this.
Tags Component TS
ngOnInit(): void {
this.tagService.getAllTagsByType('user').subscribe((normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
})
}
If i add this call in Tags Component, it show me all the existing tags in drop down. I tried to move this to Edit component ngOnIt since I only want Eidt Component to use that call but It didn't show me existing tags anymore.
Tags.Service.ts
getAllTagsByType(tagType: any){
return this.http.get<Tag[]>(`${environment.api.chart}/tags/all/${tagType}`).pipe(first());
}
You could try to setup a flag to trigger the backend call using #Input.
tags.component.ts
import { Component, OnInit, Input } from '#angular/core';
export class TagsComponent implements OnInit {
#Input() getAllTags = false;
ngOnInit(): void {
if (this.getAllTags) { // <-- check here
this.tagService.getAllTagsByType('user').subscribe(
(normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
},
error => {
// handle error
}
);
}
}
}
Now pass the value true to getAllTags when you wish to make the backend call. Since ngOnChanges hook is triggered before ngOnInit, the call will not be made if the property isn't passed in the component selector.
<!-- edit component -->
<mc-tags
[getAllTags]="true"
[workspace]="workspace"
[removable]="true"
[selectable]="true"
[canAdd]="true" ]
[editMode]="true"
(added)="tagAdded($event)"
(removed)="tagRemoved($event)"
> </mc-tags>
<!-- other components -->
<mc-tags [workspace]="workspace"></mc-tags>
Try to use RxJS. You should keep your Tags Data in TagService as a Subject (observable). Btw it is always best practise to store data in service layer.
TagService:
#Injectable({
providedIn: 'root'
})
export class TagService {
tagsSource = new BehaviorSubject<Tag[]>(null);
allnormalTags$ = this.tagsSource.asObservable();
getAllTagsByType(type: string){
http.request.subscribe(resultData => this.tagsSource.next(resultData))
}
}
Then in your component you can check whether data are already loaded and don't call it again.
export class ProductListComponent implements OnInit {
constructor(private tagService: TagService) { }
ngOnInit(): void {
if (isNullOrUndefined(this.tagService.tagSource.getValue())
this.tagService.getAllTagsByType('user')
}
P.S. You don't need to explicitly subscribe service observable in your component. Instead you can directly get your data from service subject/observable with async pipe.
<table *ngIf="tagService.allnormalTags$ | async as allnormalTags">
<tbody>
<tr class="product-list-item" *ngFor="let tag of allnormalTags">
<td data-label="name"> {{tag.name}} </td>

change class of an element with media query ,angular 4

want to change a class of an element when the width of browser changes
have that in my .ts
matchMedia('(max-width: 400px)').addListener((mql => {
if (mql.matches) {
this.myclass = 'toggled';
}
}));
and in the html somthing like that:
<app-side-bar [ngClass]="myclass"></app-side-bar>
value of 'myclass' is changed but the HTML element(app-side-bar) is not getting updated -what am I missing here?
Because Angular does keep track of the the event that occurs when the browser size changes, it wont detect the change. You have to trigger it yourself:
You can do this by warpping the code inside NgZone:
import { NgZone } from '#angular/core';
// Inject NgZone in your constructor:
constructor(private zone: NgZone) {
}
// Run the code that changes state inside the zone
matchMedia('(max-width: 400px)').addListener((mql => {
if (mql.matches) {
this.zone.run(() => {
this.myclass = 'toggled';
});
}
}));

what does "#observer" attribute do?

First thing I learnt about mobx-react is use "#observer" attribute to track values of properties which defined in state class..
this is my sample below;
//#observer cut it off
SingUp.js
import React, {Component} from 'react'
import {observer} from 'mobx-react'
class SignUp extends Component{
constructor(props){
super(props)
}
SaveUser(e){
e.preventDefault();
this.props.appState.user.username = this.username.value;
this.props.appState.user.password = this.password.value;
this.props.appState.postSaveUser();
}
render(){<form onSubmit={()=>this.SaveUser(e)}>...
when I submit the form it "SaveUser()" called and set app state values. you see I dont define "#observer" attribute at top of SignUp class
and here is state class; AppState.js
import { observable, action} from "mobx"
import {user} from './models/user'
class AppState {
#observable user=new user;
constructor() {
}
postSaveUser(){
debugger
var asd = this.user
}
}
the thing is when I check the values in "postSaveUser()" method I see values exactly I set it "SignIn" component, is it weird?
I thought it only track values assigned in any class which defined with "#observer" attribute but although I dont use it I am able to access data?
Using the #observer decorator on a React component class is much like using autorun. The component will re-render when the observables that got de-referenced in the last render are changed. You can still of course change the value of observable data, it is just that your React component will not re-render automatically if you don't use the #observer decorator.
Example (JSBin)
class Store {
#observable data = 'cool';
}
const store = new Store();
setTimeout(() => {
store.data = 'wow';
}, 2000);
#observer
class Observer extends Component {
render() {
return <h1> This component will re-render when {store.data} changes.</h1>;
}
};
class NonObserver extends Component {
render() {
return <h1> This component will NOT re-render when {store.data} changes.</h1>;
}
};
ReactDOM.render(
<div>
<Observer />
<NonObserver />
</div>,
document.getElementById('app')
);

reset or reload the same page in Angular 2

I am trying to reload the same url in Angular 2 by using router.navigate but it is not working.
Url: http://localhost:3000/page1
Scenario: I am on http://localhost:3000/landing and on click of button, will pass routing parameter which should reload the page.
Example:
Suppose user is in page1(Edit form) and url reads as localhost:3000/page1 and there will be a button Create new, on click of button passing a routing paramter using
let navigationExtras: NavigationExtras = { queryParams: { "refresh": "Y" } };
this.router.navigate(['page1'], navigationExtras);
ngOnInit is only called when the component is created. When you navigate to the same page (with different url parameters) the ngOnInit method is not recalled. You should use the ActivatedRoute Params Observable
import { ActivatedRoute } from '#angular/router';
export class MyComponent implements OnInit {
constructor(private _activatedRoute: ActivatedRoute){}
ngOnInit(){
this._activatedRoute.params.subscribe(
params => {
// this is called everytime the url changes
}
}
}
You just need to call window.location.reload();