Current Working Code
I have the following rxjs/Observable:
findMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
let processing: boolean = false;
this.firebaseDataService.findMessages(chatItem).forEach(firebaseItems => {
if (!processing) {
processing = true;
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
let mergedItems: any[] = this.arrayUnique(firebaseItems.concat(localItems), false);
mergedItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
if (this.me && mergedItems && mergedItems[0] && this.me.uid === mergedItems[0].memberId2) {
this.updateChatWithMessage(chatItem, mergedItems[0], false);
}
observer.next(mergedItems);
processing = false;
});
}
});
});
}
and
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
As you can see, it returns a list of firebaseItems and localItems, which are merged to mergedItems. This works perfectly.
Performance Enhancement
However, I am trying to increase the performance that the items load. So figure, I would like to first load the localItems, and then add to the list with the firebaseItems.
So I try add the following function:
findLocalMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
localItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
observer.next(localItems);
});
});
}
and call it as follows:
this.firelist = this.dataService.findLocalMessages(this.chatItem);
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
Problem
This has now introduced a bug, that there are now 2 Observables and the results are not as expected. The sort order is incorrect, and some of the items are not being added to the this.firelist for some reason.
Question
Whats the best way to handle this?
I was thinking if it's possible to make the findLocalMessages Observable only get fired once, and then it never works again, as the findMessages Observable will maintain the list. Is this possible? I have been looking at the Observable api, and can't seem to figure out how to do that.
Any help appreciated.
With the risk of oversimplifying the problem statement, you have two streams of data that you want to merge and sort in an efficient manner.
The separation you have made is a step in the right direction.
The reason why you are not getting all the messages is that you are overriding the first observable with the second.Have a look at the following example and see what happens if you try and assign the second observable to move instead of move2.
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
move2
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
<h1>
<div id="1">
aaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div id="2">
bbbbbbbbbbbbbbbbbbbbbbbbb
</div>
</h1>
In order to merge the two streams together properly you need to use the merge operator as shown below:
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move.merge(move2)
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
Now all you need to do is sort them. I would advice that you do the sort only after the merge because otherwise, you will end up with two streams that are sorted locally, not globally.
Related
I am working on one Reactjs project- where i have cities and areas of each city ,in each city it may have more the 200 areas . Each area is having 3 attributes cityId,AreaID ,isAdded,. And city is have one attribute cityId.
Here i need to store Areas of each city in a separate array.How can i optimize this operation
export const getAreas = (cityID,allAreas) => {
try {
let areas = [];
if (allAreas) {
allAreas?.forEach((area) => {
if (area?.cityID === cityID) {
areas.push(area);
}
});
return areas;
}
} catch (error) {
console.log(error);
}
};
const areasPerCity = new Map();
allAreas.forEach((area) => {
if (!areasPerCity.get(area.cityId)) {
areasPerCity.set(area.cityId, []);
}
areasPerCity.get(area.cityId).push(area);
});
return areasPerCity;
// here, you have a Map of a city ID => array of areas
// you could use it like `const areas = areasPerCity.get(cityId);`
Complexity is O(n) with one iteration.
You can use useMemo hook. it can memorise your returned value.
https://www.w3schools.com/react/react_usememo.asp
The fastest JavaScript iteration by FAR is the basic for loop, so something like this would be much faster:
export const getAreas = (cityID,allAreas) => {
try {
let areas = [];
if (allAreas) {
for(let area = 0; area < allAreas.length; area++){
if (allAreas[area].cityID === cityID) {
areas.push(allAreas[area]);
}
});
return areas;
}
} catch (error) {
console.log(error);
}
};
If by optimise you mean wanted to shorten your provided snippet, the below is a shorter version of your example. The Array.prototype.filter array method works great here for filtering down your initial array.
const getAreas = (cityID, allAreas) => allAreas.filter((area) => area.cityID === cityID)
Although this isn't much faster if you were looking to optimise for algorithm speed. If you want to improve this, the answers found here "What's the fastest way to loop through an array in JavaScript?" might help you speed up the loop which will have the biggest impact on performance.
class IBSProgressExtension extends Autodesk.Viewing.Extension{
constructor(viewer, options) {
super(viewer, options);
}
load() {
//For proof of concept project, I will simply store the externalIds here in a variable.
const allExternalIds = [
'8a00f4c7-0709-4749-88b6-abb0ddccf965-0006879a',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-000688ee',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068961',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068963',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a78',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0d',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0f',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a11',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a13',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c2f',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c31',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c33',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b2e',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b30',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b32',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b34',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3e',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b36',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b38',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3a',
'8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3c'
];
this.viewer.model.getExternalIdMapping(data => onSuccessMapping(data));
function onSuccessMapping(data) {
const resArray = [];
allExternalIds.forEach(externalId => {
if (data[externalId]) resArray.push(data[externalId], externalId);
});
console.log(resArray);
};
console.log('IBSProgressExtension is loaded.');
return true;
}
};
Autodesk.Viewing.theExtensionManager.registerExtension("IBSProgressExtension", IBSProgressExtension);
Please have a look at my extension and please help me figure out why is this happening.
Every time i run it, the devtools logs: ViewerExtension.js:31 Uncaught TypeError: Cannot read properties of undefined (reading 'getExternalIdMapping').
The extensions get loaded before the model so the getExternalIdMapping() method does not have the model properties yet. To handle this scenario, we usually recommend using the viewer events such as Autodesk.Viewing.GEOMETRY_LOADED_EVENT to “catch” the moment when the model is available. It’s better to wait for the event. This will be fired when the model/drawing finishes loading.
Instead of:
this.viewer.model.getExternalIdMapping(data => onSuccessMapping(data));
Try this:
this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, (x) => {
this.viewer.model.getExternalIdMapping(data => onSuccessMapping(data));
});
Please test this and see if it's helpful. I tried to incorporate items from your comments to help you structure it out.
class IBSProgressExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this._externalIds = null;
//Eventually will want to pass in your external IDs to this function, I assume:
//this._externalIds = options.externalIds
this._doStuff = () => {
this.startDoingStuff();
};
}
load() {
console.log("loading extension");
//For now, hard coded like your example.
this._externalIds = [
"8a00f4c7-0709-4749-88b6-abb0ddccf965-0006879a",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-000688ee",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068961",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068963",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a78",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0d",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a0f",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a11",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068a13",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c2f",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c31",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068c33",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b2e",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b30",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b32",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b34",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3e",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b36",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b38",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3a",
"8a00f4c7-0709-4749-88b6-abb0ddccf965-00068b3c",
];
//Not sure if this is truly the right event, but it worked when I tested on mine.
this.viewer.addEventListener(Autodesk.Viewing.MODEL_LAYERS_LOADED_EVENT, this._doStuff);
}
startDoingStuff() {
console.log("startDoingStuff executing");
this.getDbIds(this._externalIds).then((CombinedArray) => {
this.setCustomColors(CombinedArray);
});
}
setCustomColors(arrayOfIDs) {
console.log("setCustomColors executing");
var somecolor = "#7D5B51";
var threecolor = new THREE.Color(somecolor);
var vectcolor = new THREE.Vector4(threecolor.r, threecolor.g, threecolor.b, 1);
arrayOfIDs.forEach((e) => {
this.viewer.setThemingColor(e[0], vectcolor, this.viewer.getVisibleModels()[0]);
});
}
getDbIds(externalIds) {
console.log("getDbIds executing");
return new Promise((resolve) => {
this.viewer.model.getExternalIdMapping((d) => {
//console.log("getDbIdFromExternalId Executed");
let responseArr = [];
externalIds.forEach((externalId) => {
if (d[externalId]) responseArr.push([d[externalId], externalId]);
});
console.log("resolving", responseArr);
resolve(responseArr);
});
});
}
}
Autodesk.Viewing.theExtensionManager.registerExtension("IBSProgressExtension", IBSProgressExtension);
Regarding this, I was trying to achieve 3 things at this stage.
Get the externalIds from Mongodb.
Compare the externalIds with the ones gotten from getExternalIdMapping().
Get DbIds of those that matched.
Solved these by realising 2 and 3 can be put inside a.then() after .loadDocumentNode().
I am using Ionic3 with a rxjs/Observable. I have the following function, and for some reason, even though the function is only called once, the 3rd line gets fired twice.
findChats(): Observable<any[]> {
return Observable.create((observer) => {
this.chatSubscription2 = this.firebaseDataService.findChats().subscribe(firebaseItems => {
this.localDataService.findChats().then((localItems: any[]) => {
let mergedItems: any[] = [];
if (localItems && localItems != null && firebaseItems && firebaseItems != null) {
for (let i: number = 0; i < localItems.length; i++) {
if (localItems[i] === null) {
localItems.splice(i, 1);
}
}
mergedItems = this.arrayUnique(firebaseItems.concat(localItems), true);
} else if (firebaseItems && firebaseItems != null) {
mergedItems = firebaseItems;
} else if (localItems && localItems != null) {
mergedItems = localItems;
}
mergedItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
observer.next(mergedItems);
this.checkChats(firebaseItems, localItems);
});
});
});
}
Problem
This is causing a problem because this.chatSubscription2 is taking the value of the second subscription, and the first subscription gets lost, not allowing me to ever unsubscribe.
line 2 is executed once
line 3 is executed twice
Question
How do I create an Observable with only one subscription?
Thanks
UPDATE
I change the code to the following using share(), but the 3rd line still gets executed twice:
findChats(): Observable<any[]> {
return Observable.create((observer) => {
const obs = this.firebaseDataService.findChats().share();
this.chatSubscription2 = obs.subscribe(firebaseItems => {
....
As other users have suggested, while findChats is only called once, it would seem that the observable it returns is subscribed to multiple times. create returns a cold observable which will cause all the internal logic to be executed for each subscription. You can whack a share on the end of the whole thing (i.e. outside / after the create call) to test this, but I would suggest the solution would actually be simpler if you did not use create at all, and rather just mapped / flatMapped / switchMapped your original stream into your desired stream (to avoid manual subscription management).
Little context, I have an API that returns a competition with a link to its matches and each match has a link to the teams that played.
My intention for now, is to build a standings table based on the competition matches therefore, my first step is to print a list of unique "teams" from that array of matches. That is I need to go through all the matches, check the home and away teams, and if the team is not present, push it into the list.
Here is the tricky part, I have the teams like this:
match.away = this.teamService.getTeam(match._links.awayTeam.href);
match.home = this.teamService.getTeam(match._links.homeTeam.href);
And my getTeam() method is returning a promise Team.
getTeam(id: string): Promise<Team> {
return this.http.get(id)
.toPromise()
.then(response => response.json() as Team)
.catch(this.handleError);
}
The Team class has teamName: string; which is the value I need to verify to build the array and that is the part I need help with. I have an OnInit implemented that goes like this:
ngOnInit(): void {
this.teams = [];
this.route.params
.switchMap((params: Params) => this.competitionService.getCompetition(+params['id']))
.subscribe(competition => {
this.competitionService.getMatches(competition)
.then(
matches => {
matches.forEach(match => {
match.away = this.teamService.getTeam(match._links.awayTeam.href);
match.home = this.teamService.getTeam(match._links.homeTeam.href);
match.away.then(away => {
var found = this.teams.find(team => {
return team.teamName.toLocaleLowerCase().includes(away.teamName)
});
if(!found) {
this.teams.push(away);
}
});
match.home.then(home => {
var found = this.teams.find(team => {
return team.teamName.toLocaleLowerCase().includes(home.teamName)
});
if(!found) {
this.teams.push(home);
}
});
});
return matches;
}
).then(matches =>
this.matches = matches
);
this.competition = competition
});
}
What I tried to do there is resolve the Promise first and check for the teams' presence in my array and if it is !found then push it to the list.
My HTML looks like this:
<md-list>
<md-list-item *ngFor="let team of teams">
<button md-raised-button>{{team.teamName}}</button>
</md-list-item>
</md-list>
But the result is a list of each team in each match, so appearantly the push part is working because the array teams: Team[]; is being populated and printed correctly, but the verification is not.
Please don't kill me for my code cause I have 0 idea on this, I'm just learning on the fly, ot only Angular but to code.
So my Question Is:
How do I build an array of unique teams from that promise of matches?
Furthermore, how do you usually split arrays for these calculations? Every research I found points me to a "Custom Pipe" but it seems to be filtering the view only, After I have my array of teams, I have to make calculations on each of the matches for Goals, points, etc, so it does not seem that it will work for me.
Any ideas or suggestions?
I'd add a new method to delegate the teams array population:
addToSet(team): void {
let teamNames: string[] = this.teams.map((team) => team.teamName.toLocaleLowerCase());
if (teamNames.lastIndexOf(team.teamName.toLocaleLowerCase()) === -1) {
this.teams.push(team);
}
}
And then, substitute this part
var found = this.teams.find(team => {
return team.teamName.toLocaleLowerCase().includes(home.teamName)
});
if(!found) {
this.teams.push(home);
}
for
this.addToSet(home);
Also, to be sure that both promises are fulfilled before going on with the array loading, you could wrap both getTeam calls with Promise.all.
Promise.all([
this.teamService.getTeam(match._links.awayTeam.href),
this.teamService.getTeam(match._links.homeTeam.href)
]).then((teams) => {
match.away = teams[0];
match.home = teams[1];
});
EDIT: putting it all together
addToSet(team): void {
let teamNames: string[] = this.teams.map((team) => team.teamName.toLocaleLowerCase());
if (teamNames.lastIndexOf(team.teamName.toLocaleLowerCase()) === -1) {
this.teams.push(team);
}
}
ngOnInit() {
// rest of the initialization
matches.forEach(match => {
Promise.all([
this.teamService.getTeam(match._links.awayTeam.href),
this.teamService.getTeam(match._links.homeTeam.href)
]).then(teams => {
match.away = teams[0];
match.home = teams[1];
this.addToSet(away);
this.addToSet(home);
});
});
}
EDIT 2: added toLocaleLowerCase() in addToSet() for check. I'm assuming the getTeam returns a single object, otherwise, you'll need to enforce the returned object schema.
I have a stream where the events look something like this:
{
endOfSequence : false,
sequenceId: 12345,
data: [.....]
}
I need to terminate the sequence when endOfSequence === true. I started with takeWhile:
seq = stream.takeWhile( function(event){
return !event.endOfSeq;
});
but the problem is that I miss the last event.
I can obviously write code that accomplishes the same thing, for example:
function beforeEnd(event){
return !event.endOfSeq;
}
seq = stream.takeWhile(beforeEnd)
.merge(stream.skipWhile(beforeEnd).take(1));
But this is a bit ugly. Is there a better way?
You can write a custom handler with Bacon.withHandler that emits a Bacon.End() when you get the endOfSequence.
seq.withHandler(function(event) {
var ret = this.push(event)
if(event.hasValue() && event.value().endOfSequence) {
ret = this.push(new Bacon.End())
}
return ret
})
A working example can be found in this jsFiddle.
Accepted answer adapted for for baconjs v3:
takeUntilLastEventSatisfies = (predicate, observable) =>
observable.transform(
(baconEvent, sink) => {
if (!Bacon.hasValue(baconEvent)) {// error or end
return sink(baconEvent);
}
if (predicate(baconEvent.value)) {
sink(baconEvent);
return sink(new Bacon.End());
}
return sink(baconEvent);
})
);