Marmicode
Blog Post
Younes Jaaidi

Beyond Angular Signals: Signals & Custom Render Strategies

by Younes Jaaidi โ€ข 
Apr 7, 2023 โ€ข 8 minutes
Beyond Angular Signals: Signals & Custom Render Strategies

@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);
  }
}

@Component({
  ...
  template: `{{ throttledCount() }}`
})
class MyCmp {
  count = signal(0);
  throttledCount = throttleSignal(this.count, {duration: 1000});
  ...
}

lazyCount = applyViewportStrategy(this.count, {element});

template: `
  <span *lazyViewportSignal="count(); let countValue">{{ countValue }}</span>
  <span> x 2 = </span>
  <span *lazyViewportSignal="double(); let doubleValue">{{ doubleValue }}</span>
`

@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());
}

/**
 * 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();
});

onConsumerDependencyMayHaveChanged() {
  ...
  markViewDirty(this._lView);
}

@Directive({
  standalone: true,
  selector: '[viewportStrategy]',
})
class ViewportStrategyDirective {
  private _templateRef = inject(TemplateRef);
  private _vcr = inject(ViewContainerRef);

  ngOnInit() {
    const viewRef = this._vcr.createEmbeddedView(this._templateRef);
  }
}

viewRef.detectChanges();

viewRef.detach();

const reactiveViewConsumer = viewRef['_lView'][REACTIVE_TEMPLATE_CONSUMER /* 23 */];

let timeout;
reactiveViewConsumer.onConsumerDependencyMayHaveChanged = () => {
  if (timeout != null) {
    return;
  }

  timeout = setTimeout(() => {
    viewRef.detectChanges();
    timeout = null;
  }, 1000);
};

interface ViewRef {
  /* This doesn't exist. */
  setCustomSignalChangeHandler(callback: () => void);
}

const CounterWithViewportStrategy = withViewportStrategy(() => <div>{count}</div>);

export function App() {
  ...
  return <>
    {items.map(() => <CounterWithViewportStrategy count={count}/>}
  </>
}

defineComponent({
  setup() {
    const count = ref(0);

    return viewportStrategy(({ rootEl }) => (
      <div ref={rootEl}>{ count }</div>
    ));
  },
})