Not load data onClose dialog ? using Angular - javascript

I want to trigger load data ( which also load on init ) when user close dialog.
in AddUser-component.ts i have any logic and only important part
public onClose: Subject<any> = new Subject<any>();
this.addService.addNewUser(user).subscribe(res => {
this.onClose.next(res.body)
this.bsModalRef.hide();
}
and when trigger this in main component AllUser-components.ts i have
public addNewUser() {
this.bsModalRef = this.modalService.show(AddUserComponent);
(this.bsModalRef.content as AddUserComponent).onClose.subscribe(singleUserFromChildComponent)=>{
this.loadUser(); // HERE IS PROBLEM... when i console.log this.allUsers i don't see new added user .....
if (bill) {
this.lastAddedItem(this.allUsers, singleUserFromChildComponent);
}
this.bsModalRef.hide()
})
}
and above i have loadUser
private loadUser() {
this.service.getAllUsers(
).subscribe((res) => {
this.allUsers = res.body!;
})
}
this.allUsers var is not updated....not load new user.... why ? I load data and allUsers need to be updated with new data
this.allUsers = res.body!;
but not... i don't know why ?

Well its because the loadUser() method is asynchronous, and while you are loading it on modal close, it does not finish yet (also having nested subscriptions is a kind of bad practice, you can read more in that thread Why nested subscription is not good?)
What I can suggest you is to remove the subscribe invocation from loadUser method and return just an observable:
private loadUser() {
return this.service.getAllUsers().pipe(map((res: any) => res.body!),tap((res: any) => this.allUsers = res));
}
In your OnInit (or constructor method initialize allUsers in this way.
this.loadUser().pipe(first()).subscribe();
and then refactor your modal close part with the switchMap( it will wait untill your service.getAllUsers finished)
public addNewUser() {
this.bsModalRef = this.modalService.show(AddUserComponent);
(this.bsModalRef.content as AddUserComponent).onClose
.pipe(tap(singleUserFromChildComponent => this.singleUserFromChildComponent = singleUserFromChildComponent), switchMap(addedUser => (this.loadUser()))).subscribe(_ => {
if (bill) {
this.lastAddedItem(this.allUsers, this.singleUserFromChildComponent);
}
this.bsModalRef.hide()
});
}
also please add the lastAddedUser property to your component
public singleUserFromChildComponent: any;

Related

How to make API response updates automatically in angular

API Model contains cpu usage categories which keeps on updating dynamically in API. But When I refresh page then only its updates Data but can It be done without refreshing page in the view. Setimeout Works fine is there any method other than setTimeoutInterval?
//app.service.ts
private behaviourData = new BehaviorSubject<Model>(null);
getBPmInfo(): Observable<Model>{
return this.http.get<Model>(`${this.url}`).pipe(
retry(3),
tap(data => this.behaviourData.next(data))
);
}
//app.component.ts
model!: Model;
ngOnInit() {
getInformation(){
this.bpmService.getBPmInfo().subscribe(data => {
this.model = data;
});
}
}
//app.component.html
<div>
<h1>CPU Usage{{model?.cpuUsage}}</h1>
</div>
But data should be updated dynamically without refreshing page. Tried with BehaviourSubject I dont know why its not working. Can anyone help me on this please.
The method of your service should just return a Observable. If you really want to use a BehaviorSubject, it should be defined in your "app.component.ts" like so:
private behaviourData = new BehaviorSubject<Model>(null);
public model$: Observable<Model>;
constructor {
this.model$ = this.behaviourData.asObservable();
}
You should then dismiss the "tap(data => this.behaviourData.next(data))" in your service and move it to the component.
And finally, just subscribe to the observable in your ngInit and forget about the "getInformation()" method like so:
ngOnInit() {
this.bpmService.getBPmInfo().subscribe(data => {
this.behaviourData.next(data)
});
}
And in your template, you just use the "model$" observable like so:
<div>
<h1 *ngIf="(model$ | async) as model">CPU Usage{{model.cpuUsage}}</h1>
</div>
You can use Interval(milliseconds) to fetch data . Usage :
counter = interval(60000); // sets 60 seconds interval
subscription: any = null;
ngOnInit(): void {
this.subscription = this.counter.subscribe(f => {
this.bpmService.getBPmInfo().subscribe(data => {
this.model = data;
});
})
}

Why my code works with arrow function and broken with bind?

I have thought that bind(this) is an equivalent of the arrow function however I have met an issue(
This is a store which purpose to track delayed requests. I am using the orbitjs library to save everything into IndexedDB. It provides api which allows to subscribe to db changes, so here my store:
export class DelayedRequestsStore implements IDelayedRequests {
add = addHttpRequest;
list = queryHttpRequests;
remove = removeHttpRequest;
#observable private _delayedRequestsCount = 0;
#computed get any(): boolean {
return this._delayedRequestsCount > 0;
}
#computed get empty(): boolean {
return !this.any;
}
constructor(db: Sources) {
db.source.on('update', () => {
this._updateDelayedRequestsCount();
});
this._updateDelayedRequestsCount();
}
private async _updateDelayedRequestsCount(): Promise<void> {
const delayedRequests = await this.list();
runInAction(() => {
this._delayedRequestsCount = delayedRequests.length;
});
}
}
See that code on constructor
constructor(db: Sources) {
db.source.on('update', () => {
this._updateDelayedRequestsCount();
});
this._updateDelayedRequestsCount();
}
And some code on react:
<button onClick={async () => {
await notifyServer();
await clearRequestsFromIndexedDb();
goToAnotherPage();
})>Cancel</button>
Everything works well until I have not changed the constructor code to
constructor(db: Sources) {
db.source.on('update', this._updateDelayedRequestsCount.bind(this));
this._updateDelayedRequestsCount();
}
With that change I have not seeing any errors in console but Cancel button is not working. I have debugged and found that notifyServer has been called, then clearRequestsFromIndexedDb has been called but goToAnotherPage not called, like if error occured in clearRequestsFromIndexedDb, but no errors. SO I rollbacked back to arrow function everything works again. Does it can affect anything? Or the problem actually in some other place I am missing?
I see that you bound this only to db.source.on('update', ... ). But the call for this._updateDelayedRequestsCount() in your constructor has no binding. This could be a problem.
You can explicitly bind this to every call of you method like this:
constructor(db: Sources) {
this._updateDelayedRequestsCount = this._updateDelayedRequestsCount.bind(this);
db.source.on('update', this._updateDelayedRequestsCount);
this._updateDelayedRequestsCount();
}
Maybe it will fix your problem.

What's an angular lifecycle-hook need when changing sets in componentInstance property by service?

I have a component that i send to MdDialog(Angular Material Dialog in my custom service.ts)
dialogRef = this.dialog.open(component, config);
And when I change a public property of this component by componentInstance like that:
dialogRef.componentInstance.task = task;
Angular shows me an error:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'dialog'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?
Full code of open-modal.service.ts
#Injectable()
export class TasksPopupService {
constructor(
private dialog: MdDialog,
private router: Router,
private tasksService: TasksService
) { }
public open(component: any, id?: string) {
if (id) {
this.tasksService.find(id)
.subscribe(task => {
this.bindDialog(component, task);
});
} else {
this.bindDialog(component, new Task());
}
}
bindDialog(component, task: Task) {
let dialogRef;
let config = new MdDialogConfig();
config.height = '80%';
config.width = '70%';
dialogRef = this.dialog.open(component, config);
dialogRef.componentInstance.task = task;
dialogRef.afterClosed().subscribe(res => {
this.router.navigate([{ outlets: { popup: null } }], { replaceUrl: true });
});
return dialogRef;
}
}
But an error occured only if id is undefined (in ELSE block) I think it's because of this.tasksService.find return Observable (async), and block ELSE is not async. But I'm not sure.
I has some confuse becouse error eccured in MdContainer of Angular Material.
If i get data from server it's need some time, but when i pass a new object it's occur fast and change detection is not finished if i understend right.
Also, it's not parent/child component and lifecycle hooks maybe not works as we expect.
I found solution, but it's not right. Just fast solution.
if (id) {
this.tasksService.find(id)
.subscribe(task => {
this.bindDialog(component, task);
});
} else {
Observable.of(new Task()).delay(300).subscribe(task => {
this.bindDialog(component, task);
});
}
I use delay for change detection has finished and error will not throw.

(Closed) Integrate delete subject for an existing search stream

I've been working on a personal app. My goal is to have a search and delete users using Observables. For search functionality I have used this idea, this is how I implemented:
export class UserComponent implements OnInit {
users: Observable<User[]>;
private searchTerms = new Subject<string>();
ngOnInit(): void {
this.setUsers();
}
private setUsers(): void {
this.users = this.searchTerms
.debounceTime(300) // wait 300ms after each keystroke before considering the term
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time the term changes
// return the http search observable
? this.userService.search(term)
// or the observable of all users if there was no search term
: this.userService.search(''))
.catch(() => {
this.response = -1;
return Observable.of<User[]>([]);
});
}
}
For delete functionality I use this:
private deleteSubject = new Subject();
delete(user: User): void {
this.userService
.delete(user)
.then(() => {
this.deleteSubject.next('');
})
.catch(() => {
console.log('error');
});
}
private setUsers(): void {
... // here comes the search functionality shown above
this.subscription = this.deleteSubject.subscribe(
(term: string) => this.users = this.userService.search(''),
(err) => console.log(err)
);
}
The problem that I faced with this implementation is that after I delete an user my search functionality is broken because I lost the subscriber for searchTerms because I reassigned this.users. So I've been searching and found that I can use merge operator to append another subject, follow this idea. Sadly I'm facing a problem with the implementation. I tried this:
delete(user: User): void {
...
// change
this.deleteSubject.next('');
// with
this.deleteSubject.next({op: 'delete', id: user.id});
}
private setUsers(): void {
...
this.users.merge(this.deleteSubject)
.startWith([])
.scan((acc: any, val: any) => {
if (val.op && val.op === 'delete') {
let index = acc.findIndex((elt: any) => elt.id === val.id);
acc.splice(index, 1);
return acc;
} else {
return acc.concat(val);
}
});
But my scan operator is never execute because this.deleteSubject doesn't have a subscriber for it. I know how to create and associate a subscriber to my this.deleteSubject but don't know how to mix it with my merge operator. Also see that it's getting a bit complex, I guess it should be another way to get this done. Comments are appreciated.
Please take a look at my plunker, hope gets a bit clear. I've simulated the calls to my services using a memory array userList.
I fixed my problem, I had to do two changes:
Assign this.users.merge(this.deleteSubject) to this.users
In the else block for scan operator change return acc.concat(val); to return val;
I updated my plunker if you want to take a look.
Cheers.

How to call a function after the completion of two Angular 2 subscriptions?

I am on Angular 2.3.1 and I am fairly new to both Angular and event based programming. I have two subscriptions, route.params.subscribe and engineService.getEngines(). In my onInit I want to call getEngineName after this.route.params.subscribe and this.engineService.getEngines().subscribe complete.
Reason for this: getEngineName functions depends on the engineId from the queryParams and the engines array which is populated after the completion of getEngines() call.
I did look at flatMap and switchMap but I did not completely understand them.
This is the code in the component:
export class ItemListComponent implements OnInit {
items: Item[];
engines: Engine[];
private engineId: number;
constructor(
private router: Router,
private route: ActivatedRoute,
private itemService: ItemService,
private engineService: EngineService
) {}
ngOnInit() {
this.route.params.subscribe((params: Params) => {
this.engineId = +params['engineId'];
// itemService is irrelevant to this question
this.itemService.getItems({engineId: this.engineId})
.subscribe((items: Item[]) => {
this.items = items;
});
});
this.engineService.getEngines()
.subscribe(engines => this.engines = engines);
// This should only run after engineId and engines array have been populated.
this.getEngineName(this.engineId);
}
getEngineName(engineId: number) {
this.engines.find((engine) => {
return engine.id === engineId;
})
}
}
Why don't you just move the logic inside the route.params callback?
this.route.params.subscribe((params: Params) => {
this.engineId = +params['engineId'];
// itemService is irrelevant to this question
this.itemService.getItems({engineId: this.engineId})
.subscribe((items: Item[]) => {
this.items = items;
});
//this.engineId is defined here (1)
this.engineService.getEngines()
.subscribe((engines) => {
this.engines = engines;
//this.engines is defined here (2)
this.getEngineName(this.engineId);
});
});
with flatMap and forkJoin:
this.route.params.flatMap((params: Params) => {
this.engineId = +params['engineId'];
return Observable.forkJoin(
this.itemService.getItems({engineId: this.engineId}),
this.engineService.getEngines()
)
}).subscribe((data)=>{
let items = data[0];
let engines = data[1];
this.items = items;
this.engines = engines;
this.getEngineName(this.engineId);
});
switchMap is recommended in this scenario.
this.route.params.pluck('engineId') //pluck will select engineId key from params
.switchMap(engineId => {
this.getItems(engineId);
return this.engineService.getEngines().map(engines => {
/*this.engineService.getEngines() emits the list of engines.
Then use map operator to convert the list of engines to engine we are looking for
*/
return engines.find((engine) => {
return engine.id === engineId;
})
})
}).subscribe(engine => {
//engine
})
getItems(engineId) {
this.itemService.getItems({engineId: engineId})
.subscribe((items: Item[]) => {
this.items = items;
});
}
Suppose the engineId in the params changes, the first observable this.route.params.pluck('engineId') will emit data, which will cause the next observable this.engineService.getEngines() to get fired. Now suppose the route changes before this observable emits the data. Here you need to cancel getEngines observable to prevent error. This is done by switchMap.
switchMap cancels inner observable if outer observable is fired.
PS: I have avoided keeping any states like this.engineId etc.

Categories

Resources