MOBILE-4612 compile: Improve destroying effects

main
Dani Palou 2024-07-31 10:00:08 +02:00
parent 9f910d338f
commit e8610272c7
2 changed files with 42 additions and 11 deletions

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
import { toBoolean } from '@/core/transforms/boolean'; import { toBoolean } from '@/core/transforms/boolean';
import { effectWithInjectionContext } from '@/core/utils/signals';
import { import {
Component, Component,
Input, Input,
@ -34,6 +35,9 @@ import {
Type, Type,
KeyValueDiffer, KeyValueDiffer,
Injector, Injector,
EffectRef,
EffectCleanupRegisterFn,
CreateEffectOptions,
} from '@angular/core'; } from '@angular/core';
import { CorePromisedValue } from '@classes/promised-value'; import { CorePromisedValue } from '@classes/promised-value';
@ -212,6 +216,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
return class CoreCompileHtmlFakeComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy { return class CoreCompileHtmlFakeComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
private ongoingLifecycleHooks: Set<keyof AfterViewInit | keyof AfterContentInit | keyof OnDestroy> = new Set(); private ongoingLifecycleHooks: Set<keyof AfterViewInit | keyof AfterContentInit | keyof OnDestroy> = new Set();
protected effectRefs: EffectRef[] = [];
constructor() { constructor() {
// Store this instance so it can be accessed by the outer component. // Store this instance so it can be accessed by the outer component.
@ -221,12 +226,25 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
this['dataObject'] = {}; this['dataObject'] = {};
this['dataArray'] = []; this['dataArray'] = [];
const effectWithContext = effectWithInjectionContext(compileInstance.injector);
// Inject the libraries. // Inject the libraries.
CoreCompile.injectLibraries( CoreCompile.injectLibraries(this, {
this, extraLibraries: compileInstance.extraProviders,
compileInstance.extraProviders, injector: compileInstance.injector,
compileInstance.injector, // Capture calls to effect to retrieve the effectRefs and destroy them when this component is destroyed.
); // Otherwise effects are only destroyed when the parent component is destroyed.
effectWrapper: (
effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
options?: Omit<CreateEffectOptions, 'injector'>,
): EffectRef => {
const effectRef = effectWithContext(effectFn, options);
this.effectRefs.push(effectRef);
return effectRef;
},
});
// Always add these elements, they could be needed on component init (componentObservable). // Always add these elements, they could be needed on component init (componentObservable).
this['ChangeDetectorRef'] = compileInstance.changeDetector; this['ChangeDetectorRef'] = compileInstance.changeDetector;
@ -280,6 +298,9 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
* @inheritdoc * @inheritdoc
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.effectRefs.forEach(effectRef => effectRef.destroy());
this.effectRefs = [];
this.callLifecycleHookOverride('ngOnDestroy'); this.callLifecycleHookOverride('ngOnDestroy');
} }

View File

@ -23,6 +23,7 @@ import {
ViewContainerRef, ViewContainerRef,
signal, signal,
computed, computed,
effect,
untracked, untracked,
} from '@angular/core'; } from '@angular/core';
import { import {
@ -261,20 +262,19 @@ export class CoreCompileProvider {
* Inject all the core libraries in a certain object. * Inject all the core libraries in a certain object.
* *
* @param instance The instance where to inject the libraries. * @param instance The instance where to inject the libraries.
* @param extraLibraries Extra imported providers if needed and not imported by this class. * @param options Options.
* @param injector Injector of the injection context. E.g. for a component, use the component's injector.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
injectLibraries(instance: any, extraLibraries: Type<unknown>[] = [], injector?: Injector): void { injectLibraries(instance: any, options: InjectLibrariesOptions = {}): void {
if (!this.libraries || !this.exportedObjects) { if (!this.libraries || !this.exportedObjects) {
throw new CoreError('Libraries not loaded. You need to call loadLibraries before calling injectLibraries.'); throw new CoreError('Libraries not loaded. You need to call loadLibraries before calling injectLibraries.');
} }
const libraries = [ const libraries = [
...this.libraries, ...this.libraries,
...extraLibraries, ...options.extraLibraries ?? [],
]; ];
injector = injector ?? this.injector; const injector = options.injector ?? this.injector;
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance. // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
for (const i in libraries) { for (const i in libraries) {
@ -305,7 +305,7 @@ export class CoreCompileProvider {
instance['signal'] = signal; instance['signal'] = signal;
instance['computed'] = computed; instance['computed'] = computed;
instance['untracked'] = untracked; instance['untracked'] = untracked;
instance['effect'] = effectWithInjectionContext(injector); instance['effect'] = options.effectWrapper ?? effectWithInjectionContext(injector);
instance['model'] = modelWithInjectionContext(injector); instance['model'] = modelWithInjectionContext(injector);
/** /**
@ -430,3 +430,13 @@ export class CoreCompileProvider {
} }
export const CoreCompile = makeSingleton(CoreCompileProvider); export const CoreCompile = makeSingleton(CoreCompileProvider);
/**
* Options for injectLibraries.
*/
type InjectLibrariesOptions = {
extraLibraries?: Type<unknown>[]; // Extra imported providers if needed and not imported by this class.
injector?: Injector; // Injector of the injection context. E.g. for a component, use the component's injector.
effectWrapper?: typeof effect; // Wrapper function to create an effect. If not provided, a wrapper will be created using the
// injector. Use this wrapper if you want to capture the created EffectRefs.
};