- Start Date: 2017-01-14
- RFC PR: (leave this empty)
- Ember Issue: (leave this empty)
Introduce a let
keyword to bind values to names within a handlebars
template.
Block form:
or inline:
The primary method for supplying named values to a template is via properties on its backing scope (either a controller or a component). These properties are specified in JavaScript which, while as a mechanism is very well understood by the community, also comes with several drawbacks.
The first is that it spreads the requisite knowledge to understanding
a template across two places. In other words, it is impossible to understand
what exactly it does without simultaneously holding both the .hbs
and .js
files together and continuously merging them with your
mind. The lack of this overhead is one of the commonly cited strengths
of JSX over Handlebars.
Another is that computed properties are error-prone because of their requirement to explicitly enumerate dependencies. If the computation of a property has incorrectly specified dependent keys it will break in mysterious ways that usually involve a sudden and hard-to-reproduce lack of reactivity to changing inputs.
In order to avoid these pitfalls, developers have begun moving more
and more computation into templates, and the rise of popular of addons
like ember-truth-helpers
, ember-math-helpers
and
ember-composable-helpers
are strong evidence of this pressure.
For example:
This solves the problem of proximity by placing the derivation of a value right next to where it's used. There's no question about how the disabled attribute is computed. It also solves the dependency problem because the dependencies of an expression are implicity enumerated merely through the act of expressing it.
While these are advantages that a large portion of the community finds empowering, they do not come without drawbacks.
If an expression becomes sufficiently complex, then it actually begins to reduce clarity rather than improve it; especially when you end up using it more than once.
Consider:
The moment you need to re-use values, or compose them from constituent values that are themselves re-used, you need to unroll the whole thing and move towards composition via computed properties.
The let
helper relieves this pressure by allowing you to bind the
value of computation to a name so that it can be used to derive
subsequent values. It preserves the advantages of proximity and
dependency management, while retaining the re-use and composability
of computed properties.
With let
, the above example can be re-written as:
By allowing you to bind a computation to a name, the let
helper lets
you define computed properties right within your template.
The let
keyword is implemented as a block helper that takes
its positional parameters and yields them on to passed block. In this
way, it is very similar to the with
keyword (in fact, the original
implementation was cribbed from there). The only difference is that
let
will always render its block. Whereas with
will not
render if it is passed an empty list, null
, undefined
, false
, or
an empty string. This counterintuitive behavior leads to nasty suprises
and difficult to workaround situations.
On the other hand, let
binds all values equitably.
An inline form is also supported via a syntax transform, so
expands to
Like let
in JavaScript and most functional languages, bindings can be
"temporarily" overriden within a scope, and then "restored" once they
pass out of scope:
Since this expands to (approximately):
For a reference implementation. See https://github.com/thefrontside/ember-let
let
is an extraordinarily common concept for programmers of all
stripes, and so while it is new to Ember templates, it still enjoys the
familiarity only 60 years of ubiquity can bring. It makes sense to
lean on that shared understanding to introduce it to the Ember
community. It's about binding a name to value full stop.
At the same time, Embereños are familiar with computed properties, and
in many cases you could use a let
expression instead of a computed
property. In other words, let
performs a very similar function to a
computed property: it assigns a referencable name to a value that is
purely derived from other values.
Anywhere in the guides that uses with
would be a
candidate for using let
instead, but it would also be helpful to
have a section on "when to use computed properties" and "when to use
let" since let
is not a silver bullet. An audit of all the examples
contained in the guides with an eye to how they might be improved via
the use of let
or confirmed as much better using CPs would serve to
mark and understand these use cases.
This knocks down some barriers that today keep people from declaring more computation in their templates. While there are stronger guarantees of purity with computations performed in handlebars, it does introduce a fundamental question that developers will need to ask themselves when specifying each computation: "should I put this in a template or should I put it in my controller / component?" That overhead as deveolpers attempt to figure out where that line is drawn could be cumbersome.
Another potential drawback is that computations declared in Handlebars cannot be shared back to JavaScript except via an action. We might find computations duplicated in templates that really could be avoided by having a strong shared model with computed properties.
Doing nothing is one approach. with
is almost let
, and does handle
many simple cases.
See the Detailed design section for the caveats of
using with
As proposed, let
bindings happen in parallel, which is to say,
binding expressions within a let
expression are invisible to each
other:
Many languages have a let*
form which binds values in sequence
which can be nice if you have lots of dependent derived values:
Many languages also have a function similar to Ember's with
called
if-let
which only binds and evaluates its body if its test is
truthy. Something to consider is deprecating with
in favor of
if-let
to align Ember with wider programming practice and re-enforce
what with
is actually doing.