@@ -6,8 +6,12 @@ An implementation of Resources in Ember.JS without decorators.
6
6
7
7
## Compatibility
8
8
9
- * Ember.js v3.25 or above
9
+ * Ember.js v3.25+
10
+ * TypeScript v4.2+
10
11
12
+ _ NOTE_ : if you are also using ember-could-get-used-to-this, ` @use ` is not compatible with
13
+ this library's ` LifecycleResource ` , and ` useResource ` does not work with ember-could-get-used-to-this' ` Resource ` .
14
+ However, both libraries can still be used in the same project.
11
15
12
16
## Installation
13
17
@@ -24,13 +28,168 @@ ember install ember-resources
24
28
25
29
### ` useResource `
26
30
31
+ ` useResource ` takes a ` LifecycleResource ` and an args thunk.
32
+
33
+ ``` ts
34
+ class MyClass {
35
+ data = useResource (this , SomeResource , () => [arg list ]);
36
+ }
37
+ ```
38
+
39
+ When any tracked data in the args thunk, the ` update ` function on ` SomeResource `
40
+ will be called.
41
+
42
+ The ` this ` is to keep track of destruction -- so when ` MyClass ` is destroyed, all the resources attached to it can also be destroyed.
43
+
44
+ The args thunk accepts the following data shapes:
45
+ ```
46
+ () => [an, array]
47
+ () => ({ hello: 'there' })
48
+ () => ({ named: {...}, positional: [...] })
49
+ ```
50
+ #### An array
51
+
52
+ when an array is passed, inside the Resource, ` this.args.named ` will be empty
53
+ and ` this.args.positional ` will contain the result of the thunk.
54
+
55
+ _ for function resources, this is the only type of thunk allowed._
56
+
57
+ #### An object of named args
58
+
59
+ when an object is passed where the key ` named ` is not present,
60
+ ` this.args.named ` will contain the result of the thunk and ` this.args.positional `
61
+ will be empty.
62
+
63
+ #### An object containing both named args and positional args
64
+
65
+ when an object is passed containing either keys: ` named ` or ` positional ` :
66
+ - ` this.args.named ` will be the value of the result of the thunk's ` named ` property
67
+ - ` this.args.positional ` will be the value of the result of the thunk's ` positional ` property
68
+
69
+ This is the same shape of args used throughout Ember's Helpers, Modifiers, etc
70
+
71
+
72
+
27
73
### ` useTask `
28
74
75
+ _ Coming soon_
76
+
77
+ This is a utility wrapper like ` useResource ` , but can be passed an ember-concurrency task
78
+ so that the ember-concurrency task can reactively be re-called whenever args change.
79
+ This largely eliminates the need to start concurrency tasks from the constructor, modifiers,
80
+ getters, etc.
81
+
82
+ A concurrency task accessed via ` useTask ` is only "ran" when accessed, and automatically updates
83
+ when it needs to.
84
+
85
+ ``` ts
86
+ class MyClass {
87
+ myData = useTask (this , this .myTask , () => [args , to , task ])
88
+
89
+ @task
90
+ * myTask(args , to , task ) { /* ... */ }
91
+ }
92
+ ```
93
+
29
94
### Making your own Resources with
30
95
31
96
#### ` LifecycleResource `
32
97
98
+ This resource base class has 3 lifecycle hooks:
99
+ - ` setup ` - called upon first access of the resource
100
+ - ` update ` - called when any ` tracked ` used during ` setup ` changes
101
+ - ` teardown ` - called when the containing context is torn down
102
+
103
+ An example of this might be an object that you want to have perform some
104
+ complex or async behavior
105
+
106
+ ``` ts
107
+ class MyResource extends LifecycleResource {
108
+ @tracked isRunning;
109
+ @tracked error;
110
+
111
+ get status() {
112
+ if (this .isRunning ) return ' pending' ;
113
+ if (this .error ) return this .error ;
33
114
115
+ return ' idle' ;
116
+ }
117
+
118
+ setup() {
119
+ this .doAsyncTask ();
120
+ }
121
+
122
+ update() {
123
+ this .doAsyncTask ();
124
+ }
125
+
126
+ async doAsyncTask() {
127
+ // need to consume potentially tracked data so that
128
+ // update may be called when these args change
129
+ let [ids] = this .args .positional ;
130
+
131
+ // defer to next (micro)task queue to not block UI
132
+ // (and avoid double render bugs because we're about to set tracked data)
133
+ await Promise .resolve ();
134
+
135
+ this .isRunning = true ;
136
+ this .error = undefined ;
137
+
138
+ try {
139
+ // some long running stuff here
140
+ } catch (e ) {
141
+ this .error = e
142
+ }
143
+
144
+ this .isRunning = false ;
145
+ }
146
+ }
147
+ ```
148
+
149
+ Using your custom Resource would look like
150
+ ``` ts
151
+ class ContainingClass {
152
+ data = useResource (this , MyResource , () => [this .ids ])
153
+ }
154
+ ```
155
+
156
+ #### ` function ` Resources
157
+
158
+ While functions can be "stateless", Resources don't provide much value unless
159
+ you can have state. ` function ` Resources solve this by passing the previous
160
+ invocation's return value as an argument to the next time the function is called.
161
+
162
+ Example:
163
+ ``` ts
164
+ class StarWarsInfo {
165
+ // access result on info.value
166
+ info = useResource (this , async (state , ... args ) => {
167
+ if (state ) {
168
+ let { characters } = state ;
169
+
170
+ return { characters };
171
+ }
172
+
173
+ let [ids] = args ;
174
+ let response = await fetch (` /characters/${ids } ` ) ;
175
+ let characters = await response .json ();
176
+
177
+ return { characters };
178
+ }, [this .ids /* defined somewhere */ ])
179
+ }
180
+ ```
181
+
182
+ While this example is a bit contrived, hopefully it demonstrates how the ` state ` arg
183
+ works. During the first invocation, ` state ` is falsey, allowing the rest of the
184
+ function to execute. The next time ` this.ids ` changes, the function will be called
185
+ again, except ` state ` will be the ` { characters } ` value during the first invocation,
186
+ and the function will return the initial data.
187
+
188
+ This particular technique could be used to run any async function _ safely_ (as long
189
+ as the function doesn't interact with ` this ` ).
190
+
191
+ In this example, where the function is ` async ` , the "value" of ` info.value ` is ` undefined ` until the
192
+ function completes.
34
193
35
194
36
195
## Contributing
@@ -41,3 +200,13 @@ See the [Contributing](CONTRIBUTING.md) guide for details.
41
200
## License
42
201
43
202
This project is licensed under the [ MIT License] ( LICENSE.md ) .
203
+
204
+
205
+ ## Thanks
206
+
207
+ This library wouldn't be possible without the work of:
208
+ - [ @pzuraq ] ( https://github.com/pzuraq )
209
+ - [ @josemarluedke ] ( https://github.com/josemarluedke )
210
+
211
+ So much appreciate for the work both you have put in to Resources <3
212
+
0 commit comments