
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
ember-resources
Advanced tools
An implementation of Resources in Ember.JS without decorators.
NOTE: if you are also using ember-could-get-used-to-this, @use
is not compatible with
this library's LifecycleResource
, and useResource
does not work with ember-could-get-used-to-this' Resource
.
However, both libraries can still be used in the same project.
npm install ember-resources
# or
yarn add ember-resources
# or
ember install ember-resources
import { useResource, useFunction, useTask } from 'ember-resources';
class MyClass {
data = useResource(this, DataClass, () => [arg list]);
data1 = useFunction(this, () => { /* synchronous function */ })
data2 = useFunction(this, async () => {}),
data3 = useTask(this.someEmberConcurrencyTask, () => [optional arg list]);
}
useResource
useResource
takes a LifecycleResource
and an args thunk.
class MyClass {
data = useResource(this, SomeResource, () => [arg list]);
}
When any tracked data in the args thunk, the update
function on SomeResource
will be called.
this
is to keep track of destruction -- so when MyClass
is destroyed, all the resources attached to it can also be destroyed.useTask
This is a utility wrapper like useResource
, but can be passed an ember-concurrency task
so that the ember-concurrency task can reactively be re-called whenever args change.
This largely eliminates the need to start concurrency tasks from the constructor, modifiers,
getters, etc.
A concurrency task accessed via useTask
is only "ran" when accessed, and automatically updates
when it needs to.
class MyClass {
myData = useTask(this, this._myTask, () => [args, to, task])
@task
*_myTask(args, to, task) { /* ... */ }
}
Accessing myData
will represent the last TaskInstance
, so all the expected properties are available:
value
, isRunning
, isFinished
, etc
LifecycleResource
This resource base class has 3 lifecycle hooks:
setup
- called upon first access of the resourceupdate
- called when any tracked
used during setup
changesteardown
- called when the containing context is torn downAn example of this might be an object that you want to have perform some complex or async behavior
class MyResource extends LifecycleResource {
@tracked isRunning;
@tracked error;
get status() {
if (this.isRunning) return 'pending';
if (this.error) return this.error;
return 'idle';
}
setup() {
this.doAsyncTask();
}
update() {
this.doAsyncTask();
}
async doAsyncTask() {
let [ids] = this.args.positional;
this.isRunning = true;
this.error = undefined;
try {
// some long running stuff here
} catch (e) {
this.error = e
}
this.isRunning = false;
}
}
Using your custom Resource would look like
class ContainingClass {
data = useResource(this, MyResource, () => [this.ids])
}
function
ResourcesWhile functions can be "stateless", Resources don't provide much value unless
you can have state. function
Resources solve this by passing the previous
invocation's return value as an argument to the next time the function is called.
Example:
class StarWarsInfo {
// access result on info.value
info = useFunction(this, async (state, ...args) => {
if (state) {
let { characters } = state;
return { characters };
}
let [ids] = args;
let response = await fetch(`/characters/${ids}`) ;
let characters = await response.json();
return { characters };
}, () => [this.ids /* defined somewhere */])
}
While this example is a bit contrived, hopefully it demonstrates how the state
arg
works. During the first invocation, state
is falsey, allowing the rest of the
function to execute. The next time this.ids
changes, the function will be called
again, except state
will be the { characters }
value during the first invocation,
and the function will return the initial data.
This particular technique could be used to run any async function safely (as long
as the function doesn't interact with this
).
In this example, where the function is async
, the "value" of info.value
is undefined
until the
function completes.
If a function is synchronous, you can avoid the thunk altogether,
class MyClass {
@tracked num = 3;
info = useResource(this, () => {
return this.num * 2;
});
}
this.info.value
will be 6
With the exception of the useResource
+ class
combination, all Thunks are optional.
The main caveat is that if you want your resource to update, you must consume the tracked
properties during setup / initial execution.
The args thunk accepts the following data shapes:
() => [an, array]
() => ({ hello: 'there' })
() => ({ named: {...}, positional: [...] })
when an array is passed, inside the Resource, this.args.named
will be empty
and this.args.positional
will contain the result of the thunk.
for function resources, this is the only type of thunk allowed.
when an object is passed where the key named
is not present,
this.args.named
will contain the result of the thunk and this.args.positional
will be empty.
when an object is passed containing either keys: named
or positional
:
this.args.named
will be the value of the result of the thunk's named
propertythis.args.positional
will be the value of the result of the thunk's positional
propertyThis is the same shape of args used throughout Ember's Helpers, Modifiers, etc
import type { ArgsWrapper, Named, Positional } from 'ember-resources';
where:
interface ArgsWrapper {
positional?: unknown[];
named?: Record<string, unknown>;
}
this is a utility interface that represents all of the args used throughout Ember.
Example
class MyResource extends LifecycleResource { // default args type
constructor(owner: unknown, args: ArgsWrapper) {
super(owner, args);
}
}
export interface Positional<T extends Array<unknown>> {
positional: T;
}
Example:
class MyResource extends LifecycleResource<Positional<[number]>> {
}
export interface Named<T extends Record<string, unknown>> {
named: T;
}
Example:
class MyResource extends LifecycleResource<Named<{ bananas: number }>> {
}
These shorthands are 3 characters sharter than using the named:
or
positional:
keys that would be required if not using these shorthands...
If your resources are consumed by components, you'll want to continue to test using rendering tests, as things should "just work" with those style of tests.
Where things get interesting is when you want to unit test your resources.
There are two approaches:
new
the resource directlytest('my test', function(assert) {
class MyResource extends LifecycleResource {
// ...
}
let instance = new MyResource(this.owner, { /* args wrapper */ });
// assertions with instance
})
The caveat here is that the setup
and update
functions will have to
be called manually, because we aren't using useResource
, which wraps the
Ember-builtin invokeHelper
, which takes care of reactivity for us. As a
consequence, any changes to the args wrapper will not cause updates to
the resource instance.
If, instead of creating MyResource
directly, like in the example above,
it is wrapped in a test class and utilizes useResource
:
class TestContext {
data = useResource(this, MyResource, () => { ... })
}
changes to args will trigger calls to setup
and update
.
NOTE: like with all reactivity testing in JS, it's important to
await settled()
after a change to a reactive property so that you allow
time for the framework to propagate changes to all the reactive bits.
Example:
test('my test', async function (assert) {
class Doubler extends LifecycleResource<{ positional: [number] }> {
get num() {
return this.args.positional[0] * 2;
}
}
class Test {
@tracked count = 0;
data = useResource(this, Doubler, () => [this.count]);
}
let foo = new Test();
assert.equal(foo.data.num, 0);
foo.count = 3;
await settled();
assert.equal(foo.data.num, 6);
See the Contributing guide for details.
This project is licensed under the MIT License.
This library wouldn't be possible without the work of:
So much appreciate for the work both you have put in to Resources <3
FAQs
An implementation of Resources with some helpful utilities
The npm package ember-resources receives a total of 17,583 weekly downloads. As such, ember-resources popularity was classified as popular.
We found that ember-resources demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.