Description
This might be a wontfix (it's a niche problem, and it's very easily worked around) but I don't have time to fully investigate it right now, so just leaving myself some breadcrumbs:
<p>y: {{y}}</p>
{{#if x}}
<Foo bind:y/>
{{else}}
<Bar bind:y/>
{{/if}}
<Baz bind:x/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
import Baz from './Baz.html';
export default {
components: {
Foo,
Bar,
Baz
}
};
</script>
// Foo.html
<p>y: {{y}}</p>
<script>
export default {
data: () => ({
y: 'foo'
})
};
</script>
// Bar.html
<p>y: {{y}}</p>
<script>
export default {
data: () => ({
y: 'bar'
})
};
</script>
// Baz.html
<script>
export default {
data: () => ({
x: true
})
};
</script>
That looks like it should result in this...
<p>y: foo</p>
<p>y: foo</p>
...but in fact it ends up like this:
<p>y: undefined</p>
<p>y: foo</p>
That's because on the initial pass, the {{else}}
part is rendered, which initialises the binding between Main
and Bar
(because x
doesn't yet have a value). Then, Svelte encounters the <Baz bind:x>
component, and creates a new binding.
Bindings are flushed, which means that x
has a value, which means that Bar
is torn down (which has the effect of setting its _state
to {}
). Then the Bar
binding is triggered, and y
is set to undefined
in Main
.
As a starting point, no observer should be created when triggering a binding on a torn down component. (Perhaps observing a torndown component should also have no effect, or even throw — possibly a separate question.)
Changing the order of things in the template in Main.html
yields different results:
<p>y: {{y}}</p>
<Baz bind:x/>
{{#if x}}
<Foo bind:y/>
{{else}}
<Bar bind:y/>
{{/if}}
<p>y: bar</p>
<p>y: bar</p>
(bar
and not foo
because by the time <Foo bind:y/>
is created, y
has a value in the parent component, and the child won't override the parent. Removing the phantom observer would presumably fix that as well.)