I have array of object with following data :
[
{
"name":"Uber",
"points":20,
"nodeName":"C=GB,L=London,O=Uber",
"port":10007,
"nodeType":"merchant"
},
{
"name":"Starbucks",
"points":20,
"nodeName":"C=US,L=New York,O=Starbucks",
"port":10010,
"nodeType":"merchant"
},
{
"name":"KFC",
"points":20,
"nodeName":"C=US,L=New York,O=KFC",
"port":10013,
"nodeType":"merchant"
}
]
I want to loop through this array and show determinate progress bar with animation and also should see points increasing in view shown in below image.for points attribute and want to show it one after another. i.e first for Uber progress bar displayed which will load till 100% for points of uber. and so on for starbucks and then for KFC.
I have tried below code ,where res is my above array:
res.forEach((v, i) => {
Observable.timer(100).subscribe((i) => {
let interval = Observable.interval(100).subscribe((i) => {
this.percentageCom = (this.points / this.PointsAdded) * 100;
if (this.percentageCom === 100) {
// this.isHidden = true;
interval.unsubscribe();
}
})
});
});
let timer = Observable.interval(100).subscribe(() => {
this.points++;
if (this.points === 60) {
timer.unsubscribe();
// this.router.navigate(['/main/dashboard']);
}
});
and HTML :
<div class="vendor">{{merchantName|| 'Uber'}}</div>
<mat-progress-bar mode="determinate" value="{{percentageCom }}"></mat-progress-bar>
But above code not display my progress bar one after another as those things are asynchronous,some weird output being shown i.e. simultaneously displaying progress bar .
Is there any way to show above loader one after another for each element of array ?
UPDATED
My Use case is as follow :
From service response I get Array of object as mention above
Lets consider first item of that array of object. It has points 20; so in my View ,points should increase from 0 to 20(just like counter)
So while this point increment happens till 20, I want to show percentage Progress bar for Uber in this case which will run full 100%.
Once above 2 points complete for one object i.e. uber same should happen for next item in array of object.
Above are the steps that i want to implement. But as interval and timer are async, i am not able to run it one after another by looping abpve object.
I have messed up code there in timer and interval...somehow I couldnt get throuugh it !
I don't exactly find the purpose of mocking that loading behavior, but I think that you are looking for something like this code snipped. I think too, and the example shows, that you can remove the points variable.. but I am not sure because I did not understand 100% the context on where you are.
const res = [
{
"name":"Uber",
"points":20,
"nodeName":"C=GB,L=London,O=Uber",
"port":10007,
"nodeType":"merchant"
},
{
"name":"Starbucks",
"points":20,
"nodeName":"C=US,L=New York,O=Starbucks",
"port":10010,
"nodeType":"merchant"
},
{
"name":"KFC",
"points":20,
"nodeName":"C=US,L=New York,O=KFC",
"port":10013,
"nodeType":"merchant"
}
];
Rx.Observable.from(res)
.map( (value) => {
return Rx.Observable.interval(100)
.scan((acc, curr) => acc + 1)
.take(value.points+1)
.map(currentPoints => {
const percentage = (currentPoints / value.points) * 100;
return {arrayItem: value, percentage: percentage}
})
})
.concatAll()
.subscribe((data) => {
console.log(data.arrayItem.name + ': ' + data.percentage);
});
<script src="https://npmcdn.com/#reactivex/rxjs#5.0.0-beta.8/dist/global/Rx.umd.js"></script>
Edit: I edited the solution, now is in serie.
Explanation:
First we convert our array into an stream. 'Observable.from(res)'.
For every item on the array, now in the stream, we map it into an Observable.interval(100)
We take that Observable interval and we count the times it emits with scan and we finish it taking only as items as point the array item have.
After that we map an return the value with its current percentage.
The concatAll() operator just concatenates the observable sequence we have.
Finally the subscribe method only shows a console log of the result
I'm not sure, but I think you are doing an interval in the wrong place. You should do this on http get, then your subscribers will catch changes on observables with intervals. What you are looking for probably is called polling request and is high frequencies here
helpful article, related issue
example component.ts file:
import { Component, OnInit } from '#angular/core';
import { Service } from './service';
#Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {
constructor(private service: Service) { }
ngOnInit() {
this.service.getNewValue()
.subscribe((res) => console.log(res));
}
}
example service.ts file:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs'; // ! Dont forget to Import Observable !
#Injectable()
export class Service {
constructor(private http: Http) { }
getNewValue = () => {
return Observable
.interval(1000)
.flatMap((i) => this.http.get("http://yourhost.com/api"))
}
}
didn't test it but should fix your problem
You are creating a new timer for each item. I don't think that is what you want.
I think you want something like this:
res.forEach((v, i) => {
//Add the point here because this is where your processing happens I guess?
this.points++;
});
//Move it outside of your loop
$timer = Observable.timer(100).subscribe((i) => {// $timer as a local variable. 1000/60 might be better as it will refresh for every frame.
this.percentageCom = (this.points / this.PointsAdded) * 100;
if (this.percentageCom === 100) {
// this.isHidden = true;
this.$timer.unsubscribe();
}
});
Also, this will probably process so fast, you won't see it.
Related
i have a component called DipComponent.ts and in this component i have a function named submit, this function pass 3 datas to a function in another page called DipService.ts(ine array, one number and another array). the last array(an array of objects) is full of data in the page DipComponent.ts but when i try to read it in the DipService.ts it looks empty.
here is my code of DipComponent.ts:
import { Component, Input, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs';
import { competenza } from '../interfaces/competenza';
import { dipendente } from '../interfaces/dipendente';
import { AssenzeService } from '../services/assenzeservice';
import { CompetenzeService } from '../services/competenzeservice';
import { DipendentiService } from '../services/dipendenteservice';
#Component({
selector: 'app-modificadip',
templateUrl: './modificadip.component.html',
styleUrls: ['./modificadip.component.css']
})
export class ModificadipComponent implements OnInit {
[x: string]: any;
public dipendentemodificato: any[] = [];
public dip: dipendente[] = [];
public competenze: competenza[]=[];
public competenzeDip: competenza[]=[];
public competenzeposs = [];
public optionsMap=<any>[];
public optionsChecked = <any>[];
public options=<any>[];
public stringtemp: string=''
public Comps=<any[]>([])
public idcomp!: number[];
constructor (private route : ActivatedRoute, public DS:DipendentiService, public
CS:CompetenzeService, public AS:AssenzeService) {
var id=this.route.snapshot.params.id
this.DS.getdipendentebyid(id).toPromise().then(data =>{console.log(data);
for(let i in data){
this.dip.push(data[i]);
}
});
this.CS.getcompetenze().toPromise().then(data =>{console.log(data);
for(let i in data){
this.competenze.push(data[i]);
}
});
this.CS.getcompetenzebyiddipendente(id).toPromise().then(data =>{console.log(data);
for(let i in data){
this.competenzeDip.push(data[i]);
}
});
}
ngOnInit(): void {
this.initOptionsMap();
}
submit(login: any){
console.log("submit dipendente", login)
for(let i in login){
this.dipendentemodificato.push(login[i]);
}
console.log("modifiche del dipendente: "+this.dipendentemodificato)
//----------------
for(var x in this.optionsMap) {
if(this.optionsMap[x]) {
this.optionsChecked.push(x);
}
}
for (var k = 0; k<this.optionsChecked.length; k++) {
console.log("id competenza "+this.optionsChecked[k])
this.CS.getcompetenzabyid(this.optionsChecked[k]).toPromise().then(data =>{console.log("datas: "+JSON.stringify(data));
this.Comps.push(data);
});
}
console.log(this.Comps)
//----------------
this.DS.postmoddipendenti(this.dipendentemodificato, this.route.snapshot.params.id, this.Comps)
}
initOptionsMap() {
for (var x = 0; x<this.competenze.length; x++) {
this.optionsMap[x] = true;
}
}
updateCheckedOptions(option:number, event:any) {
this.optionsMap[option] = event.target.checked;
console.log("dopo aggiornamento "+this.optionsMap[option])
}
}
here is my code of the interested function of DipService.ts:
postmoddipendenti(raw: any,id:number,comp=<any[]>([])){
console.log("::::::"+ comp)
var dip = '{' +'"id"'+':'+id+','+'"nome"'+':'+'"'+raw[0]+'"'+','+'"cognome"'+':'+'"'+raw[1]+'"'+','+'"data_nascita"'+':'+'"'+raw[2]+'"'+','+'"mail"'+':'+'"'+raw[3]+'"'+','+'"telefono"'+':'+raw[4]+','+'"setCompetenze"'+':'+'[]' +'}'
console.log("non json ->"+ dip)
//const obj = JSON.parse(dip);
//console.log("obj ->"+ obj)
// this.http.post<any>('http://localhost:8080/postdipendenti', obj).subscribe(
// (val) => {console.log("POST call successful value returned in body", val);
// })
}
thank you and sorry for my bad english.
I think your variable this.Comps is empty because you fill it in an asynchronous way :
this.CS.getcompetenzabyid(this.optionsChecked[k]).toPromise().then(data =>{console.log("datas: "+JSON.stringify(data));
this.Comps.push(data);
});
So when you do
this.DS.postmoddipendenti(this.dipendentemodificato, this.route.snapshot.params.id, this.Comps)
Maybe the array is not filled yet.
What does your console.log(this.Comps) display ? If it's an empty array I think it's because of that.
So in that case you should wait for all the events to finish.
In your case you use .toPromise() so you could do a Promise.all instead of a forEach.
But note that, in most cases, it's recommended to avoid Promises in Angular and to keep using Observables. You can check the Angular documentation and RxJs library if you want to know more about that.
Anyway for now I don't see over explanations besides asynchronism.
EDIT:
An example to show you how await Promises and Promise.all() work. But you can (and should) find much more detailed tutorials about that on the web.
So when you do
this.CS.getcompetenzabyid(this.optionsChecked[k]).toPromise()
you will get a promise. It's not a value you can directly use yet. It will resolve and give you the final value but you don't know exactly when. For example, it's what happens when you run a SQL request from your code. You don't know how long the request will take.
And Javascript is a "non-blocking" language. This means it will not wait for the DB to answer, it does something else to not waste time. And in our case something else means "Continue to execute the code".
The Promise object we get before allow javascript to take care of the result when it finally arrive (and when that happens you can be really far in your code already).
So to take care of this Promise you have 2 ways :
Or you give a callback to the then keyword.
Or you use the keyword await to "block" the code
With the then approach (what you used in your code) you can't force javascript to not execute the code written after your then.
But with the await you can. You would write something like that :
const data = await this.CS.getcompetenzabyid(this.optionsChecked[k]).toPromise()
Like that you would get the result of the Promise (when it's resolved) in the variable "data". And with the await keyword javascript will wait the result without executing anymore code.
So to sum up you can just write:
const data = await this.CS.getcompetenzabyid(this.optionsChecked[k]).toPromise(); this.Comps.push(data);
And you will be sure that this.Comps will be up to date. If you have only one request to do.
But in your case you have few promises because you do few requests in your forEach. So you need to wait for all of them. For that we can use the function Promise.all() that will wait all the Promises you give him.
So in your case :
const datas = [];
for (var k = 0; k<this.optionsChecked.length; k++) {
//create array of Promises
datas.push(this.CS.getcompetenzabyid(this.optionsChecked[k]).toPromise());
}
// Wait for all the promises to resolve and get all the datas
const resolvedDatas = await Promise.all(datas);
this.Comps = resolvedDatas;
Like this you should have all the values from your request in the Comps variable. And you sure that the call to the service will not be done before you get all the datas.
I hope it's clear. I tried to make it short so don't hesitate to look for more informations online.
And I hope it will solve your issue. But even if it doesn't it can avoid bugs in the future.
Within my Angular app i ve the following treatment :
OnInit on lauching a subscibe from a subject call (SubjectOne)
when there is a new data coming from SubjectOne ,
and if some condition is verified ; i reeuse this data to launch a second call whitch is a http call from a service call .
Here is my code
MyComponent.ts :
ngOnInit() {
this.getFirstTreatment();
}
getFirstTreatment() {
this.subscriptionOne = this.myService.subjectOne.subscribe((data) => {
this.myValue = data['myValue'];
this.getSecondTreatment(data['myValue'])
})
}
getSecondTreatment(thatValue) {
if(thatValue >= 100){
this.subscriptionTwo = this.myService.sendToBackend(thatValue).subscribe((response)=>{}
}
}
MyService.ts
sendToBackend(thatValue){
let newValue = someFormatingnMethod(thatValue)
return this.httpClient.post(url , newValue );
}
My Purpose is how may i dynamically close the subscribtionTwo so it won't be called n times after each time i got new data from the subject .
NB : mySubject can notice some new Data even before the destroy of the compoment
I ve tried to use switchMap , but it seems to not work correctly
Suggestions ?
You are starting with one observable
That observable stays open after it has emitted a value, so we need to unsubscribe
You then want to conditionally run a second observable based on the result of the first observable
I would take this approach:
Set up your first observable as you are currently doing
Use takeUntil to unsubscribe on destroy
Use filter to only continue based on a condition
Use switchMap to run the second observable
The second observable is an HttpClient request, which self-completes, so we don't need to unsubscribe
private destroyed$ = new Subject();
ngOnInit() {
getFirstTreatment();
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
getFirstTreatment() {
this.myService.subjectOne.pipe(
takeUntil(this.destroyed$),
tap(data => this.myValue = data['myValue']),
filter(data => data['myValue'] >= 100),
switchMap(data => this.getSecondTreatment(data['myValue']))
).subscribe(data => {
console.log(data); // the output of the second observable
});
}
getSecondTreatment(myValue): Observable<any> {
return this.getSecondTreatment(myValue);
}
I'm trying to optimize the speed (happens to make my code faster). How can I optimize below piece of code so that load time get optimize further. And if possible, please suggest what I should usually keep in mind.
As of now Finish time = 2.45sec.
TS
export class AppComponent implements OnInit {
searchKeywords: string;
CoffeeItemList: any = [];
type: string;
search: string;
selectedType = '';
showLoader: boolean;
empty = false;
data: any = [];
// tslint:disable-next-line:max-line-length
constructor(private getDataListingService: DataListingService) {}
ngOnInit(): void {
this.getGlobalSearchList('');
this.getAllData();
this.getSmartSearchValues('');
if (this.CoffeeItemList.length === 0) {
this.empty = true;
}
}
getAllData() {
this.showLoader = true;
this.getDataListingService.getAllDataLists().subscribe(value => {
this.CoffeeItemList = value.data;
this.showLoader = false;
});
}
getGlobalSearchList(type: string) {
this.selectedType = type;
this.CoffeeItemList = [];
this.getDataListingService.getAllDataLists().subscribe(value => {
this.data = value.data;
console.log(this.data);
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < this.data.length; i++) {
if (this.data[i].type === type) {
this.CoffeeItemList.push(this.data[i]);
}
}
});
}
getSmartSearchValues(search: string) {
if (search === '' ) {
this.getAllData();
return false;
}
if (search.length >= 3) {
this.getDataListingService.searchList(search).subscribe(value => {
this.data = value.data;
this.CoffeeItemList = value.data;
// check selected type either coffee, mobile or ALL.
if (this.selectedType && this.selectedType !== '' ) {
this.CoffeeItemList = [];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < this.data.length; i++) {
if (this.data[i].type === this.selectedType) {
this.CoffeeItemList.push(this.data[i]);
}
}
}
});
}
}
}
I can see that you call your service getDataListingService three times in ngOnInit() and every time you make a request and, I suppose, you collect data and then you work on data.
I'd like to see your HTML file as well. Maybe you don't need to make that much requests on init.
First of all you call getAllDataLists twice why?
Can't one of these subscribe be made redundant?
Second try to filter out more data cause atleast 1 second for a call is alot of data getAllDataLists should be filter before you load all the data into your application.
Either it's to much data so you should check your network tab. Or the html is to complicated and load way to much data and that's why your aplication is getting so slow.
Also I see you're making the same mistakes with subscription?
You know it will trigger everytime data get changed? So with your current setup and if you subscribe on every keyup you will create 100 of subsciptions that never finish and keep poling for changes.
https://stackoverflow.com/a/60389896/7672014
Based on the code you have shared:
You need to add some sort of pagination to your 'getAllDataLists' & 'searchList' methods i.e. you always get results in chunks instead of getting larger data results to render on the UI.
The following methods when called are performing API calls that can all be combined into 1 single API call to reduce the memory load and reduce wait time for user to see results/data being rendered on the page.
this.getGlobalSearchList('');
this.getAllData();
this.getSmartSearchValues('');
Optimize your API call e.g. send the 'selectedType' in the API itself to already get filtered results from the backend instead of adding a check of if (this.selectedType && this.selectedType !== '' )
Let's say we have this global const:
const isSignedIn = fromPromise(fetch('/api/is-signed-in'))
.pipe(throttleTime(1000), shareReply(1));
After page load, several components will subscribe to this at the same time:
isSignedIn.subscribe(() => console.log('do 1st'));
isSignedIn.subscribe(() => console.log('do 2nd'));
isSignedIn.subscribe(() => console.log('do 3rd'));
The above will only call the API once, however i need it to call the API again (ie after 1 second) if another component subscribes to it.
isSignedIn.subscribe(() => console.log('button press'));
How do i that using RxJS?
I think this is what you want:
A pipeable operator (declare globally somewhere and import it)
export const refreshAfter = (duration: number) => (source: Observable<any>) =>
source.pipe(
repeatWhen(obs => obs.pipe(delay(duration))),
publishReplay(1),
refCount());
Then use it like this:
data$ = fetch('/api/is-signed-in').pipe(refreshAfter(5000)); // refresh after 5000 ms
Note: You actually asked for this:
i need it to call the API again (ie after 1 second) if another component subscribes to
it.
Not quite sure this is what you really meant. I think what you really meant was - you want the data to be refreshed for all components currently subscribed after an expiry time. Anyway my answer sends the new value to all listeners. If you really want what you originally said you'd need to add some kind of alternative repeat trigger.
But if this is for a global constant - the above is what I'm using for the same scenario.
Note: I haven't actually tested the handling of an error condition when the item is repested, but I think the error will propagate to all listeners.
If we reimplement ShareReplay so it:
- will never unsubscribe from source even if it have no more subscribers (remove refCount, potential memory leak).
- accept rerunAfter argument, time passed from last subscribe to source.
import {Subject, of, Observable, ReplaySubject, Subscriber} from 'rxjs';
import {pluck, shareReplay, tap, delay} from 'rxjs/operators';
function shareForeverReplayRerun<T>(bufferSize: number, rerunAfter: number) {
let subject;
let subscription;
let hasError = false;
let isComplete = false;
let lastSubTime = 0;
return source => Observable.create((observer: Subscriber<T>) => {
if (!subject || hasError || (Date.now() - lastSubTime) >= rerunAfter) {
lastSubTime = Date.now();
hasError = false;
subject = new ReplaySubject<T>(bufferSize);
subscription = source.subscribe({
next(value) { subject.next(value); },
error(err) {
hasError = true;
subject.error(err);
},
complete() {
isComplete = true;
subject.complete();
},
});
}
const innerSub = subject.subscribe(observer);
// never unsubscribe from source
return () => {
innerSub.unsubscribe();
};
})
}
const source = of('Initial').pipe(
tap(()=>console.log('COMPUTE')),
delay(200),
shareReplayRerun(1, 1000),
);
source.subscribe(console.log.bind(null, 'syncI:'));
source.subscribe(console.log.bind(null, 'syncII:'));
setTimeout(()=>source.subscribe(console.log.bind(null, 'after500:')), 500);
setTimeout(()=>source.subscribe(console.log.bind(null, 'after900:')), 900);
setTimeout(()=>source.subscribe(console.log.bind(null, 'after1500:')), 1500);
as output we have:
COMPUTE
syncI: Initial
syncII: Initial
after500: Initial
after900: Initial
COMPUTE
after1500:Initial
EDITED: The answer is wrong. BufferSize is how long the last N events are replayed. After this the stream is completed.
signature: shareReplay(
bufferSize?: number,
windowTime?: number,
scheduler?: IIScheduler
):Observable
#param {Number} [bufferSize=Number.POSITIVE_INFINITY] Maximum element count of the replay buffer.
#param {Number} [windowTime=Number.MAX_VALUE] Maximum time length of the replay buffer in milliseconds.
Try to add 1000 as second argument to shareReply:
const isSignedIn = fromPromise(fetch('/api/is-signed-in'))
.pipe(throttleTime(1000), shareReplay(1, 1000));
shareReplay.ts - be care of refCount-- on unsubcribe as it can trigger additional requests.
I am building a Chat application, and when a new message gets added to a list, I need to update the chat item that contains the message list.
I am using AngularFire2, and have an Observable. This Observable works perfectly to dynamically maintain a list as expected.
I do however, have a conundrum. When the list changes I need to perform an update which in turn causes the list to change, and so on resulting in an infinite loop.
code:
The following is the Observable
findMessages(chatItem: any): Observable<any[]> {
return this.af.database.list('/message/', {
query: {
orderByChild: 'negativtimestamp'
}
}).map(items => {
const filtered = items.filter(
item => ((item.memberId1 === chatItem.memberId1 && item.memberId2 === chatItem.memberId2)
|| (item.memberId1 === chatItem.memberId2 && item.memberId2 === chatItem.memberId1))
);
return filtered;
});
}
I populate a list with this Observable
findAllMessages(chatItem: any): Promise<any> {
return this.firebaseDataService.findMessages(chatItem).forEach(firebaseItems => {
chatItem.lastMsg_text = 'updated value';
this.firebaseDataService.updateChat(chatItem);
});
}
As you can see, I update the chatItem
updateChat(chatItem: any): firebase.Promise<void> {
return this.findChat(chatItem).forEach((chatItems: any[]) => {
if (chatItems.length > 0) {
for (let i: number = 0; i < chatItems.length; i++) {
return this.af.database.object('/chat/' + chatItems[i].$key).update({
timestamp: chatItem.timestamp,
negativtimestamp: chatItem.negativtimestamp,
lastMsg_text: chatItem.lastMsg_text,
lastMsg_read1: chatItem.lastMsg_read1,
lastMsg_read2: chatItem.lastMsg_read2
});
}
} else {
this.createChatFromItem(chatItem);
}
});
}
Question
My confusion here, is that I am updating the chats and not the messages, so why is the messages Observable being triggered? Is there a way to achieve this avoiding the infinite loop?
Any help appreciated.
Seems like you could handle all of this in a much simpler way... The way you mixed Promises & Observables feels a bit convoluted.
Have you looked into Subject?
To put it very simply, you could have a Subject "publishing" any changes in your chat application by calling
yourSubject.next(newChatMessage)
From then on, subscribing to that Subject from any parts of your application, you'd be able to make any updates necessary.
Then if you want to dig deeper into the subject of state management, you could have a look at the following:
RxJS powered state management for Angular applications, inspired by Redux