diff --git a/ember-resources/src/core/function-based/manager.ts b/ember-resources/src/core/function-based/manager.ts index 03851b565..c20590f19 100644 --- a/ember-resources/src/core/function-based/manager.ts +++ b/ember-resources/src/core/function-based/manager.ts @@ -1,7 +1,12 @@ // @ts-ignore import { createCache, getValue } from '@glimmer/tracking/primitives/cache'; import { assert } from '@ember/debug'; -import { associateDestroyableChild, destroy, registerDestructor } from '@ember/destroyable'; +import { + associateDestroyableChild, + destroy, + isDestroyed, + registerDestructor, +} from '@ember/destroyable'; // @ts-ignore import { invokeHelper } from '@ember/helper'; // @ts-ignore @@ -113,6 +118,22 @@ class FunctionResourceManager { cleanup: (destroyer: Destructor) => { registerDestructor(currentFn, destroyer); }, + setup: (setuper: () => void | Destructor) => { + // TODO: figure out a good option here. + // Potential options: + // - requestAnimationFrame + // - requestIdleCallback + // - "after render" (runloop schedule) + requestAnimationFrame(() => { + if (isDestroyed(currentFn)) return; + + let destroyer = setuper(); + + if (destroyer) { + registerDestructor(currentFn, destroyer); + } + }); + }, }, use, owner: this.owner, diff --git a/ember-resources/src/core/function-based/types.ts b/ember-resources/src/core/function-based/types.ts index 8a2cc0ee5..d8f4c63f8 100644 --- a/ember-resources/src/core/function-based/types.ts +++ b/ember-resources/src/core/function-based/types.ts @@ -53,6 +53,38 @@ export type ResourceAPI = { * }) */ cleanup: (destroyer: Destructor) => void; + + /** + * Co-locate setup and teardown together. + * This allows you to keep "management variables" out of scope + * for the overall resource. + * + * Example: + * ```js + * import { resource, cell } from 'ember-resources'; + * + * const DateTime = resource(({ on }) => { + * const now = cell(new Date()) + * + * on.setup(() => { + * let interval = setInterval(() => now.current = new Date(), 1000); + * + * // This function is the cleanup + * return () => clearInterval(interval); + * }); + * + * on.setup(() => { + * let timeout = setTimeout(() => now.current = 0, 30_000); + * + * return () => clearTimeout(timeout); + * }); + * + * return now; + * }); + * ``` + * + */ + setup: (setup: () => void | Destructor) => void; }; /**