How to make API response updates automatically in angular - javascript

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;
});
})
}

Related

API call returns empty object when called from inside Angular service

I'm getting this only when I subscribe to the function that makes api call from inside the Angular service, so the object that subscribes to the function is empty. Here's my code snippet from my service:
getSchedules(): Observable<Schedule[]> {
this.http.get<TempSchedules[]>(this.apiUrl).subscribe(x => this.temp = x);
this.temp.forEach((e, i) => {
// Do something, this loop is never executed because this.temp is empty
});
// Some processing here
return something; }
Here is my http.get function somewhere inside the service:
getTempSchedules(): Observable<TempSchedules[]> {
return this.http.get<TempSchedules[]>(this.apiUrl);
}
From the above, this.temp is empty. Why is that?
I called the above function in the service constructor as
constructor(private http:HttpClient) {
this.getTempSchedules().subscribe(x => this.temp = x);
}
Here is a code snippet from a component that calls that function in the service:
ngOnInit(): void {
this.scheduleService.getTempSchedules().subscribe(x => this.tempSchedules = x);
}
The component works fine, so when I use the value this.tempSchedules in the html it is displayed correctly. What am I missing here?
It is not working because you are not getting the way observable works. It is async process and you need to be in subscribe block to get it. In case you want to do some funky stuff with the response before returning it to the component, then you should use map
getTempSchedules(): Observable<Schedule[]> {
return this.http.get<TempSchedules[]>(this.apiUrl)
.pipe(map(res => {
return res.forEach(() => {
// Do something, this loop will be executed
})
})) }
use it in component as :
ngOnInit(): void {
this.scheduleService.getTempSchedules().subscribe(x => this.tempSchedules = x);
}

Not load data onClose dialog ? using Angular

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;

Communicating between Angular components and non-Angular components: Returning data from an observable coming from a callback

I have an application that requires that I use non-Angular JavaScript for certain things. To trigger an action in the Angular component, I'm passing a down a callback to the non-Angular component. When the callback is triggered, an observable runs on the Angular component (doing an http call). This works but the only piece of the puzzle I'm having trouble with is getting the data returned from this observable passed back down to the non-Angular component somehow. My actual application is fairly complex so I've created a Stackblitz for a much more simplified version to make it easier to see what I'm doing.
This is tricky for me as the GET call in doStuff is async, so I can't just return the results. I'd have some ideas on how to work around this in a pure Angular app... but I'm not sure how to accomplish this when sharing data between an Angular component and a Non-Angular one.
app.component.ts:
export class AppComponent {
constructor(private http: HttpClient){}
doStuff() {
let randomNum = this.getRandomInt(2); // Simulate different http responses
this.http.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`).subscribe(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
} else {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
}
});
}
ngOnInit() {
var x = new NonAngularComponent(this.doStuff.bind(this));
}
private getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max)) + 1;
}
}
NonAngularComponent.ts:
export class NonAngularComponent {
constructor(private onSave: () => void) {
this.init()
}
init() {
const newElement = document.createElement('button');
newElement.innerHTML = 'Click';
newElement.addEventListener('click', () => {
this.onSave(); // Works, but now I need to do something with the results of doStuff()
});
document.getElementById('foo').append(newElement);
}
}
Any suggestions would be much appreciated.
It would be better to return Observable from your doStuff() method and use tap operator if you want to have some side effect in Angular component:
doStuff() {
let randomNum = this.getRandomInt(2);
return this.http.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`).pipe(tap(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
} else {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
}
}));
}
non-angular.component.ts
newElement.addEventListener('click', () => {
this.onSave().subscribe(res => {
// do whatever you want
});
});
Forked Stackblitz
I think that the easiest solution would be to simply have an instance of your NonAngularComponent inside the AppComponent
this.nonAngularComponent = new NonAngularComponent(this.doStuff.bind(this));
And in the callback simply call the method you want from the NonAngularComponent like so:
doStuff() {
let randomNum = this.getRandomInt(2);
this.http
.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`)
.subscribe(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
// console.log(x.id);
this.nonAngularComponent.doSomething(x);
} else {
// Here is where I want to share data with the non-Angular component
// console.log(x.id);
this.nonAngularComponent.doSomething(x);
}
});
}
doSomething method:
public doSomething(result) {
console.log("Non-Angular component received result", result);
}
And console output:
Stackblitz: https://stackblitz.com/edit/angular-tddc7q?file=src%2Fapp%2FNonAngularComponent.ts

Get observable return value without subscribing in calling class

In TypeScript / Angular, you would usually call a function that returns an observable and subscribe to it in a component like this:
this.productsService.getProduct().subscribe((product) => { this.product = product });
This is fine when the code runs in a class that manages data, but in my opinion this should not be handled in the component. I may be wrong but i think the job of a component should be to ask for and display data without handling how the it is retrieved.
In the angular template you can do this to subscribe to and display the result of an observable:
<h1>{{ product.title | async }}</h1>
Is it possible to have something like this in the component class? My component displays a form and checks if a date is valid after input. Submitting the form is blocked until the value is valid and i want to keep all the logic behind it in the service which should subscribe to the AJAX call, the component only checks if it got a valid date.
class FormComponent {
datechangeCallback(date) {
this.dateIsValid$ = this.dateService.checkDate(date);
}
submit() {
if (this.dateIsValid$ === true) {
// handle form submission...
}
}
}
You can convert rxjs Observables to ES6 Promises and then use the async-await syntax to get the data without observable subscription.
Service:
export class DateService {
constructor(private http: HttpClient) {}
async isDateValid(date): Promise<boolean> {
let data = await this.http.post(url, date, httpOptions).toPromise();
let isValid: boolean;
// perform your validation and logic below and store the result in isValid variable
return isValid;
}
}
Component:
class FormComponent {
async datechangeCallback(date) {
this.dateIsValid = await this.dateService.isDateValid(date);
}
submit() {
if (this.dateIsValid) {
// handle form submission...
}
}
}
P.S:
If this is a simple HTTP request, which completes on receiving one value, then using Promises won't hurt. But if this obersvable produces some continuous stream of values, then using Promises isn't the best solution and you have to revert back to rxjs observables.
The cleanest way IMHO, using 7.4.0 < RxJS < 8
import { of, from, tap, firstValueFrom } from 'rxjs';
const asyncFoo = () => {
return from(
firstValueFrom(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
)
);
};
asyncFoo();
// Outputs "World" once
asyncFoo().subscribe((foo) => console.info(foo));
// Outputs "World" twice
The "more cleanest" way would be having a factory (in some service) to build these optionally subscribeable function returns...
Something like this:
const buildObs = (obs) => {
return from(firstValueFrom(obs));
};
const asyncFoo = () => {
return buildObs(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
);
};

Change Detection works intermittently when receiving data from Electron Container IPC Channel

I have an application that is listening for incoming data from an IPC Renderer Channel. Here is my setup:
container that sends data to angular app (mainWindow):
mainWindow.loadURL('http://www.myangularapp.com') //where the angular app lives (example url).
mainWindow.webContents.on('did-finish-load', () => {
const data = { name: "John Doe", address: "123 Main St", city: "NY"}
mainWindow.webContents.send('data-from-container', data)
}
})
angular app:
constructor(
private store: Store<fromStore.AppState>,
private cd: ChangeDetectorRef,
private zone: NgZone,
) {}
ngOnInit() {
this.isLoading = true;
if (this.electronService.ipcRenderer) {
this.electronService.ipcRenderer.on('data-from-container', (event, data) => {
this.zone.run(() => {
if(data){
setTimeout(() => {
console.log(data) // verified data is always received
this.formData = data; // property that form uses to populate data in the dom
this.prepopulateForm(data) // method that places incoming data in ngrx store
}, 1000)
}
})
})
}
this.store.select('containerData').subscribe(data => {
setTimeout(()=> {
console.log(data) // data is always being set in ngrx
this.isLoading = false
}, 5000);
})
}
Everytime the IPC Channel emits the 'data-from-container' event, the data is always getting received from my OnInit call, but the data doesn't always get set in my form! The pattern that i've noticed is typically that the data does not prepopulate the form when the angular app first launches inside the container, after the initial launch, every subsequent time the app is launched, the data appears.
I've tried using ngZone, setTimeout, and detectChange methods to trigger Change Detection so the Angular App can detect the newly set formData, but its not consistently prepopulating the form. Any suggestions on how to fix this?
I have a very basic knowledge of electron, so i'll try to show you the idea. For me, your problem comes from the initialization of the view. You're not loosing events because you can see them in the console but not in the view, which enforces my guesses.
As shown in your code, your are sending only one event (I suppose it's only for testing raison) and we want to show it when the view is rendered.
In your component add a subject which informes us that the view is initialized, like:
import { Subject, combineLatest, fromEvent } from 'rxjs';
viewInitialized$ = new Subject();
...
ngAfterViewInit() {
this.viewInitialized$.next();
}
...
Now we can wait the two emissions to come, one from the ipcRenderer and the other from viewInitialized$by using combineLatest operator.
Before that, we have to convert the ipcRenderer to an Observable. From this SO response we can do fromEvent(ipcRenderer,'data-from-container'). If it does not work we can use another subject that emits events each time we receive something in ipcRenderer.on(), the second solution requires ngZone.
ngOnInit() {
...
const containerData$ = fromEvent(this.electronService.ipcRenderer, 'data-from-container');
this.subscription = combineLatest(containerData$, this.viewInitialized$).subscribe(combined => {
const data = combined[0];
this.formData = data;
this.prepopulateForm(data)
})
...
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
Hope this helps.

Categories

Resources