Repository: sveltejs/svelte (JavaScript)
Date: 2018-04-29 03:40:13+00:00
Task type: Feature

Issue:
"current data object" in templates (re: spread)
Follow-on to #195 - 

In order to use the spread functionality to pass arbitrary properties that were applied to the current component on to child components, we need to be able to reference the current data/state object in the template.

I think `{{...state}}` and `{{...data}}` and `{{...@}}` were tossed around, but my vague impression is that `{{...this}}` is the current top pick.


Issue discussion:
One thing about template tags — `{{this}}` is already totally valid if you have a property called 'this': https://svelte.technology/repl?version=1.60.0&gist=3a6b97e3ec254df98cc99db546de4520

You can't, however, use it in expressions (even `{{(this)}}` fails). 

So I wonder if the right move here is to make `this` be an alias for the state object *regardless* of spread. Would be a breaking change for anyone using `{{this}}` (though I suspect no-one would be affected by it, realistically). Also, would need to figure out what to do here:

```html
<h2>{{this.thing}}</h2>

{{#each things as thing}}
  <p>What is {{this.thing}}?</p>
{{/each}}
```

It sort of *looks* like the second instance would resolve to `state.thing`, when `{{thing}}` by itself would equate to `state.things[i]`. Changing that would require some engineering.
> I wonder if the right move here is to make `this` be an alias for the state object regardless of spread

👍 I think this would be the least surprising behavior.  I can't think of much reason to use it outside of spread, but that would feel consistent to me.

> It sort of looks like the second instance would resolve to `state.thing`, when `{{thing}}` by itself would equate to `state.things[i]`

One of the things that I really really like about Svelte's mustache implementation compared to Ractive is that I never have to think about scopes created by different mustache features.

The only option is to `{{#each things as thing}}` and then refer to `thing`, you can't just do `.wat` or `this.wat`, you have to be explicit.

I feel pretty strongly that I want Svelte to stay this way, and never create new scopes - devs should be forced to reference `thing.wat` instead of `this.wat` inside of that loop.

`this` should just refer to the component's data object.

For one thing, unless you made `this` inside of loops inherit objects from the scopes above it (eeeugh), you wouldn't be able to use `{{...this}}` to pass in arbitrary attributes to child components in a loop.
**Question unrelated to the previous posts:** are we cool with the idea that `this` would reference only the data object, and not the component - i.e., you wouldn't be able to reference `this.method()` from the template?
(I think having `this` only reference the data object would be simplest/best, but I worry that some folks might assume it's a reference to the component object, instead of just the data object)
> are we cool with the idea that this would reference only the data object, and not the component

💯 yes, it would be deeply confusing if it had a dual meaning.

My earlier point about `this.thing` was purely that if `this` was just replaced with `state`, `{{this.thing}}` would be treated exactly the same way as `{{thing}}` currently is. In the context of an `each` block, that means it would refer to the `state.things[i]` even though it *looks* like it should refer to `state.thing` — unless we jump through some unfortunate hoops.
oh, I follow you now.  Yeah, I would expect it/want it to reference the top-level/component state :-x

Otherwise, I can't use spread in loops, and we're in the I-have-to-think-about-what-scope-I'm-in territory where I don't want to be.

How unfortunate are the hoops? >_<
So would using `this` allow you to label objects in your state `events` and not have their name collide with the `event` keyword in listeners? E.g.

```html
<h2>Calendar Events</h2>

{{#each events as event}}
  <div>
    <h4>{{ event.name }}</h4>
    <button on:click="cancelEvent(this.event)">Cancel</button>
  </div>
{{/each}}
```
> How unfortunate are the hoops? >_<

Well, #1173 changed the behaviour so that instead of passing down a `state` object followed by an arbitrary number of parameters (`(state, things, thing, i)`), plus additional params for every new `each` or `await`i block), child blocks are passed a 'context' object which has (for example) a `thing` property. So `thing` is effectively shadowed, and we can't access the top-level state within the each block. We would have to do something unpleasant like carting around a `state.__topLevelState` object for those cases.

> So would using this allow you to label objects in your state events and not have their name collide with the event keyword in listeners?

well... no. `this` in an event listener refers to the node, so that you can do this sort of thing:

```html
<input on:focus='this.select()' on:input='updateValue(this.value)' >
```
Maybe we can unite this feature with one more idea - creating computed properties which depend on all data. We can use ```rest parameter```: 
```javascript
computed: {
    props(...data) => data 
}
```
And after that ```spread operator```:
```html
<Child {{...props}} />
```
It'll also allow to pass not all data and filter it somehow:
```javascript
computed: {
    props(...data) => Object.keys(data).reduce(() => {}, {})
}
```

Just an idea.
Related: https://gitter.im/sveltejs/svelte?at=5ac652c827c509a774df1edc
I do think that the 'computed properties with a rest param' thing is an interesting idea, but there are some things we need to be careful of. It seems like there are two main ways it could work:

- The rest param receives no computed properties in its object
- The rest param receives computed properties that are not themselves based on a rest param value, but not any that are based on a rest param value

The second option is what I was focusing on in a brief Gitter conversation, but I'm now thinking that the first one is better. It's less confusing to understand (try writing docs that don't suck for that second one!), and also it's not any less powerful. If someone wants a rest-param-using-computed-property to depend on the computed properties, they can just list it as an explicit dependency with its own argument. The list of computed properties is known at compile time to Svelte, and will also be known at coding time to the developer, so there doesn't really seem to be any reason to include computed properties in the rest param.
@Conduitry 
> The list of computed properties is known at compile time to Svelte, and will also be known at coding time to the developer, so there doesn't really seem to be any reason to include computed properties in the rest param.

Completely agree. Perhaps, Store data also should be explicit:
```javascript
computed: {
     foo(bar, $baz, ...data) {
     },
     bar(qux) {
     }
}
```


What I understand is that anything we (as a community) define here will probably have two implications:

- Possible breaking of old code;
- Dual meaning as in the case of the `each` loop;

So I will suggest something that may happen to be more "friendly" and customizable to everyone, **but** it is just an idea, so maybe it is just bad (but to me it makes sense).

Why just do not add a new option, on the object exported by `export default`, to define the identifier used to identify the global state on the state? I mean, that way a few things would be possible: 

- Let the feature be totally **optional** (probably disabled by default);
- Allow use on computed properties (as the user can define the identifier and the compiled would be able to detect the identifier usage on the computed property and work accordingly);
- Avoid possible conflicts (because the definition would be managed by the user, according to the template, so it's easy to see what is the identifier associated with the global state);
- Let `each` cases be more "correctly" managed (because the option would let the user to see that the identifier mentioned is referring to the **global** state);

As an example, I see something like that happening:

```<h2>{{this.thing}}</h2>

{{#each things as thing}}
  <p>What is {{root.thing}}?</p>
{{/each}}

<script>
export default {
    stateIdentifier: "root"
}
</script>
```

I do not have an idea actually about the name used to define the option, but I do not see a problem on the approach actually. Of course, let configuration happen that way depends totally on the architecture of the compiled (which I do not know anything about).

Another option would be to define that option as a option to `compile`, but, after all, it's just ideas.

What do you think? Is that a viable option?  (Thanks for creating Svelte, by the way, I absolutely love this project!)
I made a note in #1338, but it occurs to me that this problem is solved by #1069:

```html
<Widget {...props}/>

<script>
  import Widget from './Widget.html';

  export default {
    components: { Widget },
    computed: {
      props: state => state
    }
  };
</script>
```

This gives you flexibility over the naming, adds no new syntax, and allows you to exclude props you don't want:

```js
props: state => {
  const { unwanted, alsoUnwanted, ...props } = state;
  return props;
}
```
How would this handle regular vs computed properties? Computed properties can depend on other computed properties, but things seem they would get hairy if the `state` object contained computed properties or if other computed properties depend on a computed property that accepts the `state` object.
I assume that the concept of "what is available in the state for computed properties" would stay the same as it is now - containing both raw values, and computed properties.

I write lots of computed properties that depend on other computed properties.
At the moment, the compiler uses its knowledge of each computed property's dependencies to sort them topologically:

```js
// `b` will be computed before `c`
computed: {
  c: ({ b }) => b * 2
  b: ({ a }) => a * 2
}
```

If a computed property accepts the whole state object, it just goes at the end. If you have two or more such properties, they *could* get out of order if one depends on the output of another, but I think the solution to that is a) document it, and b) preserve the order in which those properties were specified, so it's still deterministic.
well, I like the proposed idea.

With the release of the version 2, what blocks this issue from being implemented with the proposed idea? just to know 
nothing! someone just needs to make it happen. I'd probably get round to it eventually, but if anyone wants to make a PR... 😀 

I had always assumed they were processed in the order they appeared, so I
think that makes sense.

So I'm guessing then that we would not allow a computed property that explicitly depends on a computed property that depends on the whole state object? (That would prevent computed properties that accept the whole state object from all going at the end.)

But I'm still not sure what's a good way to handle passing other (normal or whole-state) computed properties to a whole-state computed property. Would each one simply get the whole state object as it currently exists, whether its current mix of updatedness and nonupdatedness? I'm imagining that would be simplest to implement, but may be confusing to use.
If whole-state computed properties get called after everything else, then the only issue would be other whole-state computeds.

There's probably no way to magically resolve how whole-object computed properties see each other - I think as long as they were resolved deterministically in order of declaration it would be fine.  Having multiples of this special case computed property would be pretty uncommon I imagine.
What if we don't do any ordering based on dependencies? Just process them in the order they are defined. This puts all the power into the hands of the developer, as well as make it explicit and clear how it works. Otherwise we are left guessing what might happen in this situation or that situation and may lead to difficult bugs to resolve.

```js
{
  computed: {
    props: state => ({ ...state }), // `props.name` may be stale
    name: ({ firstName, lastName }) => firstName + ' ' + lastName,
    props2: state => ({ ...state }), // `props2.name` will not be stale
  }
}
```

It is easy to look at the computed property and follow through what happens from top to bottom. Just like event listeners, the first ones are called first, then the second, etc. We can try to "fix" things to help you avoid bugs, but then it makes it more complex and less understandable how it works, possibly causing bugs that you can't understand or easily fix. Though, this is theoretical, I have no practical examples lying around. I do like the simplicity of top to bottom however.
There is no way I wouldn't shoot myself in the foot with that behavior :-x

I don't think there's any benefit to ever passing stale values to a single whole-state-computed function.
Well, there isn't value in stale values, just in a consistent model. I'll backtrack here. I agree with your (@TehShrike) earlier comment about putting those properties last and going in order of declaration.

Solution patch:
diff --git a/src/compile/Compiler.ts b/src/compile/Compiler.ts
--- a/src/compile/Compiler.ts
+++ b/src/compile/Compiler.ts
@@ -564,16 +564,29 @@ export default class Compiler {
 				if (templateProperties.computed) {
 					const dependencies = new Map();
 
+					const fullStateComputations = [];
+
 					templateProperties.computed.value.properties.forEach((prop: Node) => {
 						const key = getName(prop.key);
 						const value = prop.value;
 
-						const deps = value.params[0].properties.map(prop => prop.key.name);
-
-						deps.forEach(dep => {
-							this.expectedProperties.add(dep);
+						addDeclaration(key, value, false, 'computed', {
+							state: true,
+							changed: true
 						});
-						dependencies.set(key, deps);
+
+						const param = value.params[0];
+
+						if (param.type === 'ObjectPattern') {
+							const deps = param.properties.map(prop => prop.key.name);
+
+							deps.forEach(dep => {
+								this.expectedProperties.add(dep);
+							});
+							dependencies.set(key, deps);
+						} else {
+							fullStateComputations.push({ key, deps: null })
+						}
 					});
 
 					const visited = new Set();
@@ -590,16 +603,15 @@ export default class Compiler {
 						computations.push({ key, deps });
 
 						const prop = templateProperties.computed.value.properties.find((prop: Node) => getName(prop.key) === key);
-
-						addDeclaration(key, prop.value, false, 'computed', {
-							state: true,
-							changed: true
-						});
 					};
 
 					templateProperties.computed.value.properties.forEach((prop: Node) =>
 						visit(getName(prop.key))
 					);
+
+					if (fullStateComputations.length > 0) {
+						computations.push(...fullStateComputations);
+					}
 				}
 
 				if (templateProperties.data) {
diff --git a/src/compile/dom/index.ts b/src/compile/dom/index.ts
--- a/src/compile/dom/index.ts
+++ b/src/compile/dom/index.ts
@@ -64,10 +64,6 @@ export default function dom(
 
 	if (computations.length) {
 		computations.forEach(({ key, deps }) => {
-			deps.forEach(dep => {
-				computationDeps.add(dep);
-			});
-
 			if (target.readonly.has(key)) {
 				// <svelte:window> bindings
 				throw new Error(
@@ -77,11 +73,22 @@ export default function dom(
 
 			target.readonly.add(key);
 
-			const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`;
+			if (deps) {
+				deps.forEach(dep => {
+					computationDeps.add(dep);
+				});
 
-			const statement = `if (this._differs(state.${key}, (state.${key} = %computed-${key}(state)))) changed.${key} = true;`;
+				const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`;
+				const statement = `if (this._differs(state.${key}, (state.${key} = %computed-${key}(state)))) changed.${key} = true;`;
 
-			computationBuilder.addConditional(condition, statement);
+				computationBuilder.addConditional(condition, statement);
+			} else {
+				// computed property depends on entire state object —
+				// these must go at the end
+				computationBuilder.addLine(
+					`if (this._differs(state.${key}, (state.${key} = %computed-${key}(state)))) changed.${key} = true;`
+				);
+			}
 		});
 	}
 
diff --git a/src/compile/ssr/index.ts b/src/compile/ssr/index.ts
--- a/src/compile/ssr/index.ts
+++ b/src/compile/ssr/index.ts
@@ -120,8 +120,7 @@ export default function ssr(
 			ctx = Object.assign(${initialState.join(', ')});
 
 			${computations.map(
-				({ key, deps }) =>
-					`ctx.${key} = %computed-${key}(ctx);`
+				({ key }) => `ctx.${key} = %computed-${key}(ctx);`
 			)}
 
 			${target.bindings.length &&
diff --git a/src/validate/js/propValidators/computed.ts b/src/validate/js/propValidators/computed.ts
--- a/src/validate/js/propValidators/computed.ts
+++ b/src/validate/js/propValidators/computed.ts
@@ -81,14 +81,5 @@ export default function computed(validator: Validator, prop: Node) {
 				message: `Computed properties must take a single argument`
 			});
 		}
-
-		const param = params[0];
-		if (param.type !== 'ObjectPattern') {
-			// TODO post-v2, allow the entire object to be passed in
-			validator.error(computation.value, {
-				code: `invalid-computed-argument`,
-				message: `Computed property argument must be a destructured object pattern`
-			});
-		}
 	});
 }


Test patch:
diff --git a/test/runtime/samples/computed-state-object/_config.js b/test/runtime/samples/computed-state-object/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/computed-state-object/_config.js
@@ -0,0 +1,21 @@
+export default {
+	data: { a: 1 },
+
+	html: `
+		<p>a: 1</p>
+		<p>x: 2</p>
+		<p>y: 4</p>
+		<p>z: 8</p>
+	`,
+
+	test(assert, component, target) {
+		component.set({ a: 2 });
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>a: 2</p>
+			<p>x: 4</p>
+			<p>y: 8</p>
+			<p>z: 16</p>
+		`)
+	},
+};
diff --git a/test/runtime/samples/computed-state-object/main.html b/test/runtime/samples/computed-state-object/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/computed-state-object/main.html
@@ -0,0 +1,14 @@
+<p>a: {a}</p>
+<p>x: {x}</p>
+<p>y: {y}</p>
+<p>z: {z}</p>
+
+<script>
+	export default {
+		computed: {
+			y: state => state.x * 2,
+			z: state => state.y * 2,
+			x: ({ a }) => a * 2
+		}
+	};
+</script>