Skip to content

Commit 728a41d

Browse files
siarheihuzarevichshuzarevich
andauthored
fix: wait for progressive virtual render before fLoaded (#284)
* fix: wait for progressive virtual render before fLoaded Fixes #281 * chore(package): bump version to 18.2.1 - Updated version from 18.1.0 to 18.2.1 in package.json - Ensured consistency in versioning for release --------- Co-authored-by: shuzarevich <s.huzarevich@redcore.group>
1 parent cc393ab commit 728a41d

6 files changed

Lines changed: 126 additions & 8 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "18.1.0",
2+
"version": "18.2.1",
33
"description": "Angular-native node-based UI library for building node editors, workflow builders, and interactive graph interfaces.",
44
"author": "Siarhei Huzarevich",
55
"homepage": "https://flow.foblex.com",

projects/f-flow/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@foblex/flow",
3-
"version": "18.2.0",
3+
"version": "18.2.1",
44
"description": "Angular-native node-based UI library for building node editors, workflow builders, and interactive graph interfaces.",
55
"main": "index.js",
66
"types": "index.d.ts",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Component } from '@angular/core';
2+
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
4+
import { FFlowModule } from '../f-flow.module';
5+
import { FComponentsStore } from '../f-storage';
6+
import { FFlowComponent } from './f-flow.component';
7+
8+
@Component({
9+
standalone: true,
10+
imports: [FFlowModule],
11+
template: `
12+
<f-flow (fLoaded)="loadedIds.push($event)">
13+
<f-canvas></f-canvas>
14+
</f-flow>
15+
`,
16+
})
17+
class HostComponent {
18+
public loadedIds: string[] = [];
19+
}
20+
21+
describe('FFlowComponent', () => {
22+
beforeEach(async () => {
23+
await TestBed.configureTestingModule({
24+
imports: [HostComponent],
25+
}).compileComponents();
26+
});
27+
28+
it('should wait for progressive rendering before emitting fLoaded', fakeAsync(() => {
29+
const fixture = TestBed.createComponent(HostComponent);
30+
fixture.detectChanges();
31+
32+
const flowDebugElement = fixture.debugElement.query(By.directive(FFlowComponent));
33+
const store = flowDebugElement.injector.get(FComponentsStore);
34+
35+
store.beginProgressiveRender();
36+
store.emitNodeChanges();
37+
38+
tick(300);
39+
fixture.detectChanges();
40+
41+
expect(fixture.componentInstance.loadedIds).toEqual([]);
42+
43+
store.endProgressiveRender();
44+
45+
tick(300);
46+
fixture.detectChanges();
47+
48+
expect(fixture.componentInstance.loadedIds.length).toBe(1);
49+
expect(fixture.componentInstance.loadedIds[0]).toContain('f-flow-');
50+
}));
51+
});

projects/f-flow/src/f-flow/f-flow.component.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ import { FMediator } from '@foblex/mediator';
3636
import { F_DRAGGABLE_PROVIDERS, FDraggableDataContext } from '../f-draggable';
3737
import {
3838
EmitConnectionsChangesRequest,
39+
FComponentsStore,
3940
F_STORAGE_PROVIDERS,
4041
ListenConnectionsChangesRequest,
41-
ListenNodesChangesRequest,
4242
} from '../f-storage';
4343
import { BrowserService } from '@foblex/platform';
4444
import { afterNextPaint, debounceTime, FChannelHub, notifyOnStart, takeOne } from '../reactivity';
@@ -75,6 +75,7 @@ export class FFlowComponent extends FFlowBase implements OnInit, AfterContentIni
7575
private readonly _destroyRef = inject(DestroyRef);
7676
private readonly _mediator = inject(FMediator);
7777
private readonly _browser = inject(BrowserService);
78+
private readonly _componentsStore = inject(FComponentsStore);
7879
private readonly _cache = inject(F_CACHE_OPTIONS);
7980
private readonly _injector = inject(Injector);
8081
private readonly _worker = inject(FConnectionWorker);
@@ -111,14 +112,20 @@ export class FFlowComponent extends FFlowBase implements OnInit, AfterContentIni
111112
}
112113

113114
private _listenNodesChanges(): void {
114-
this._mediator
115-
.execute<FChannelHub>(new ListenNodesChangesRequest())
115+
new FChannelHub(
116+
this._componentsStore.nodesChanges$,
117+
this._componentsStore.progressiveRenderChanges$,
118+
)
116119
.pipe(notifyOnStart(), debounceTime(SORT_ITEM_LAYERS_DEBOUNCE_MS), afterNextPaint())
117120
.listen(this._destroyRef, () => {
118121
if (this._mediator.execute(new IsDragStartedRequest())) {
119122
return;
120123
}
121124

125+
if (this._componentsStore.hasPendingProgressiveRender) {
126+
return;
127+
}
128+
122129
this._mediator.execute(new SortItemLayersRequest());
123130
this._mediator.execute<void>(new EmitConnectionsChangesRequest());
124131

projects/f-flow/src/f-storage/f-components-store.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export class FComponentsStore {
2727
public readonly nodesChanges$ = new FChannel();
2828
private _nodesRevision = 0;
2929

30+
public readonly progressiveRenderChanges$ = new FChannel();
31+
private _pendingProgressiveRenderCount = 0;
32+
3033
public get connectionsRevision(): number {
3134
return this._connectionsRevision;
3235
}
@@ -35,6 +38,10 @@ export class FComponentsStore {
3538
return this._nodesRevision;
3639
}
3740

41+
public get hasPendingProgressiveRender(): boolean {
42+
return this._pendingProgressiveRenderCount > 0;
43+
}
44+
3845
public get flowHost(): HTMLElement {
3946
return this.fFlow?.hostElement as HTMLElement;
4047
}
@@ -69,6 +76,20 @@ export class FComponentsStore {
6976
this.connectionsChanges$.notify();
7077
}
7178

79+
public beginProgressiveRender(): void {
80+
this._pendingProgressiveRenderCount++;
81+
this.progressiveRenderChanges$.notify();
82+
}
83+
84+
public endProgressiveRender(): void {
85+
if (!this._pendingProgressiveRenderCount) {
86+
return;
87+
}
88+
89+
this._pendingProgressiveRenderCount--;
90+
this.progressiveRenderChanges$.notify();
91+
}
92+
7293
public transformChanged(): void {
7394
this.transformChanges$.notify();
7495
}

projects/f-flow/src/f-virtual/f-virtual-for.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import {
55
Input,
66
NgZone,
77
OnChanges,
8+
OnDestroy,
89
SimpleChanges,
910
TemplateRef,
1011
ViewContainerRef,
1112
} from '@angular/core';
13+
import { FComponentsStore } from '../f-storage';
1214

1315
interface FVirtualContext<T> {
1416
$implicit: T;
@@ -19,15 +21,18 @@ interface FVirtualContext<T> {
1921
selector: '[fVirtualFor][fVirtualForOf]',
2022
standalone: true,
2123
})
22-
export class FVirtualFor<T> implements OnChanges {
24+
export class FVirtualFor<T> implements OnChanges, OnDestroy {
2325
@Input() fVirtualForOf: readonly T[] = [];
2426
@Input() fVirtualForTrackBy?: (index: number, item: T) => unknown;
2527

2628
private readonly _vc = inject(ViewContainerRef);
2729
private readonly _tpl = inject<TemplateRef<FVirtualContext<T>>>(TemplateRef);
2830
private readonly _zone = inject(NgZone);
31+
private readonly _componentsStore = inject(FComponentsStore, { optional: true });
2932

3033
private _views: EmbeddedViewRef<FVirtualContext<T>>[] = [];
34+
private _rafId: number | null = null;
35+
private _isProgressiveRenderActive = false;
3136

3237
ngOnChanges(changes: SimpleChanges): void {
3338
if (changes['fVirtualForOf']) {
@@ -36,14 +41,25 @@ export class FVirtualFor<T> implements OnChanges {
3641
}
3742
}
3843

44+
ngOnDestroy(): void {
45+
this._reset();
46+
}
47+
3948
private _reset(): void {
49+
if (this._rafId !== null) {
50+
cancelAnimationFrame(this._rafId);
51+
this._rafId = null;
52+
}
53+
54+
this._finishProgressiveRender();
4055
this._vc.clear();
4156
this._views.length = 0;
4257
}
4358

4459
private _renderProgressively(): void {
4560
const FRAME_BUDGET = 10; // ms
4661
let index = 0;
62+
this._startProgressiveRender();
4763

4864
this._zone.runOutsideAngular(() => {
4965
const pump = () => {
@@ -62,11 +78,34 @@ export class FVirtualFor<T> implements OnChanges {
6278
}
6379

6480
if (index < this.fVirtualForOf.length) {
65-
requestAnimationFrame(pump);
81+
this._rafId = requestAnimationFrame(pump);
82+
83+
return;
6684
}
85+
86+
this._rafId = null;
87+
this._finishProgressiveRender();
6788
};
6889

69-
requestAnimationFrame(pump);
90+
this._rafId = requestAnimationFrame(pump);
7091
});
7192
}
93+
94+
private _startProgressiveRender(): void {
95+
if (this._isProgressiveRenderActive) {
96+
return;
97+
}
98+
99+
this._isProgressiveRenderActive = true;
100+
this._componentsStore?.beginProgressiveRender();
101+
}
102+
103+
private _finishProgressiveRender(): void {
104+
if (!this._isProgressiveRenderActive) {
105+
return;
106+
}
107+
108+
this._isProgressiveRenderActive = false;
109+
this._componentsStore?.endProgressiveRender();
110+
}
72111
}

0 commit comments

Comments
 (0)