Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove class-based resource documentation #1103

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading