I was showing someone how yielding works with Ember components and got a pretty good question that stumped me. She said: “doesn’t that go against one-way data flow?”
How would one answer this question?
I was showing someone how yielding works with Ember components and got a pretty good question that stumped me. She said: “doesn’t that go against one-way data flow?”
How would one answer this question?
That’s a good question. I would try to explain it by analogy with higher-order functions in Javascript.
Functions can accept other functions as arguments. Here is a silly example:
function print123(how) {
let numbers = [1,2,3];
for (let number of numbers) {
how(number);
}
}
function toConsole(number) {
console.log(number);
}
function toConsoleWithExclamation(number) {
console.log(`${number}!`);
}
print123(toConsole);
print123(toConsoleWithExclamation);
And a convenient thing we do when calling higher-order functions is define their argument anonymously, inline:
function example1() {
print123(number => {
console.log(`${number}!`);
});
}
Notice that console.log
appears “inside” example1
, but it’s not really part of example1
in the usual way. It’s still in a separate function, that just happens to be nested inside example1
. print123
never calls example1
, it calls the anonymous inline function.
Now, all of this is analogous with components. When a component invokes another component, that’s like a function calling another function. (This is not just an analogy, it’s really implemented as function invocation in the glimmer virtual machine that runs your templates.)
When you invoke a component and pass it a block:
{{! templates/component/example1.hbs }}
<PrintNumbers as |number|>
<div>{{number}}</div>
</PrintNumbers>
The block is an anonymous function, just like in the Javascript case. Just as “console.log” appears inside the example1
function but is really not part of its own body, <div>{{number}}</div>
appears inside the example1
component but it’s not really part of the component’s own body. It’s a separate block that will get called (zero or one or any number of times) by the PrintNumbers
component.
So data never goes “up”. It goes down from example
to PrintNumbers
and then down again from PrintNumbers
to the anonymous block when PrintNumbers
says something like {{yield currentNumber}}
.
Ah awesome. Thinking of blocks as anonymous functions makes a lot of sense. Thanks!