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!
Related
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
I have a Behavior Subject dataService in Angular that makes a request to server to get the user details when admin-layout-component loads (parent component), and then injects the data in the user component, this is my user component onInit:
ngOnInit(){
this._dataService.currentUserDetails.subscribe(userDetails => this.userDetails = userDetails);
console.log(this.userDetails);
if(this.userDetails === ''){
this.getUser();
}
}
When i refresh the browser in user component, first loads the current component ngOnInit, i have to make the conditional to check userDetails and make a new request to server, but admin-layout-component is making the request too, my question: There is a way to await the dataService so i don't have to make the getUser() twice?. Thanks in advance.
You can use Subject to store information about user and if you need, you always get data from subject by this.userDetails anywhere in you component:
export class Component implements OnInit {
userDetails$ = new BehaviorSubject<any>(null);
ngOnInit(){
this._dataService.currentUserDetails.subscribe(userDetails => {
this.userDetails.next(userDetails)
});
}
get userDetails(): any {
return this.userDetails$.getValue();
}
}
and in template you can subscribe to your service Subject
<div> {{ userDetails$ | async }} </div>
I have a service which has following method:
room-service.ts
#Injectable()
export class RoomService {
private readonly _equipment: BehaviorSubject<EquipmentDto[]> = new BehaviorSubject([]);
public equipment$ = this._equipment.asObservable();
getEquipmentForRoom(roomId: number) {
this.restService.getEquipmentForRoom(roomId).subscribe(res => {
this._equipment.next(res);
});
}
room-component.ts:
#Component()
export class RoomsComponent implements OnInit {
#Input() room: RoomEntity;
equipment$: Observable < EquipmentDto[] > ;
equipmentList: Array < EquipmentDto > ;
constructor(private equipmentService: EquipmentService) {}
ngOnInit() {
this.equipment$ = this.equipmentService.equipment$;
this.equipmentService.getEquipmentForRoom(this.room.id);
this.equipment$.subscribe(items => {
this.equipmentList = items;
});
room-component.html
<div *ngFor="let eq of room.equipmentList">
<!-- list my equipment here -->
</div>
Now I have a parent component which contains multiple Room Components (those are added programmatically based on the amount of rooms). Anyway, list of equipment is the same for each of the rooms. It looks like once subscribed, the data in first components is overwritten by the component created as the last one.
My question is, how can I get a proper data for each of the rooms using the observable from my service?
You can use this approach with single BehaviorSubject only when your data is the only source of this data.
Instead, you can change your getEquipmentForRoom(roomId: number) like this:
getEquipmentForRoom(roomId: number) {
return this.restService.getEquipmentForRoom(roomId);
}
And then subscribe to it in the compoment:
this.equipmentService.getEquipmentForRoom(this.room.id).subscribe(items => {
this.equipmentList = items;
});
And I agree with Alexander, this component should be dumb as possible.
try to build pipes instead of subscriptions
#Injectable()
export class RoomService {
// return an observable.
getEquipmentForRoom$(roomId: number) {
return this.restService.getEquipmentForRoom(roomId);
}
#Component()
export class RoomsComponent implements OnInit {
#Input() room: RoomEntity;
equipment$: Observable<EquipmentDto[]>;
constructor(private equipmentService: EquipmentService){}
ngOnInit() {
// simply share observable.
this.equipment$ = this.equipmentService.getEquipmentForRoom$(this.room.id);
});
<div *ngFor="let eq of equipment$ | async"> <!-- add async here -->
<!-- list my equipment here -->
</div>
You can fetch the data once in your parent and pass the data to your child components.
That child component (RoomsComponent) should be dumb and not doing requests
Adding onto Alexander’s answer.
The problem is that you are subscribing to the same BehaviourSubject for all the components and the components are taking the latest roomData that is emitted by the BehaviourSubject, which would be the last RoomComponent.
It would work if the BehaviuorSubject holds all the rooms which are fetched by the parent component, and passing each room data to each RoomComponent using #Input.
dialog.ts
export class DialogComponent {
constructor(private service: RestService, private dialogRef: MatDialogRef<DialogComponent>) {
}
no() {
this.dialogRef.close();
}
save() {
const url = 'https://gorest.co.in/public-api/users';
const options = {headers: HttpHeaders, params: HttpParams};
const getResp: any = this.service.Get(url, options);
this.dialogRef.close();
///THIS save() FUNCTION HARDCODED, BUT I WANT TO MAKE RESTCALL DYNAMICALLY WHEN ANYONE CLICKS SAVE BUTTON IN DIALOG
}
}
dialog.html
<mat-dialog-actions>
<button class="mat-raised-button"
(click)="no()">
no
</button>
<button class="mat-raised-button mat-primary"
(click)="save()">
save
</button>
</mat-dialog-actions>
component1.ts
getUsers() {
const dialogConfig = new MatDialogConfig();
const dialogRef = this.dialog.open(DialogComponent,
dialogConfig, passSomeRestCall-1-FunctionSomehow());
}
component2.ts
getEmployees() {
const dialogConfig = new MatDialogConfig();
const dialogRef = this.dialog.open(DialogComponent,
dialogConfig, passSomeRestCall-2-FunctionSomehow());
}
Above 2 components have to make use of Dialog Component dynamically, currently above save() hardcoded with some restcall, but actually rest call needed when clicked on save() for both above components. So, in short, save() button should happen dynamic restcall based on the component.
Could anyone please help me on above, I am very new to angular.
EDIT:
for (let i = 0; i < this.items.length; i++) {
this.service.makeRestCall(this.items[i]);
}
How do I pass above for loop logic in dialog component? I should be able to do some business logic dynamically inside save() based on component like below
save(){
dynamicMethod() // this should be passed somehow from a component
this.dialogRef.close();
}
You do not need to pass the callback method. It should be part of modal's parent.
Only you have need is to set #Output into the modal with type EventEmitter. Like:
#Output() save = new EventEmitter<boolean>();
On Save button just implement:
handleSave: void {
this.save.emit(true);
}
Parent will listen this #Output and handle it properly.
dialogRef.componentInstance.save$.subscribe((res) => {
// Here is your business logic
}
);
So:
If after processing is everything OK, you can use dialogRef in your parent and close the modal (dialogRef.close()).
If something is wrong, modal will not be closed.
Using this way, our modal will be free of business logic. Especially important for generic modals, like confirmation, user inputs, ...
You can pass your url , options etc as data in your dialog component and pass it in save function like below.
UserComponent
openDialog(): void {
let ApiDetails = {
url: 'https://gorest.co.in/public-api/users'
}
const dialogRef = this.dialog.open(DialogComponent,
{
data: ApiDetails
});
}
DialogComponent
export class DialogComponent implements OnInit {
constructor(
private dialogRef: MatDialogRef<DialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: any) {
}
no(){
this.dialogRef.close()
}
save(){
console.log(this.data) // this will have url and other details whatever you send from calling parent
}
}
I have an Ionic application where I have created a component to show some data of an object. My problem is that when I update the data in the parent that hosts the component the data within the component does not update:
my-card.component.ts
#Component({
selector: 'my-card',
templateUrl: './my-card.html'
})
export class MyCard {
#Input('item') public item: any;
#Output() itemChange = new EventEmitter();
constructor() {
}
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData().subscribe(data => {
if (data.item){
this.item = data.item;
}
this.itemChange.emit(this.item);
});
}
}
my-card.html
<div class="comment-wrapper" *ngFor="let subitem of item.subitems">
{{subitem.title}}
</div>
And in the parent I use the component like this:
<my-card [(item)]="item"></my-card>
And the ts file for the parent:
#IonicPage()
#Component({
selector: 'page-one',
templateUrl: 'one.html',
})
export class OnePage {
public item = null;
constructor(public navCtrl: NavController, public navParams: NavParams) {
this.item = {id:1, subitems:[]};
}
addSubItem():void{
// AJAX call to save the new item to DB and return the new subitem.
this.addNewSubItem().subscribe(data => {
let newSubItem = data.item;
this.item.subitems.push(newSubItem);
}
}
}
So when I call the addSubItem() function it doesnt update the component and the ngFor loop still doesnt display anything.
You are breaking the object reference when you are making the api request. You are assigning new value, that is overwriting the input value you get from the parent, and the objects are no longer pointing to the same object, but item in your child is a completely different object. As you want two-way-binding, we can make use of Output:
Child:
import { EventEmitter, Output } from '#angular/core';
// ..
#Input() item: any;
#Output() itemChange = new EventEmitter();
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData(item.id).subscribe(data => {
this.item = data;
// 'recreate' the object reference
this.itemChange.emit(this.item)
});
}
Now we have the same object reference again and whatever you do in parent, will reflect in child.
If the getMoreData method returns an observable, this code needs to look as follows:
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData().subscribe(
updatedItem => this.item = updatedItem
);
}
The subscribe causes the async operation to execute and returns an observable. When the data comes back from the async operation, it executes the provided callback function and assigns the item to the returned item.
You declared item with #Input() decorator as:
#Input('item') public item: any;
But you use two-way binding on it:
<my-card [(item)]="item"></my-card>
If it is input only, it should be
<my-card [item]="item"></my-card>
Now if you invoke addSubItem() it should display the new added item.
this.item = this.getMoreData();
The getMoreData() doesn't make sense if you put it in your card component as you want to use the item passed via #Input()
Your component interactions are a little off. Check out the guide on the Angular docs (https://angular.io/guide/component-interaction). Specifically, using ngOnChanges (https://angular.io/guide/component-interaction#intercept-input-property-changes-with-ngonchanges) or use a service to subscribe and monitor changes between the parent and the child (https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service).