Skip to content

Commit 32326fb

Browse files
committed
Fix missing imports, add more advanced observer replacment options
1 parent f446e79 commit 32326fb

File tree

2 files changed

+102
-4
lines changed

2 files changed

+102
-4
lines changed

content/ember/v6/deprecate-ember-object-observable.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ When working with legacy computed properties, the way you set matters for reacti
8787
To trigger reactivity (like re-computing a dependent computed property) when changing a plain property, you **must** use the `set` function. A native JavaScript assignment (`person.firstName = 'Jane'`) will change the value but will **not** trigger reactivity.
8888
8989
```javascript
90-
import { computed, set } from '@ember/object';
91-
import EmberObject from '@ember/object';
90+
import EmberObject, { computed, set } from '@ember/object';
9291

9392
class Person {
9493
// These properties are NOT tracked
@@ -116,8 +115,7 @@ console.log(person.fullName); // 'Jane Doe'
116115
In contrast, if a computed property is defined with its own setter, you **can** use a native JavaScript assignment to update it. Ember will correctly intercept this and run your setter logic.
117116
118117
```javascript
119-
import { computed } from '@ember/object';
120-
import EmberObject from '@ember/object';
118+
import EmberObject, { computed, set } from '@ember/object';
121119

122120
class Person {
123121
firstName = 'John';

content/ember/v6/deprecate-ember-object-observers.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,104 @@ This approach is much cleaner because:
210210
2. It clearly separates the component's data and template from the DOM-specific logic.
211211
3. The modifier's lifecycle (setup, update, teardown) is managed by Ember, making it more robust.
212212

213+
### Replacing Observer-Based Waiting Patterns
213214

215+
Sometimes observers were used not just to mirror state into another property or invoke a side effect immediately, but to "wait" until a property reached a certain condition (e.g. became non-null / a flag flipped) and then continue logic. Instead of wiring an observer that removes itself when the predicate passes, choose one of these approaches:
216+
217+
#### 1. Prefer Reactive Rendering (Often You Need Nothing Extra)
218+
219+
If the goal is just to show different UI when something becomes ready, branch in the template:
220+
221+
```hbs
222+
{{#if this.isReady}}
223+
<LoadedState @data={{this.data}} />
224+
{{else}}
225+
<LoadingSpinner />
226+
{{/if}}
227+
```
228+
229+
`this.isReady` should be a `@tracked` property (or derived from other tracked state). No explicit watcher is required; changes trigger re-render automatically.
230+
231+
#### 2. requestAnimationFrame Polling (UI-Frame Cadence)
232+
233+
Use when a rapidly changing UI-related value will settle soon and you want per-frame checks without observers:
234+
235+
```js
236+
export class RafWaiter {
237+
constructor(object, key, predicate, callback, { maxFrames = Infinity } = {}) {
238+
this.object = object;
239+
this.key = key;
240+
this.predicate = predicate;
241+
this.callback = callback;
242+
this.maxFrames = maxFrames;
243+
this._frame = 0;
244+
this._stopped = false;
245+
}
246+
247+
start() {
248+
const tick = () => {
249+
if (this._stopped) return;
250+
let value = this.object[this.key];
251+
if (this.predicate(value)) {
252+
this.callback(value);
253+
this.stop();
254+
return;
255+
}
256+
if (++this._frame < this.maxFrames) {
257+
this._rafId = requestAnimationFrame(tick);
258+
}
259+
};
260+
tick(); // initial synchronous check
261+
return () => this.stop();
262+
}
263+
264+
stop() {
265+
this._stopped = true;
266+
if (this._rafId) cancelAnimationFrame(this._rafId);
267+
}
268+
}
269+
270+
// Usage:
271+
// const cancel = new RafWaiter(model, 'isReady', v => v === true, value => doSomething(value)).start();
272+
```
273+
274+
#### 3. ember-concurrency Task (Interval / Backoff Friendly)
275+
276+
Provides structured cancellation and adjustable cadence; integrate with destruction for safety.
277+
278+
```js
279+
import { task, timeout } from 'ember-concurrency';
280+
import { registerDestructor } from '@ember/destroyable';
281+
282+
class WaitServiceLike {
283+
constructor(object) {
284+
this.object = object;
285+
registerDestructor(this, () => this.waitFor?.cancelAll?.());
286+
}
287+
288+
waitFor = task(async (key, predicate, { interval = 50, maxChecks = Infinity } = {}) => {
289+
let checks = 0;
290+
while (checks < maxChecks) {
291+
let value = this.object[key];
292+
if (predicate(value)) {
293+
return value;
294+
}
295+
checks++;
296+
await timeout(interval);
297+
}
298+
throw new Error('Condition not met before maxChecks');
299+
});
300+
}
301+
302+
// Usage:
303+
// let waiter = new WaitServiceLike(model);
304+
// let result = await waiter.waitFor.perform('isReady', v => v === true, { interval: 30 });
305+
```
306+
307+
#### Choosing an Approach
308+
309+
- Template branching: simplest; no manual cleanup.
310+
- `requestAnimationFrame`: sync with paint loop for short-lived UI readiness waits.
311+
- `ember-concurrency` task: tunable intervals, cancellation, good for async processes.
312+
313+
Avoid reimplementing implicit observer semantics. Favor explicit state + rendering or cancellable tasks.

0 commit comments

Comments
 (0)