Skip to content

Commit 3e9bf47

Browse files
committed
feat: add ref
1 parent 1ff55c3 commit 3e9bf47

File tree

1 file changed

+118
-0
lines changed
  • libs/angular-three/src/lib/di

1 file changed

+118
-0
lines changed

libs/angular-three/src/lib/di/ref.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { ChangeDetectorRef, ElementRef, ViewRef } from '@angular/core';
2+
import {
3+
BehaviorSubject,
4+
distinctUntilChanged,
5+
filter,
6+
map,
7+
merge,
8+
Observable,
9+
of,
10+
Subscription,
11+
switchMap,
12+
takeUntil,
13+
} from 'rxjs';
14+
import { NgtAnyRecord, NgtInstanceNode } from '../types';
15+
import { getLocalState } from '../utils/instance';
16+
import { is } from '../utils/is';
17+
import { injectNgtDestroy } from './destroy';
18+
19+
type Subscribe<T> = (callback: (current: T, previous: T | null) => void) => Subscription;
20+
21+
export type NgtInjectedRef<T> = ElementRef<T> & {
22+
/* a Subscribe fn that emits current and previous value. Useful for debug */
23+
subscribe: Subscribe<T>;
24+
/* consumers should use this for listening to value of this ref. This filters out initial null value */
25+
$: Observable<T>;
26+
/* consumers should use this for listenting to children changes on this ref */
27+
children$: (type?: 'objects' | 'nonObjects' | 'both') => Observable<NgtInstanceNode[]>;
28+
/* notify this CD when ref value changes */
29+
useCDR: (cdr: ChangeDetectorRef) => void;
30+
};
31+
32+
export function injectNgtRef<T>(initialValue: NgtInjectedRef<T> | (T | null) = null): NgtInjectedRef<T> {
33+
let ref = new ElementRef<T>(initialValue as T);
34+
35+
if (is.ref(initialValue)) {
36+
ref = initialValue;
37+
}
38+
39+
let lastValue = ref.nativeElement;
40+
const cdRefs = [] as ChangeDetectorRef[];
41+
const ref$ = new BehaviorSubject<T>(lastValue);
42+
43+
const { destroy$, cdr } = injectNgtDestroy(() => {
44+
ref$.complete();
45+
});
46+
47+
cdRefs.push(cdr);
48+
49+
const obs$ = ref$.asObservable().pipe(distinctUntilChanged(), takeUntil(destroy$));
50+
51+
const subscribe: Subscribe<T> = (callback) => {
52+
return obs$.subscribe((current) => {
53+
callback(current, lastValue);
54+
lastValue = current;
55+
});
56+
};
57+
58+
const $ = obs$.pipe(
59+
filter((value, index) => index > 0 || value != null),
60+
takeUntil(destroy$)
61+
);
62+
63+
const children$ = (type: 'objects' | 'nonObjects' | 'both' = 'objects') =>
64+
$.pipe(
65+
switchMap((instance) => {
66+
const localState = getLocalState(instance as NgtInstanceNode);
67+
if (localState.objects && localState.nonObjects) {
68+
return merge(localState.objects, localState.nonObjects).pipe(
69+
map(() => {
70+
try {
71+
return type === 'both'
72+
? [...localState.objects.value, ...localState.nonObjects.value]
73+
: localState[type].value;
74+
} catch (e) {
75+
console.error(`[NGT] Exception in accessing children of ${instance}`);
76+
return [];
77+
}
78+
})
79+
);
80+
}
81+
82+
return of([]);
83+
}),
84+
filter((children, index) => index > 0 || children.length > 0),
85+
takeUntil(destroy$)
86+
);
87+
88+
Object.defineProperty(ref, 'nativeElement', {
89+
set: (newVal: T) => {
90+
if (ref.nativeElement !== newVal) {
91+
ref$.next(newVal);
92+
93+
lastValue = ref.nativeElement;
94+
ref.nativeElement = newVal;
95+
96+
// clone the cdRefs so we can mutate cdRefs in the loop
97+
const cds = [...cdRefs];
98+
for (let i = 0; i < cds.length; i++) {
99+
const cd = cds[i];
100+
// if a ChangeDetectorRef is destroyed, we stop tracking it and go to the next one
101+
if ((cd as ViewRef).destroyed) {
102+
cdRefs.splice(i, 1);
103+
continue;
104+
}
105+
// during creation phase, 'context' on ViewRef will be null
106+
// we check the "context" to avoid running detectChanges during this phase.
107+
// becuase there's nothing to check
108+
if ((cd as NgtAnyRecord)['context']) {
109+
cd.detectChanges();
110+
}
111+
}
112+
}
113+
},
114+
get: () => ref$.value,
115+
});
116+
117+
return Object.assign(ref, { subscribe, $, children$, useCDR: (cdr: ChangeDetectorRef) => void cdRefs.push(cdr) });
118+
}

0 commit comments

Comments
 (0)