How to keep pagination data during navigations in Angular? - javascript

I have an application that has a list and details page. In the list page, the table has paging feature and there is also a search box that filters the list items. When clicking on an item in the list it opens details of the related record. I am wondering what is a proper way to keep the paging and filter parameters of the list page when I navigate to details page and then use these parameters when come back to this list page. I meant that, the user in on the 3rd page of the list and then click details icon of a record. then opens details page and later click back button on that page. I want the user can navigate to the 3rd page again by getting the parameter values that he sent before. If I can get these values the rest would be ok of course.
Should I use NavigationExtras or etc for this purpose?

There are multiple ways of implementing such functionality. In general, you need to save the last search state (search value, page, search results, etc.) somewhere during the list component OnDestroy and then use them in OnInit hook of the list component. Here's an example of using service:
list component
constructor(private preserveSearch: PreserveSearchService){}
ngOnInit() {
this.recoverLastSearchValue();
}
ngOnDestroy() {
this.preserveSearch.searchState = {
searchValue: this.searchControl.value,
results: this.data,
resultsLength: this.resultsLength,
};
}
recoverLastSearchValue() {
const lastSearch = this.preserveSearch.searchState;
if (lastSearch) {
this.searchControl.setValue(lastSearch.searchValue, {
emitEvent: false,
emitModelToViewChange: false,
});
this.data = lastSearch.results;
this.resultsLength = lastSearch.resultsLength;
}
}
search state service
import { Injectable } from "#angular/core";
import { SearchResultsItem } from "app/_models/search";
interface PreservedSearchState {
searchValue: string;
results: SearchResultsItem[];
resultsLength: number;
}
#Injectable({
providedIn: "root",
})
export class PreserveSearchService {
private lastSearch: PreservedSearchState;
get searchState(): PreservedSearchState {
return this.lastSearch;
}
set searchState(lastSearch: PreservedSearchState) {
this.lastSearch = lastSearch;
}
constructor() {}
}

Related

load data on change of dropdown in other component in Angular

I have component A which is navbar and component B which is list view
Navbar has dropdown where all the users are listed, onInit method of component B all the data is loaded in the Component B Table that selected user is in local storage, now i want when the user changes the users from dropdown then that users data should automatically be refreshed in the Component B selected users are saved in localstorage. Is there anyway i can call method of second component from first component.
Navbar Component
<select class="form-control"(change)="selectUsers($event)" [(ngModel)]="UserId">
<option *ngFor="let opt of userlist" value={{opt.UserId}}>{{opt?.userName}}
</option>
</select>
selectUsers(event) {
this.Service.SelectedUserId = event.target.value;
}
In Service File
set SelectedUserId(value: string) {
if (value) localStorage.setItem('ActiveUserId', value);
}
Component B:
fetch() {
this.Userslist = [];
this.param.userId = localStorage.getItem('ActiveUserId');
this.service.getUsers(this.param).subscribe((data => {
this.Userslist = data.response;
}
}
In View:
<tbody>
<tr *ngFor="let data of Userslist | paginate:{itemsPerPage: 10, currentPage:p} ; let i=index"
..
..
</tbody>
Any solution to resolve this issue, Thanks
setup a common service which is shared by the two components. Lets assume its called testService.
so service will be like.
export class testService {
testEmitter: Subject<void> = new Subject<void>()
testObserver$: Observable<void>;
constructor () {
this.testObserver$ = this.testEmitter.asObservable();
}
}
Now the power of this pattern is that we can listen for next of the subject from any component in the application and perform action!
so in the navbar we start the next which will trigger action on all the observables!
selectUsers(event) {
this.Service.SelectedUserId = event.target.value;
this.testService.next();
}
So we subscribe on the component where the refresh should happen, and then reload the grid.
child component.
export class ChildComponent implements OnInit, OnDestroy {
subscription: Subscription = new Subscription();
ngOnInit() {
this.subscription.add(
this.testService.testObserver$.subscribe(() => {
this.fetch();
})
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
You can also emit objects like {eventType: 'test', data: {}} inside the next and then add multiple if conditions to validate the eventType and perform multiple actions on the same subject!

When pass a selected item through a shared service between to component, how to respond all changes, not just onInit?

Mg goal is when I select an item from dropdown, which is in a button component, then it passes the item to a table component throughout a shared service. I need this to change the table by just showing the selected element. Now, if I select the element, it changes the service data to the selected item, but the table component checks the service data onInit (when nothing is selected yet), and if I select an item, it doesen't checks again. Just once on init. What should I do to make the table component constantly watch if the service data changed? I thought about observable, but I don't know how to implement it in my code.
My service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class SharedDataService {
selectedData: object = {};
constructor() {}
setSelectedData(data: object) {
this.selectedData = data;
}
getSelectedData() {
return this.selectedData;
}
Button Component:
onSelect(company: any) {
for (var i: number = 0; i < this.items.length; i++) {
if (this.items[i].id == company.target.value) {
this.selectedCompany = this.items[i];
this.sharedData.setSelectedData(this.selectedCompany); //pass data to service
}
}
}
Table Component:
ngOnInit(): void {
this.onGetItems();
this.selectedCompany = this.sharedData.getSelectedData();
if (Object.getOwnPropertyNames(this.selectedCompany).length === 0) { //if nothing was selected
//do nothing
} else {
//refresh the table with displaying that one item only
}
}
Instead of just get and set a variable in a service switch to using rxjs behavior subject. So with this you can just subscribe to the variable in the service where you can listen to all the changes.
You can find a similar example here - https://stackoverflow.com/a/57355485/7203750

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.

What is the proper way to share data between two components using rxjs ReplaySubject?

I've developed a component with two views. Component A has a contact form, and Component B is the "Thank you" page.
Component A:
You fill the form and submit it.
As soon as the response arrives, a new ReplaySubject value is created.
The user will be routed to the component B.
Component B:
The component is initialized.
The component gets the value from the subject.
The view is rendered and displays the thank you message.
HTTP Call response (Returned after a successful Form data post request):
{
"status": 200,
"body": {
"message": "We have received your request with the card information below and it will take 48h to be processed. Thank you!",
"card": {
"name": "John Doe",
"email": "john.doe#gmail.com",
"accountNumber": "12345-67890"
}
},
"type": "ItemCreated"
}
Component A (Form) code:
import { Component } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { RequestCardWidgetService } from './request-card-widget.service';
import { RouterService } from '#framework/foundation/core';
import { Item } from '#pt/request-card-data'
#Component({
selector: 'pt-request-card-form',
templateUrl: './request-card-form.template.html',
providers: [RouterService]
})
export class RequestCardFormComponent {
constructor(private fb: FormBuilder, private data: RequestCardWidgetService, private router: RouterService){}
item: Item = {
name: '',
email: '',
accountNumber: ''
};
requestCardForm = this.fb.group({
name: ['', Validators.required],
email: ['', Validators.email],
accountNumber: ['', Validators.required]
})
onSubmit() {
this.item = this.requestCardForm.value;
this.data.requestCard(this.item)
.subscribe(data => {
this.data.processResult(data);
this.router.navigate(['/success']);
});
}
}
Component B (Thank you page) code:
import { Component } from '#angular/core';
import { RequestCardWidgetService } from './request-card-widget.service';
#Component({
selector: 'pt-request-card-success',
templateUrl: './request-card-success.template.html'
})
export class RequestCardSuccessComponent {
messages: any; // TODO: To use the proper type...
constructor( private requestCardService: RequestCardWidgetService) {
this.messages = this.requestCardService.message;
}
}
Component B Template (Thank you page):
<div *ngIf='(messages | async) as msg'>
{{ msg.message}}
</div>
Component Service code:
import { Injectable } from '#angular/core';
import { HttpResponse } from '#angular/common/http';
import { Observable, ReplaySubject } from 'rxjs';
import { map, take } from 'rxjs/operators';
import {
RequestCardDataService,
Item,
ItemCreated
} from '#example/request-card-data';
#Injectable()
export class RequestCardWidgetService {
constructor(private dataService: RequestCardDataService) { }
private readonly results = new ReplaySubject<ItemCreated>();
readonly message: Observable<ItemCreated> = this.results; // Message Line. This is the variable that I'm rendering in the template. Is this the correct way of extracting subject values?
requestCard (card: Item): Observable<ItemCreated> {
return this.dataService.postCardRecord(card).pipe(
take(1),
map((response: HttpResponse<ItemCreated>): ItemCreated | {} => {
return response.body
? response.body
: {};
})
);
}
processResult(data: ItemCreated) {
this.results.next(data);
}
}
Recap:
Component A has a form. After you submit the form, the results are stored as a new value in the subject. The user is routed to the thank you page.
The thank you page component renders the element and gets the newest value from the subject. Then it renders the contents.
This code works, but I do have some questions.
Question:
Is this the proper way of using the Subject?
Is this:
readonly message: Observable<ItemCreated> = this.results;
the proper way of extracting the values from a subject? (I'm passing 'message' to the view.)
Are there better ways to achieve the same result?
Thanks a lot in advance.
Is this the right way to use a subject?
ReplaySubect unconstrained will replay all of the values it has previously emitted to new subscribers. This could lead to situations where a user could receive previously emitted messages until they finally receive the current message. Therefore, either constrain the subject or consider using a BehaviorSubject instead.
Extracting values from the subject
A Subject and all of its derivatives are both Observables and Observers. When providing a subject to a consumer, you do not want to expose the Observer interface, i.e., a consumer should never be able to call next, error or complete. Thus, as suggested in a comment, you should ensure you are only exposing the Observable interface to consumers by first calling the asObservable method.
readonly message: Observable<ItemCreated> = this.results.asObservable();
Next Steps
If you want to continue using service-based communication between components, then I think you have opportunities to clean/refine your code per the docs linked in the question comments.
If your application is going to grow in complexity, I would steer you down the Redux-style architecture and look into NgRx and specifically, the use of effects to manage side effects.
Effects can meet all of your requirements with simple, discreet observable constructs, i.e., an effect to handle the form submission, receive the response, even navigate to the success page. More information about effects can be found here.
A redux architecture can be overkill for simple tasks, but if you're working on a large app managing a large state tree, I prefer this approach over service-based integrations.

Changing value in one component affects another

I have an angular 6 application, which has a top bar and a content area below this. These are different component and I am currently developing the user profile page. Name of the user is also displayed in the top bar.
My problem is like whenever I have updated the user's name in EditUser page, it successfully saves, but the same is not updated on the top bar. In Vue.js, I can simply handle this with a Vuex store; but how can I handle this in Angular 6.
Any help would be much appreciated.
Next time post a bit of code. Since there isn't, I'll show you how to do it with an example.
Let's assume we have two component, A and B. And the changes will be reflected on both of two components.
Service :
export class YourService{
private data$ = new BehaviorSubject<string>(null);
data = this.data$.asObservable();
public setData(data: string){
this.data$.next(data);
}
}
Component A/B.html :
<div>{{something}}</div>
Component A/B.ts :
isAlive = true;
something: string;
constructor(
private service: YourService
) { }
ngOnInit(){
this.service.data
.takeWhile(() => this.isAlive)
.subscribe( res => {
if(!!res){
this.something = res;
}
});
}
ngOnDestroy(){
this.isAlive = false;
}
Component that change the status:
export class AnotherComponent{
constructor(
private service: YourService
) { }
private changeData(data: string){
this.service.setData(data);
}
}
Now everything is working fine. BehaviorSubject allow the communication between components. whenever the function changeData is fired, you will see the changes on both of your components.
takeWhile is for unsubscribing when the component die.
If you have more question, feel free to ask them to me and I will edit this answer.
You can create service to exchange data between components. It could be UserService that provide access to current user information.
#Injectable()
export class UserService {
user: UserInfo;
// define user here (load from backend or somehow else)
}
In user-profile.component.ts
export class UserProfileComponent {
constructor(public userService: UserService) { }
}
user-profile.component.html
<input type="text" [(ngModel)]="userService.user.name">
In header.component.ts
export class HeaderComponent {
constructor(public userService: UserService) { }
}
header.component.html
<span>{{ userService.user.name }}</span>
So the anggular DI will create a singleton UserService and injects the same object to both components. And when you change it in any of them the changes will be shown in other.

Categories

Resources