commit
9df5533dd7
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { toBoolean } from '@/core/transforms/boolean';
|
||||
import { effectWithInjectionContext } from '@/core/utils/signals';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
|
@ -34,6 +35,9 @@ import {
|
|||
Type,
|
||||
KeyValueDiffer,
|
||||
Injector,
|
||||
EffectRef,
|
||||
EffectCleanupRegisterFn,
|
||||
CreateEffectOptions,
|
||||
} from '@angular/core';
|
||||
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 {
|
||||
|
||||
private ongoingLifecycleHooks: Set<keyof AfterViewInit | keyof AfterContentInit | keyof OnDestroy> = new Set();
|
||||
protected effectRefs: EffectRef[] = [];
|
||||
|
||||
constructor() {
|
||||
// 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['dataArray'] = [];
|
||||
|
||||
const effectWithContext = effectWithInjectionContext(compileInstance.injector);
|
||||
|
||||
// Inject the libraries.
|
||||
CoreCompile.injectLibraries(
|
||||
this,
|
||||
compileInstance.extraProviders,
|
||||
compileInstance.injector,
|
||||
);
|
||||
CoreCompile.injectLibraries(this, {
|
||||
extraLibraries: compileInstance.extraProviders,
|
||||
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).
|
||||
this['ChangeDetectorRef'] = compileInstance.changeDetector;
|
||||
|
@ -280,6 +298,9 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.effectRefs.forEach(effectRef => effectRef.destroy());
|
||||
this.effectRefs = [];
|
||||
|
||||
this.callLifecycleHookOverride('ngOnDestroy');
|
||||
}
|
||||
|
||||
|
|
|
@ -24,9 +24,7 @@ import {
|
|||
signal,
|
||||
computed,
|
||||
effect,
|
||||
EffectCleanupRegisterFn,
|
||||
CreateEffectOptions,
|
||||
EffectRef,
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActionSheetController,
|
||||
|
@ -41,6 +39,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { effectWithInjectionContext, modelWithInjectionContext } from '@/core/utils/signals';
|
||||
|
||||
// Import core services.
|
||||
import { getCoreServices } from '@/core/core.module';
|
||||
|
@ -263,20 +262,19 @@ export class CoreCompileProvider {
|
|||
* Inject all the core libraries in a certain object.
|
||||
*
|
||||
* @param instance The instance where to inject the libraries.
|
||||
* @param extraLibraries Extra imported providers if needed and not imported by this class.
|
||||
* @param injector Injector of the injection context. E.g. for a component, use the component's injector.
|
||||
* @param options Options.
|
||||
*/
|
||||
// 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) {
|
||||
throw new CoreError('Libraries not loaded. You need to call loadLibraries before calling injectLibraries.');
|
||||
}
|
||||
|
||||
const 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.
|
||||
for (const i in libraries) {
|
||||
|
@ -306,15 +304,10 @@ export class CoreCompileProvider {
|
|||
instance['Md5'] = Md5;
|
||||
instance['signal'] = signal;
|
||||
instance['computed'] = computed;
|
||||
// Create a wrapper to call effect with the proper injection context.
|
||||
instance['effect'] = (
|
||||
effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
|
||||
options?: Omit<CreateEffectOptions, 'injector'>,
|
||||
): EffectRef =>
|
||||
effect(effectFn, {
|
||||
...options,
|
||||
injector,
|
||||
});
|
||||
instance['untracked'] = untracked;
|
||||
instance['effect'] = options.effectWrapper ?? effectWithInjectionContext(injector);
|
||||
instance['model'] = modelWithInjectionContext(injector);
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1, plugins should use CoreNetwork instead.
|
||||
* Keeping this a bit more to avoid plugins breaking.
|
||||
|
@ -437,3 +430,13 @@ export class 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.
|
||||
};
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {
|
||||
CreateEffectOptions,
|
||||
effect,
|
||||
EffectCleanupRegisterFn,
|
||||
EffectRef,
|
||||
Injector,
|
||||
model,
|
||||
ModelOptions,
|
||||
ModelSignal,
|
||||
runInInjectionContext,
|
||||
} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Return an effect wrapper that can be used to create an effect with a certain injection context.
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* const effectWrapper = effectWithInjectionContext(injector);
|
||||
*
|
||||
* effectWrapper(() => {
|
||||
* // Your effect code here.
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param injector Injector to use for the effect.
|
||||
* @returns Function to create the effect.
|
||||
*/
|
||||
export function effectWithInjectionContext(injector: Injector): typeof effect {
|
||||
return (
|
||||
effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
|
||||
options?: Omit<CreateEffectOptions, 'injector'>,
|
||||
): EffectRef =>
|
||||
effect(effectFn, {
|
||||
...options,
|
||||
injector,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a model wrapper that can be used to create a model with a certain injection context.
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* const modelWrapper = modelWithInjectionContext(injector);
|
||||
*
|
||||
* const myModel = modelWrapper('');
|
||||
* ```
|
||||
*
|
||||
* @param injector Injector to use for the model.
|
||||
* @returns Function to create the model.
|
||||
*/
|
||||
export function modelWithInjectionContext<T = unknown>(injector: Injector): typeof model {
|
||||
const modelFunction = (initialValue: T, opts?: ModelOptions): ModelSignal<T> =>
|
||||
runInInjectionContext(injector, () => model(initialValue, opts));
|
||||
|
||||
modelFunction.required = (opts?: ModelOptions): ModelSignal<T> => runInInjectionContext(injector, () => model.required(opts));
|
||||
|
||||
return modelFunction as typeof model;
|
||||
}
|
Loading…
Reference in New Issue