Repository: sveltejs/svelte (JavaScript)
Date: 2018-05-24 12:35:35+00:00
Task type: Bug Fix

Issue:
<svelte:component> bindings don't run on change
Sibling issue to #1488 — when a `<svelte:component>` is first created, Svelte runs `_bind`. But that doesn't happen when the `this` value changes. [REPL](https://svelte.technology/repl?version=2.6.3&gist=44ee86d9f03e5dbca311e3b3782aab4f)


Issue discussion:

Solution patch:
diff --git a/src/compile/nodes/Component.ts b/src/compile/nodes/Component.ts
--- a/src/compile/nodes/Component.ts
+++ b/src/compile/nodes/Component.ts
@@ -290,7 +290,7 @@ export default class Component extends Node {
 				}
 
 				statements.push(deindent`
-					if (${binding.prop} in ${binding.obj}) {
+					if (${binding.value.snippet} !== void 0) {
 						${name_initial_data}.${binding.name} = ${binding.value.snippet};
 						${name_updating}.${binding.name} = true;
 					}`
@@ -304,7 +304,7 @@ export default class Component extends Node {
 				updates.push(deindent`
 					if (!${name_updating}.${binding.name} && ${[...binding.value.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
 						${name_changes}.${binding.name} = ${binding.value.snippet};
-						${name_updating}.${binding.name} = true;
+						${name_updating}.${binding.name} = ${binding.value.snippet} !== void 0;
 					}
 				`);
 			});
@@ -318,7 +318,7 @@ export default class Component extends Node {
 
 			// TODO use component.on('state', ...) instead of _bind
 			componentInitProperties.push(deindent`
-				_bind: function(changed, childState) {
+				_bind(changed, childState) {
 					var ${initialisers};
 					${builder}
 					${hasStoreBindings && `#component.store.set(newStoreState);`}
@@ -328,7 +328,7 @@ export default class Component extends Node {
 			`);
 
 			beforecreate = deindent`
-				#component.root._beforecreate.push(function() {
+				#component.root._beforecreate.push(() => {
 					${name}._bind({ ${this.bindings.map(b => `${b.name}: 1`).join(', ')} }, ${name}.get());
 				});
 			`;
@@ -406,6 +406,14 @@ export default class Component extends Node {
 
 					if (${switch_value}) {
 						${name} = new ${switch_value}(${switch_props}(ctx));
+
+						${this.bindings.length > 0 && deindent`
+						#component.root._beforecreate.push(() => {
+							const changed = {};
+							${this.bindings.map(binding => deindent`
+							if (${binding.value.snippet} === void 0) changed.${binding.name} = 1;`)}
+							${name}._bind(changed, ${name}.get());
+						});`}
 						${name}._fragment.c();
 
 						${this.children.map(child => child.remount(name))}
@@ -554,8 +562,8 @@ export default class Component extends Node {
 
 		const expression = (
 			this.name === 'svelte:self' ? this.compiler.name :
-			isDynamicComponent ? `((${this.expression.snippet}) || @missingComponent)` :
-			`%components-${this.name}`
+			(isDynamicComponent ? `((${this.expression.snippet}) || @missingComponent)` :
+			`%components-${this.name}`)
 		);
 
 		this.bindings.forEach(binding => {


Test patch:
diff --git a/test/runtime/samples/dynamic-component-bindings-recreated-b/Green.html b/test/runtime/samples/dynamic-component-bindings-recreated-b/Green.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dynamic-component-bindings-recreated-b/Green.html
@@ -0,0 +1,11 @@
+<p>green {foo}</p>
+
+<script>
+	export default {
+		data() {
+			return {
+				foo: 'green'
+			};
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/runtime/samples/dynamic-component-bindings-recreated-b/Red.html b/test/runtime/samples/dynamic-component-bindings-recreated-b/Red.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dynamic-component-bindings-recreated-b/Red.html
@@ -0,0 +1,11 @@
+<p>red {foo}</p>
+
+<script>
+	export default {
+		data() {
+			return {
+				foo: 'red'
+			};
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/runtime/samples/dynamic-component-bindings-recreated-b/_config.js b/test/runtime/samples/dynamic-component-bindings-recreated-b/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dynamic-component-bindings-recreated-b/_config.js
@@ -0,0 +1,36 @@
+export default {
+	data: {
+		x: true
+	},
+
+	html: `
+		<p>parent green</p>
+		<p>green green</p>
+	`,
+
+	test(assert, component, target) {
+		// TODO replace this with component.set({ foo: undefined }) post-#1488
+		// component.set({ foo: undefined });
+		// delete component._state.foo;
+
+		component.set({
+			x: false,
+			foo: undefined
+		});
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>parent red</p>
+			<p>red red</p>
+		`);
+
+		component.set({
+			x: true,
+			foo: undefined
+		});
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>parent green</p>
+			<p>green green</p>
+		`);
+	}
+};
\ No newline at end of file
diff --git a/test/runtime/samples/dynamic-component-bindings-recreated-b/main.html b/test/runtime/samples/dynamic-component-bindings-recreated-b/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dynamic-component-bindings-recreated-b/main.html
@@ -0,0 +1,16 @@
+<p>parent {foo}</p>
+<svelte:component this="{x ? Green : Red}" bind:foo />
+
+<script>
+	import Green from './Green.html';
+	import Red from './Red.html';
+
+	export default {
+		data() {
+			return {
+				Green,
+				Red
+			};
+		}
+	};
+</script>
\ No newline at end of file