aWdas
aWdas

Reputation: 145

How to create a new object for each emission from an RxJS Observable?

I have a Service that calculates the solution to a given Sudoku (implementation unimportant) and publishes each iteration of the calculation in a RxJS Observable.

solve() : Observable<Field[][]> {
    let observable = Observable.create((o: Observer<Field[][]>)=> {
        let iterationCount : number = 0;
        while (!this.sudokuGame.isFinished() && iterationCount < 500) {
            iterationCount++;
            this.eliminateOptions();

            o.next(this.sudokuGame.boardFields);
        }
        if(this.sudokuGame.isFinished())
            o.complete();
        else
            o.error("The game could not be finished after 500 iterations.");
    });
    return observable;
}

I do this so that I can later use Observable.zip() to combine this observable with a Observable.interval() Observer to display the iterations this solving-process goes through with a delay noticeable by humans in my Angular2-component.

However, when I set the data emitted by the Observable in my Angular2-component like so:

Observable.zip(this.sudokuSolverService.solve(),Observable.interval(500),(obs, timer) => {return obs}).subscribe((nextBoardFields: Field[][]) => {
        this.sudokuGame.boardFields = nextBoardFields;
    });

Then the emissions of the Observable are delayed, but the changes show up instantly nonetheless. I suspect this being caused by me copying the nextBoardFields-Array by reference, however I am unaware of any methods to unlink the boardFields-Array in my Angular2-controller from the boardFields-Array in my sudoku-solving service.

Is there any possibility of doing this?

Edit: The only thing that comes to my mind is to somehow make the Field class immutable (perhaps with the help of Immutable.js), which seems a bit complicated for this matter.

Upvotes: 3

Views: 1803

Answers (3)

aWdas
aWdas

Reputation: 145

Maarek's answer pushed me in the right direction.

The key was that even if I copied the Field[][]-Array with methods such as [].concat(nextBoardFields) or Maarek's Object.assign(this.sudokuGame.boardFields, nextBoardFields), the individual Field's would still refer to the same objects.

To deep copy this array, I made use of o.next(JSON.parse(JSON.stringify(this.sudokuGame.boardFields))) which allowed me to use my Observable as intended.

Upvotes: 0

user3743222
user3743222

Reputation: 18665

I guess the problem here with your solve observable is that it emits its values synchronously and in one go. So in the same tick you have all the onNext called, and also the onComplete. So when you zip, it gets the first value, waits for the timer, but in the meanwhile, your observable has emitted all its values on that same tick. What you probably need to do is to have your iteration variable linked to the timer, such as :

Observable.interval(500).scan(function(_, iterationCount){
  // your logic here
  return {
    iterationCount : iterationCount
    isFinished : // your logic here
    boardFields : // your logic here
  }
}, {})
.doWhile(function(x){return x.iteration < 500 && !x.isFinished})
.map(function(x){return x.boardFields})

Upvotes: 2

Maarek
Maarek

Reputation: 596

I've used Object.assign() to get a by value copy of an object and break the connection to the observable/observer. Would that work in your case?

Observable.zip(this.sudokuSolverService.solve(),Observable.interval(500),(obs, timer) => {return obs}).subscribe((nextBoardFields: Field[][]) => {
    Object.assign(this.sudokuGame.boardFields, nextBoardFields);
});

Upvotes: 0

Related Questions