Hi everyone !
I'm migrating my projects to fully use signals in order to be ready the day when zone.js will become optional. In few part of my projects I use NgZone.run() to detect changes triggered by external libraries callbacks (for example Braintree). As I want to no longer rely on NgZone I wonder if using ApplicationRef.tick() is a proper alternative to trigger change detection. I tried to use ChangeDetectionRef.markForCheck() and even ChangeDetectionRef.detectChanges() but without success in some cases (especially with Braintree dropin, as it use iframes under the hood). So my question is : Isn't too hacky to use ApplicationRef ?
#ApplicationRef.tick() instead of NgZone.run() ?
10 messages · Page 1 of 1 (latest)
I'd say that you should just, when zoneless is available, remove the call to NgZone.run(): the simple fact of changing the value of a signal read in the view should be sufficient to make Angular detect the change.
Thanks for the answer 🙂 I totally agree and this is what I tend to but in some very specific libs I ended with callbacks setting signals that just don't trigger change detection :
export class BraintreeDropInUiComponent implements OnDestroy {
private readonly braintree = inject(BraintreeService);
private readonly applicationRef = inject(ApplicationRef);
isLoading = signal(true);
dropInContainer =
viewChild.required<ElementRef<HTMLElement>>('dropInContainer');
options = input<DropInOptions | null>(null);
paymentOptions = input<DropInPaymentOptions | undefined>();
private dropIn = toSignal(
// ...
);
requestPayment = output<PaymentMethodPayload>();
private _isPaymentMethodRequestable = signal(false);
isPaymentMethodRequestable = this._isPaymentMethodRequestable.asReadonly();
private onPaymentMethodRequestable = () => {
this._isPaymentMethodRequestable.set(true); // <-- Don't trigger CD
this.applicationRef.tick();
};
private onNoPaymentMethodRequestable = () => {
this._isPaymentMethodRequestable.set(false); // <-- Don't trigger CD
this.applicationRef.tick();
};
constructor() {
effect((onCleanup) => {
this.dropIn()?.on(
'paymentMethodRequestable',
this.onPaymentMethodRequestable,
);
this.dropIn()?.on(
'noPaymentMethodRequestable',
this.onNoPaymentMethodRequestable,
);
onCleanup(() => {
untracked(() => this._isPaymentMethodRequestable.set(false));
this.dropIn()?.off(
'paymentMethodRequestable',
this.onPaymentMethodRequestable,
);
this.dropIn()?.off(
'noPaymentMethodRequestable',
this.onNoPaymentMethodRequestable,
);
});
});
}
//...
}
Whoa, I tried to put effect(() => this.isPaymentMethodRequestable()); in my constructor and it work without the need for applicationref.tick() this is insane Oo
I ended with callbacks setting signals that just don't trigger change detection
But zoneless is not supported yet. AFAIK, this should work fine when it is.
That's just zone doing its thing. My guess is whatever is firing the paymentMethodRequestable is outside the zone.
this._isPaymentMethodRequestable.set(true); // <-- Don't trigger CD
^ in v18 this will trigger CD regardless of whether you're in the zone. I'm actually reviewing that PR right now 🙂
For now I would do
NgZone.run(() => {
this._isPaymentMethodRequestable.set(true);
});
Thanks @vivid siren ! yeah I forgot to mention callbacks are in fact outside of the zone (cause of braintree drop in iframes). I'm so glad an angular team member give me a guidance 🙂 I will let my NgZone.run() just like before until v18
Does this mean that zoneless will be a thing in v18, or are there still things to do before?
We're hoping to have an @experimental opt-in support for it