Update view from Behaviersubject

Hello,
I’m back and maybe I have forgotten all. Okay my experience with rxjs is very, very low.

What I wanna do ist, that i calculate in a loop some time comsuming things, After each step I wanna update my view, for example ‘step {{value | async}} is done’ and update a mat progressbar. I made a stackblitz with a structur simalar to that I have at https://stackblitz.com/edit/angular-hello-async-pipe-input-ufib6h

@Component({
  selector: "my-app",
  template: `
    <p>count$: {{ count$ | async }}</p>
    
    <button (click)="doCount()">count</button>
  `
})
export class AppComponent {
  count$ = new BehaviorSubject<number>(0);
  count = 0;

  constructor() {
    
  }

  doCount() {
    for (let i = 0; i < 10; i++) {
      this.doImportant();

      this.count$.next(i);
      console.log(i);
    }
  }

  doImportant() {
    let n = 0;
    for (let i = 0; i < 500000000; i++) {
      n = n + 0;
    }
  }
}

It shows the first value and last value, but it does not count like 1, 2, 3, 4…

Thanks advance and best regards, anna-liebt

Likely angular change detection will not keep up with the loop. Console log shows all?

Hello,
…changedection keep not up with the loop. Sorry, I’m not sure howto understand that.

Console logs every i. Output is after the loop finished.

Well, I have tried in my project so much things, but this is very time comsumming. So I made a faster stackblitz and see there the same behavior. I think it is a complete wrong understanding on my side.

So what I mean to understand is, that with async pipe I need no subscripe and unsubscripe of my behaviersubject.

Best reagards, anna-liebt

Ideally, this would be a job for a service worker. If that’s not possible, you’re going to have to break doImportant into smaller chunks - it can’t be a single monolithic operation, because you need to yield control now and again so that the UI can be updated.

Yes, when using the async pipe you do not need to manually manage subscriptions, but that is tangential to the situation.

The for loop goes extremely fast and the changes are not detected by the angular tmeplate that holds the async. So most updates are not shown in the browser windows

Unless doImportant takes a while to complete (like a second)

It doesn’t matter how much wall clock time doImportant takes. It has to allow the JavaScript engine to come up for air (which, at the end of the day, means that at some level things have to be scheduled in chunks using setTimeout, either directly or indirectly).

Hello @Tommertom and @rapropos,

great to see you again.

I used a loop because that I can change how fast it is. In the wild there a 0.02 seconds and more than 5 seconds to do one loop. In sum It will take between 10seconds and more than 15 minutes to complete. Mostly on the longer side. So I will inform the user by a message that the last task is done and and a mat progressbar to visualize it.

I’ve seen some examples, most with setTimeout. I thought it just simulated a long doing.
I have become so confused by all the trying out that I don’t bring any clear thoughts together anymore. So if someone could show me the way using the stackblitz example, I would be very grateful. Apology for begging.

In shame , anna.liebt

hello,

okay I have added a SetTimeout. to the example at https://stackblitz.com/edit/angular-hello-async-pipe-input-ufib6h
And yes with a 5000 in timeout it is updating the view. But I believe my understanding is still wrong.So how people do that where you can see the numbers are changing very fast, for example at a progress bar , etc.
Hmm. I’m confused.

Best regards, anna-liebt

hello,

forget it. With setTimeout it iseems it is counting after he has done the loop or…whatever. I’m done.

hello,
after another long time waisting session, I have tried: Observable, Behaviersubjects, settimeout, increaseprozess with callback, promises, await promises, ngzone.run, faked buttonclicks, ngDoCheck, creating new arrays, tick, detectChanges, markforCheck, @Input, @Input with Behaviersubject, ChangeDetectionStrategy.OnPush in various combination. In many of these trys console.log exactly what I want. Before the next loop start I get from everywhere the new values outputet to console, but I never get the view updatet The only thing I haven’t tried is service worker.

I can believe that I am dump like a stone, but I can not believe there is not a simple way to update the view before the next loop starts.

Best regards, anna-liebt

Update the element not using angular template but adressing the dom native element

U can use angular to get the element ref or just search for a plain vanilla js solution on the web for your feature like a progress indicator

Hello,

thanks for that suggestion. I will try it in a few days, because it is better doing other todo to get the mind free. I will report then.

Best regrards, anna-liebt

JavaScript has only a single thread of execution. Everything you see that makes it look like web apps can do multiple things at once is a complete fiction, built of smoke and mirrors. The original Macintosh was like this, and old school Mac programmers (like me) remember WaitNextEvent. You had to call this system function frequently, or your app would appear to lock up the entire computer. Behind the scenes, WaitNextEvent would pause your app and allow other applications (and the OS) to take control of the CPU and do stuff.

So what this really comes down to is how to structure your big loop. The mechanics of “how do I update the view?” is not where I would recommend you focus your effort. If you would describe in more detail what this big loop is doing, we could give more targeted recommendations, but for now I can only speak in generics. We’re going to walk through a big cube, rendering voxels.

interface Point3 {
  x: number;
  y: number;
  z: number;
}
type Cube = Point3;

The single smallest unit of work we can do is to render a single voxel:

renderVoxel(pt: Point3): void {
  // do stuff
}

So here’s a naive implementation that “stops the world” while rendering the whole cube:

advance(pt: Point3, world: Cube): Point3 | null {
  let rv = {x: pt.x + 1, y: pt.y, z: pt.z};
  if (rv.x >= world.x) { rv.x = 0; ++rv.y; }
  if (rv.y >= world.y) { rv.y = 0; ++rv.z; }
  if (rv.z >= world.z) { rv = null; }
  return rv;
}

renderWorld(world: Cube): void {
  let pt: Point3 = { x: 0, y: 0, z: 0 };
  while (pt) {
    this.renderVoxel(pt);
    pt = this.advance(pt);
  }
}

Now let’s try to add progress feedback:

volume(cube: Cube): number {
  return cube.x * cube.y * cube.z;
}
percentDone(pt: Point3 | null, world: Cube): number {
  return pt ? (this.volume(pt) / this.volume(world) * 100) : 100;
}
progress = 0;
renderAndAdvance(pt: Point3, world: Cube): Point3 | null {
  this.renderVoxel(pt);
  let rv = this.advance(pt);
  this.progress = this.percentDone(rv, world);
  return rv;
}
renderWorld(world: Cube): void {
  let pt: Point3 | null = { x: 0, y: 0, z: 0 };
  while (pt) {
    pt = this.renderAndAdvance(pt, world);
  }
}

UPDATE: actually, that volume computation won’t work for the partials, but I’m leaving it as broken for now because it’s not essential to the main point of the thread

You’ve probably gotten to more or less this point, where judicious console.log usage will watch progress going up, but can’t figure out how to reflect this in the UI. To do so, we have to stop stopping the world:

renderAndAdvance(pt: Point3, world: Cube): Promise<Point3 | null> {
  return new Promise(resolve => setTimeout(() => {
    this.renderVoxel(pt);
    let rv = this.advance(pt);
    this.progress = this.percentDone(rv, world);
    resolve(rv);
  }, 0));
}
renderWorld(world: Cube): void {
  let upto: Point3 | null = { x: 0, y: 0, z: 0 };
  let voxr$ = Promise.resolve(pt);
  while (upto) {
    voxr$ = voxr$.then(pt => {
      // yes, virginia, there is only supposed to be one '=' in that condition
      return (upto = pt) ? this.renderAndAdvance(pt, world) : null;
    });
  }
}

its a very bad way to write it this way in my opinion.

So if you have the intent to do a progress bar use number divided by 1000 you will be able to see progress from 0 to 10…

and i would recommend interval…

hello,
thanks.i will think about that. I’m very thankful to any input that brings me on the correct way.
At the moment I’m totally confused about the many things I tried… It’s sometimes annoying to jump back and forth between different programming concepts or to do mostly completely different things. On the other hand it brings variety into my life…
So in this case the importent things, the reading, the converting, the heavy data calculations and output to charts, tables, pdfs is working. 3 things were unsolved. Since yesterday one is solved. (Damn broken documentation) The 2 others are related to why does this happen or not. 1 is these. They other I triy to solve now.

Best regards, anna-liebt

hello,
I forgot setinterval I tried to. I use a mat progressbar, my first thougt was: use a behaviousubject with async pipe bound to [value] =‘behaviersubject | async’. Console logs the process, but progressbar shows only the first and the last emitted value. So I think I must rethink whatever,.

at https://stackblitz.com/edit/angular-hello-async-pipe-input-ufib6 is a very short and simplified code, but my solution do not really different things. It loops over a few hundert items each loop calculate ten or hundret of thousand things. Before the next loop start I will update my view. For the user It looks like it is frozen.

Best regrds, anna-liebt

If you can build an array of top-level progress goals in advance, Array.reduce is another option. See the “count3” addition in this fork of your stackblitz.