
Beyond Angular Signals: Signals & Custom Render Strategies

๐ From Tick to Sig
๐ฌ DOM updates are not that cheap
@Component({
...
template: `
<div *ngFor="let _ of lines">{{ count() }}</div>
`,
})
export class CounterComponent implements OnInit {
count = signal(0);
lines = Array(10_000);
ngOnInit() {
setInterval(() => this.count.update(value => value + 1), 100);
}
}

๐ฆง Let's calm down a bit
@Component({
...
template: `{{ throttledCount() }}`
})
class MyCmp {
count = signal(0);
throttledCount = throttleSignal(this.count, {duration: 1000});
...
}๐บ Updating the viewport only
lazyCount = applyViewportStrategy(this.count, {element});template: `
<span *lazyViewportSignal="count(); let countValue">{{ countValue }}</span>
<span> x 2 = </span>
<span *lazyViewportSignal="double(); let doubleValue">{{ doubleValue }}</span>
`๐ค What about Eventual Consistency for DOM updates?
@Component({
...
template: `
<div *viewportStrategy>
<span>{{ count() }}</span>
<span> x 2 = </span>
<span>{{ double() }} </span>
</div>
`,
})
export class CounterComponent implements OnInit {
count = Signal(0);
double = computed(() => count());
}๐จ๐ปโ๐ณ Sneaking between Signals & Change Detection
/**
* This doesn't work as expected!
*/
const viewRef = vcr.createEmbeddedView(templateRef);
viewRef.detach();
effect(() => {
console.log('Yeay! we are in!'); // if called more than once
viewRef.detectChanges();
});๐ฌ The Reactive Graph
ReactiveNode
ReactiveNode

onConsumerDependencyMayHaveChanged()
๐ฏ The Reactive Logical View Consumer
onConsumerDependencyMayHaveChanged()
onConsumerDependencyMayHaveChanged() {
...
markViewDirty(this._lView);
}๐ Sneaking between Signals & Change Detection
1. Create the embedded view
@Directive({
standalone: true,
selector: '[viewportStrategy]',
})
class ViewportStrategyDirective {
private _templateRef = inject(TemplateRef);
private _vcr = inject(ViewContainerRef);
ngOnInit() {
const viewRef = this._vcr.createEmbeddedView(this._templateRef);
}
}2. Trigger change detection
ReactiveLViewConsumer
viewRef.detectChanges();
viewRef.detach();3. Grab the ReactiveLViewConsumer
ReactiveLViewConsumerconst reactiveViewConsumer = viewRef['_lView'][REACTIVE_TEMPLATE_CONSUMER /* 23 */];4. Override the Signal notification handler like a monkey
ReactiveLViewConsumeronConsumerDependencyMayHaveChanged()
let timeout;
reactiveViewConsumer.onConsumerDependencyMayHaveChanged = () => {
if (timeout != null) {
return;
}
timeout = setTimeout(() => {
viewRef.detectChanges();
timeout = null;
}, 1000);
};๐ and it works !



๐ฎ What's next?
๐ฆ RxAngular + Signals
๐
ฐ๏ธ We might need more low-level Angular APIs
interface ViewRef {
/* This doesn't exist. */
setCustomSignalChangeHandler(callback: () => void);
}ViewRef.detach()
Signal-Based Components
โ Custom Render Strategies in some other Libraries & Frameworks
SolidJS
Preact
React
const CounterWithViewportStrategy = withViewportStrategy(() => <div>{count}</div>);
export function App() {
...
return <>
{items.map(() => <CounterWithViewportStrategy count={count}/>}
</>
}React.createElement
Vue.js
withMemo()
defineComponent({
setup() {
const count = ref(0);
return viewportStrategy(({ rootEl }) => (
<div ref={rootEl}>{ count }</div>
));
},
})v-viewport-strategy