npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ember-modify-based-class-resource

v1.1.0

Published

modify-method based class implementation of the resource pattern

Downloads

12,060

Readme

ember-modify-based-class-resource

If you find this repo, please read: this issue which talks about the modify method pattern, and why it should be avoided.

This repo's functionality is built on public APIs, and should be able to be supported for as long as the underlying APIs exist.

Compatibility

Installation

npm install ember-modify-based-class-resource

Or if you want to use main (unstable) from git, you can use this in your package.json:

"ember-modify-based-class-resource": "github:NullVoxPopuli/ember-modify-based-class-resource#dist"

Which comes from this branch from this automation

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

Usage

[!NOTE]: These docs were moved from the old location in ember-resources as ember-resources (for a long time now) does not recommend class-based resources, as classes can be used ergonomically with function-based resources.

There are different abstractions to working with resources, each with their own set of tradeoffs and capabilities -- but ultimately are both summarized as "helpers with optional state and optional cleanup".

| | class-based Resource | function-based resource | | -- | ---------------------- | ------------------------- | | supports direct invocation in <templates> | yes | yes | | supports Glint | soon | soon | | provides a value | the instance of the class is the value[^1] | can represent a primitive value or complex object[^2] | | can be invoked with arguments | yes, received via modify[^3] hook | only when wrapped with a function. changes to arguments will cause the resource to teardown and re-run | | persisted state across argument changes | yes | no, but it's possible[^4] | | can be used in the body of a class component | yes | yes | | can be used in template-only components | yes[^5] | yes[^5] | | requires decorator usage (@use) | @use optional | @use optional[^6] | | aligns with the derived data reactivity philosophy | no[^7] | yes |

[^1]: class-based resources cannot be a single primitive value. APIs for support for this have been explored in the past, but it proved unergonomic and fine-grained reactivity per accessed property (when an object was desired for "the value") was not possible. [^2]: there are alternate ways to shape a function-resource depending on the behavior you want. These shapes and use cases are covered in the function-based Resources. [^3]: this is the same API / behavior as class-based modifiers in ember-modifier. [^4]: persisting state across argument changes with function-based resources might require a WeakMap and some stable object to reference as the key for the storage within that WeakMap. [^5]: for .hbs files the resources will need to be globally available via export defaults from the app/helpers directory. [^6]: without @use, the function-based resource must represent a non-primitive value or object. [^7]: As-is, the modify hook we have right now in the class-based resource (and in modifiers) is used as a backdoor to an effect. See this information for alternate paths.

class-based resources

🔝 back to top

Class-based resources are a way for object-oriented encapsulation of state, giving access to the application container / owner for service injection, and/or persistint state across argument changes.

Though, maybe a more pragmatic approach to the difference:

Class-based resources can be invoked with args. Function-based resources must be wrapped in another function to accept args.

Lifecycles with Resource

There is only one lifecycle hook, modify, to encourage data-derivation (via getters) and generally simpler state-management than you'd otherwise see with with additional lifecycle methods.

For example, this is how you'd handle initial setup, updates, and teardown with a Resource

import { Resource } from 'ember-resources';
import { registerDestructor } from '@ember/destroyable';

class MyResource extends Resource {
  // constructor only needed if teardown is needed
  constructor(owner) {
    super(owner);

    registerDestructor(this, () => {
      // final teardown, if needed
    });
  }

  modify(positional, named) {
    // initial setup, updates, etc
  }
}

Many times, however, you may not even need to worry about destruction, which is partially what makes opting in to having a "destructor" so fun -- you get to choose how much lifecycle your Resource has.

More info: @ember/destroyable

Reactivity

class-based Resources have lazy, usage-based reactivity based on whatever is accessed in the modify hook.

For example, consider a resource that doubles a number (this is over engineered, and you wouldn't want a Resource for doubling a number)

import { tracked } from '@glimmer/tracking';
// import { Resource } from 'ember-resources'; // in V5
import { Resource } from 'ember-resources';

class Doubler extends Resource {
  @tracked result = NaN;

  modify([num]) {
    this.result = num * 2;
  }
}

class {
  @tracked num = 2;

  doubler = Doubler.from(() => [this.num]);
}

When accessed, the value of doubler.result will be 4. Any time this.num changes, the value of doubler.result will be 8.

This happens lazily, so if doubler.result is not accessed, the Resource is not evaluated and no computation efforts are done.

Accessing can be done anywhere at any time, in JS, or in a Template (it's the same).

A class-based Resource can define its own state anywhere, but has the same stipulations as the function-based Resource: inside the modify hook, you may not access a tracked property that is later written to. This causes an infinte loop while the framework tries to resolve what the stable "value" should be.

See the Clock example below for more details.

Example: class-based Clock

Given the complete example of a clock above implemented in a function-based resource, A complete implementation, as a class-based resource could look similar to this:

// import { Resource } from 'ember-resources'; // in V5
import { Resource } from 'ember-resources'
import { tracked } from '@glimmer/tracking';
import { registerDestructor } from '@ember/destroyable';

class Clock extends Resource {
  @tracked current = new Date();

  constructor(owner) {
    super(owner);

    let interval = setInterval(() => (this.current = new Date()), 1_000);

    registerDestructor(this, () => clearInterval(interval));
  }

  get formatted() {
    return this.formatter.format(this.current);
  }

  modify([locale = 'en-US']) {
    this.formatter = new Intl.DateTimeFormat(locale, {
      hour: 'numeric',
      minute: 'numeric',
      second: 'numeric',
      hour12: false,
    });
  }
}

Resulting usage would look something like this:

{{get (Clock 'en-GB') 'formatted'}}

Or if you needed the value in JS

class {
  clock = Clock.from(this, () => ['en-GB']);

  get now() {
    return this.clock.formatted;
  }
}

Example: class-based Fetch

🔝 back to top

See: Cookbook entry, fetch with AbortController