Skip to content

Commit

Permalink
Remove class-based resource documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli committed Jan 10, 2024
1 parent a625ab2 commit e622aa9
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 187 deletions.
187 changes: 1 addition & 186 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,11 @@
In this document, you'll learn about the core features of `ember-resources`
and how to decide which primitives to use, how to create, support, compose, and test with them.

- [the primitives](#the-primitives)
- [function-based Resources](#function-based-resources)
- [function-based Resources](#function-based-resources)
- [Lifecycle](#lifecycles-with-resource)
- [Reactivity](#reactivity)
- [Example: Clock](#example-clock): Managing own state
- [Example: `fetch`](#example-fetch): Async + lifecycle
- [class-based Resources](#class-based-resources)
- [Lifecycle](#lifecycles-with-resource-1)
- [Reactivity](#reactivity-1)
- [Example: Clock](#example-class-based-clock): Managing own state
- [Example: `fetch`](#example-class-based-fetch): Async + lifecycle

## the primitives

There are two core 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`][docs-class-resource] | function-based [`resource`][docs-function-resource] |
| -- | ---------------------- | ------------------------- |
| supports direct invocation in [`<templates>`][rfc-779] | yes | yes |
| supports [Glint][gh-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] |


[rfc-779]: https://github.com/emberjs/rfcs/pull/779
[gh-glint]: https://github.com/typed-ember/glint
[gh-ember-modifier]: https://github.com/ember-modifier/ember-modifier

[docs-class-resource]: https://ember-resources.pages.dev/classes/core.Resource
[docs-function-resource]: https://ember-resources.pages.dev/modules/util_function_resource#resource

[mdn-weakmap]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap

[^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](#function-based-resources).
[^3]: this is the same API / behavior as class-based modifiers in [ember-modifier][gh-ember-modifier].
[^4]: persisting state across argument changes with function-based resources might require a [`WeakMap`][mdn-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 default`s from the `app/helpers` directory.
[^6]: without `@use`, the function-based resource must represent a non-primitive value or object.

### function-based resources

Expand Down Expand Up @@ -391,148 +351,3 @@ class {

See: Cookbook entry, [`fetch` with `AbortController`](https://github.com/NullVoxPopuli/ember-resources/blob/main/docs/docs/cookbook/fetch-with-AbortController.md#using-resource-1)

### class-based resources

[🔝 back to top](#authoring-resources)

Class-based resources are good 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`

```js
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`](https://api.emberjs.com/ember/release/modules/@ember%2Fdestroyable)

#### 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)

```js
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:

```js
// 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:

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

Or if you needed the value in JS
```js
class {
clock = Clock.from(this, () => ['en-GB']);

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

#### Example: class-based Fetch

[🔝 back to top](#authoring-resources)

See: Cookbook entry, [`fetch` with `AbortController`](https://github.com/NullVoxPopuli/ember-resources/blob/main/docs/docs/cookbook/fetch-with-AbortController.md#using-resource)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Which comes from [this branch][self-dist] from [this automation][self-dist-ci]
- [Interactive Tutorial](https://tutorial.glimdown.com/2-reactivity/5-resources)
- [util: RemoteData](https://tutorial.glimdown.com/11-requesting-data/1-using-remote-data)
- [util: keepLatest](https://tutorial.glimdown.com/12-loading-patterns/1-keeping-latest)
- [API Reference](https://ember-resources.pages.dev/modules)
- [API Reference](https://ember-resources.nullvoxpopuli.com/)


## Contributing
Expand Down

0 comments on commit e622aa9

Please sign in to comment.