Skip to content

Commit 18cafe1

Browse files
committed
refactor(core): make renderer not aware of store internally
1 parent 8c1ae6c commit 18cafe1

File tree

14 files changed

+686
-610
lines changed

14 files changed

+686
-610
lines changed

apps/kitchen-sink-new/src/app/misc/basic/scene.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
signal,
88
viewChild,
99
} from '@angular/core';
10-
import { injectBeforeRender, NgtArgs, NgtPortalDeclarations, NgtVector3 } from 'angular-three';
10+
import { NgtArgs, NgtPortalDeclarations, NgtVector3 } from 'angular-three';
1111
import * as THREE from 'three';
1212

1313
@Component({
@@ -125,13 +125,14 @@ export class Box {
125125
}
126126
-->
127127
</ngt-group>
128-
128+
<!--
129129
<ngt-portal [container]="virtualScene">
130130
<ngt-group *portalContent>
131131
<app-box />
132132
<app-condition-box />
133133
</ngt-group>
134134
</ngt-portal>
135+
-->
135136
`,
136137
imports: [NgtArgs, Box, ConditionBox, NgtPortalDeclarations],
137138
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -152,16 +153,16 @@ export class Scene {
152153
private groupRef = viewChild.required<ElementRef<THREE.Group>>('group');
153154

154155
constructor() {
155-
setInterval(() => {
156-
this.show.update((v) => !v);
157-
this.sphereArgs.update((v) => [v[0] === 0.5 ? 1 : 0.5, v[1], v[2]]);
158-
}, 2500);
159-
160-
injectBeforeRender(() => {
161-
const group = this.groupRef().nativeElement;
162-
group.rotation.x += 0.01;
163-
group.rotation.y += 0.01;
164-
});
156+
// setInterval(() => {
157+
// this.show.update((v) => !v);
158+
// this.sphereArgs.update((v) => [v[0] === 0.5 ? 1 : 0.5, v[1], v[2]]);
159+
// }, 2500);
160+
//
161+
// injectBeforeRender(() => {
162+
// const group = this.groupRef().nativeElement;
163+
// group.rotation.x += 0.01;
164+
// group.rotation.y += 0.01;
165+
// });
165166
}
166167

167168
onDocumentClick(event: MouseEvent) {

libs/core/dom/src/lib/canvas.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ import {
2323
} from '@angular/core';
2424
import { outputFromObservable } from '@angular/core/rxjs-interop';
2525
import {
26-
CANVAS_CONTENT_FLAG,
2726
injectCanvasRootInitializer,
2827
injectStore,
2928
is,
29+
NGT_CANVAS_CONTENT_FLAG,
3030
NGT_STORE,
3131
NgtCamera,
3232
NgtCameraParameters,
@@ -50,12 +50,13 @@ import { createPointerEvents } from './events';
5050
@Directive({ selector: 'ng-template[canvasContent]' })
5151
export class NgtCanvasContent {
5252
constructor() {
53+
const store = injectStore();
5354
const vcr = inject(ViewContainerRef);
5455
const commentNode = vcr.element.nativeElement;
5556

5657
// NOTE: flag this canvasContent ng-template comment node as the start
57-
commentNode.data = CANVAS_CONTENT_FLAG;
58-
commentNode[CANVAS_CONTENT_FLAG] = true;
58+
commentNode.data = NGT_CANVAS_CONTENT_FLAG;
59+
commentNode[NGT_CANVAS_CONTENT_FLAG] = store;
5960
}
6061
}
6162

@@ -132,7 +133,12 @@ export class NgtCanvas {
132133
// NOTE: this means that everything in NgtCanvas will be in afterNextRender.
133134
// this allows the content of NgtCanvas to use effect instead of afterNextRender
134135
afterNextRender(() => {
135-
const canvasElement = this.glCanvas().nativeElement;
136+
const [canvasVcr, canvasElement, canvasContent] = [
137+
this.glCanvasViewContainerRef(),
138+
this.glCanvas().nativeElement,
139+
this.canvasContentRef(),
140+
];
141+
136142
this.zone.runOutsideAngular(() => {
137143
this.configurator.set(this.initRoot(canvasElement));
138144
});
@@ -172,7 +178,7 @@ export class NgtCanvas {
172178
if (this.glRef) {
173179
this.glRef.detectChanges();
174180
} else {
175-
this.noZoneRender(canvasElement);
181+
this.noZoneRender(canvasElement, canvasVcr, canvasContent);
176182
}
177183
});
178184
},
@@ -186,7 +192,11 @@ export class NgtCanvas {
186192
});
187193
}
188194

189-
private noZoneRender(canvasElement: HTMLCanvasElement) {
195+
private noZoneRender(
196+
canvasElement: HTMLCanvasElement,
197+
canvasVcr: ViewContainerRef,
198+
canvasContent: TemplateRef<unknown>,
199+
) {
190200
// NOTE: destroy previous instances if existed
191201
this.glRef?.destroy();
192202

@@ -223,12 +233,7 @@ export class NgtCanvas {
223233
this.store.snapshot.events.connect?.(canvasElement);
224234
}
225235

226-
this.glRef = untracked(this.glCanvasViewContainerRef).createEmbeddedView(
227-
untracked(this.canvasContentRef),
228-
{},
229-
{ injector: this.injector },
230-
);
231-
236+
this.glRef = canvasVcr.createEmbeddedView(canvasContent, {}, { injector: this.injector });
232237
this.glRef.detectChanges();
233238
}
234239
}

libs/core/src/lib/directives/args.ts

Lines changed: 17 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,31 @@
1-
import {
2-
DestroyRef,
3-
Directive,
4-
effect,
5-
EmbeddedViewRef,
6-
inject,
7-
input,
8-
TemplateRef,
9-
ViewContainerRef,
10-
} from '@angular/core';
11-
import { SPECIAL_INTERNAL_ADD_COMMENT_FLAG } from '../renderer/constants';
12-
import { is } from '../utils/is';
1+
import { computed, Directive, input, linkedSignal } from '@angular/core';
2+
import { NGT_ARGS_FLAG, NGT_INTERNAL_ADD_COMMENT_FLAG } from '../renderer/constants';
3+
import { NgtCommonDirective } from './common';
134

145
@Directive({ selector: 'ng-template[args]' })
15-
export class NgtArgs {
6+
export class NgtArgs extends NgtCommonDirective<any[]> {
167
args = input.required<any[] | null>();
178

18-
private vcr = inject(ViewContainerRef);
19-
private template = inject(TemplateRef);
20-
21-
protected injected = false;
22-
protected injectedArgs: any[] | null = null;
23-
private view?: EmbeddedViewRef<unknown>;
9+
protected linkedValue = linkedSignal(this.args);
10+
protected shouldSkipRender = computed(() => {
11+
const args = this.args();
12+
return args == null || !Array.isArray(args) || (args.length === 1 && args[0] === null);
13+
});
2414

2515
constructor() {
26-
const commentNode = this.vcr.element.nativeElement;
27-
commentNode.data = 'args-container';
28-
if (commentNode[SPECIAL_INTERNAL_ADD_COMMENT_FLAG]) {
29-
commentNode[SPECIAL_INTERNAL_ADD_COMMENT_FLAG]('args');
30-
delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT_FLAG];
31-
}
32-
33-
effect(() => {
34-
const value = this.args();
35-
if (value == null || !Array.isArray(value) || (value.length === 1 && value[0] === null)) return;
36-
37-
if (is.equ(value, this.injectedArgs)) {
38-
// we have the same value as before, no need to update
39-
return;
40-
}
16+
super();
4117

42-
this.injected = false;
43-
this.injectedArgs = value;
44-
this.createView();
45-
});
18+
const commentNode = this.commentNode;
19+
commentNode.data = NGT_ARGS_FLAG;
20+
commentNode[NGT_ARGS_FLAG] = true;
4621

47-
inject(DestroyRef).onDestroy(() => {
48-
this.view?.destroy();
49-
});
50-
}
51-
52-
get value() {
53-
if (this.validate()) {
54-
this.injected = true;
55-
return this.injectedArgs;
22+
if (commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG]) {
23+
commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG]('args', this.injector);
24+
delete commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG];
5625
}
57-
return null;
5826
}
5927

6028
validate() {
61-
return !this.injected && !!this.injectedArgs?.length;
62-
}
63-
64-
private createView() {
65-
if (this.view && !this.view.destroyed) this.view.destroy();
66-
this.view = this.vcr.createEmbeddedView(this.template);
67-
this.view.detectChanges();
29+
return !this.injected && !!this.injectedValue?.length;
6830
}
6931
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
DestroyRef,
3+
Directive,
4+
effect,
5+
EmbeddedViewRef,
6+
inject,
7+
Injector,
8+
Signal,
9+
TemplateRef,
10+
ViewContainerRef,
11+
} from '@angular/core';
12+
import { is } from '../utils/is';
13+
14+
@Directive()
15+
export abstract class NgtCommonDirective<TValue> {
16+
private vcr = inject(ViewContainerRef);
17+
private template = inject(TemplateRef);
18+
protected injector = inject(Injector);
19+
20+
protected injected = false;
21+
protected injectedValue: TValue | null = null;
22+
private view?: EmbeddedViewRef<unknown>;
23+
24+
protected get commentNode() {
25+
return this.vcr.element.nativeElement;
26+
}
27+
28+
abstract validate(): boolean;
29+
protected abstract linkedValue: Signal<TValue | null>;
30+
protected abstract shouldSkipRender: Signal<boolean>;
31+
32+
constructor() {
33+
effect(() => {
34+
const value = this.linkedValue();
35+
if (this.shouldSkipRender()) return;
36+
37+
if (is.equ(value, this.injectedValue)) {
38+
// we have the same value as before, no need to update
39+
return;
40+
}
41+
42+
this.injected = false;
43+
this.injectedValue = value;
44+
this.createView();
45+
});
46+
47+
inject(DestroyRef).onDestroy(() => {
48+
this.view?.destroy();
49+
});
50+
}
51+
52+
get value() {
53+
if (this.validate()) {
54+
this.injected = true;
55+
return this.injectedValue;
56+
}
57+
return null;
58+
}
59+
60+
protected beforeCreateView() {
61+
/* noop */
62+
}
63+
64+
private createView() {
65+
if (this.view && !this.view.destroyed) this.view.destroy();
66+
67+
this.beforeCreateView();
68+
69+
this.view = this.vcr.createEmbeddedView(this.template);
70+
this.view.detectChanges();
71+
}
72+
}

0 commit comments

Comments
 (0)