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

Feature - deep $watch #2462

Merged
merged 3 commits into from Dec 20, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/alpinejs/src/magics/$watch.js
Expand Up @@ -10,9 +10,8 @@ magic('watch', el => (key, callback) => {
let oldValue

effect(() => evaluate(value => {
// This is a hack to force deep reactivity for things like "items.push()".
let div = document.createElement('div')
div.dataset.throwAway = value
// JSON.stringify touches every single property at any level enabling deep watching
JSON.stringify(value)

if (! firstTime) {
// We have to queue this watcher as a microtask so that
Expand Down
22 changes: 22 additions & 0 deletions packages/docs/src/en/magics/watch.md
Expand Up @@ -35,3 +35,25 @@ When the `<button>` is pressed, `foo.bar` will be set to "bob", and "bob" will b
<button @click="open = ! open">Toggle Open</button>
</div>
```

<a name="deep-watching"></a>
### Deep watching

`$watch` will automatically watches from changes at any level but you should keep in mind that, when a change is detected, the watcher will return the value of the observed property, not the value of the subproperty that has changed.

```alpine
<div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo', (value, oldValue) => console.log(value, oldValue))">
<button @click="foo.bar = 'bob'">Update</button>
</div>
```

When the `<button>` is pressed, `foo.bar` will be set to "bob", and "{bar: 'bob'} {bar: 'baz'}" will be logged to the console (new and old value).

> ⚠️ Changing a property of a "watched" object as a side effect of the `$watch` callback will generate an infinite loop and eventually error.

```alpine
<!-- 🚫 Infinite loop -->
<div x-data="{ foo: { bar: 'baz', bob: 'lob' }}" x-init="$watch('foo', value => foo.bob = foo.bar">
<button @click="foo.bar = 'bob'">Update</button>
</div>
```
22 changes: 22 additions & 0 deletions tests/cypress/integration/magics/$watch.spec.js
Expand Up @@ -148,3 +148,25 @@ test('$watch ignores other dependencies',
get('span').should(haveText('1'))
}
)


test('deep $watch',
html`
<div x-data="{ foo: { bar: 'baz'}, bob: 'lob' }" x-init="
$watch('foo', value => { bob = value.bar }, {deep: true});
">
<h1 x-text="foo.bar"></h1>
<h2 x-text="bob"></h2>

<button x-on:click="foo.bar = 'law'"></button>
</div>
`,
({ get }) => {
get('h1').should(haveText('baz'))
get('h2').should(haveText('lob'))
get('button').click()
get('h1').should(haveText('law'))
get('h2').should(haveText('law'))
}
)