Status: IMPLEMENTED
Champion: Unknown
Revision: latest
RFC created: 1970/01/01
Last updated: unknown
This RFC has been merged
# Else and Else-If Directives
# Summary
This proposal supersedes the existing if:true
and if:else
directives to add directives that express "else" and "else if" logic.
# Basic example
<!-- component.html -->
<template>
<template lwc:if={abra}>
Abra!
</template>
<template lwc:elseif={kadabra}>
Kadabra!
</template>
<template lwc:else>
Alakazam!
</template>
</template>
# Motivation
As of today, without "else" or "else if" directives, the developer has to chain if:true
and if:false
blocks together:
<template>
<template if:true={abra}>
Abra!
</template>
<template if:false={abra}>
<template if:true={kadabra}>
Kadabra!
</template>
<template if:false={kadabra}>
Alakazam!
</template>
</template>
</template>
This is awkward and unintuitive, and has an additional performance cost in that the property getters
(in this case, abra
and kadabra
) are called multiple times, when once should suffice.
# Prior art
The concept of "else" and "else if" exists in most programming languages, and in many other frameworks:
- Vue:
v-else
andv-else-if
- Svelte:
:else
and:else if
- Angular:
ngIfElse
- Solid:
<Show>
and<Match>
- Aura:
aura:set attribute="else"
Some other frameworks, such as React, Stencil, and Lit, have no built-in concept of else
or else if
, other than that provided by JavaScript itself.
In the case of LWC, since we already have if:else
and if:false
, and since there is no ergonomic way to do conditional rendering in JavaScript (other than render()
, which requires creating separate template HTML files), it would be most natural to align with the first group of frameworks.
# Detailed design
The basic design is to add three new directives:
lwc:if
lwc:elseif
lwc:else
The reason for not reusing the existing if:true
or if:else
is that it is more consistent to use lwc:*
for all three. (If there is a pressing need for negation, then we may consider a {!foo}
or similar expression syntax in the future.)
# Syntax
Both lwc:elseif
and lwc:else
must be immediately preceded by a sibling lwc:if
or lwc:elseif
.
<!-- Valid -->
<template lwc:if={foo}></template>
<template lwc:else></template>
<!-- Invalid -->
<template lwc:if={foo}></template>
<div>Yolo!</div>
<template lwc:else></template>
Whitespace is ignored when considering siblinghood. (This is consistent with LWC's current behavior, which is to remove whitespace between elements.)
Furthermore, these directives (along with if:true
and if:false
) are mutually exclusive, and multiple cannot be applied to the same element:
<!-- Invalid -->
<template lwc:if={foo} if:true={foo}></template>
<!-- Invalid -->
<template lwc:if={foo}></template>
<template lwc:elseif={bar} lwc:else></template>
The existing if:true
and if:else
directives cannot be combined with lwc:elseif
or lwc:else
:
<!-- Invalid -->
<template if:true={foo}></template>
<template lwc:elseif={bar}></template>
<!-- Invalid -->
<template if:true={foo}></template>
<template lwc:else></template>
# Code comments
In the case of code comments, the behavior depends on whether lwc:preserve-comments
is enabled or not. When not preserving comments, comments may appear between conditional siblings:
<!-- Valid -->
<template>
<template lwc:if={foo}></template>
<!-- Comment! -->
<template lwc:else></template>
</template>
Whereas when lwc:preserve-comments
is enabled, comments become syntactically meaningful and therefore cannot be placed between sibling conditional directives:
<!-- Invalid -->
<template lwc:preserve-comments>
<template lwc:if={foo}></template>
<!-- Comment! -->
<template lwc:else></template>
</template>
# Accessors
Similar to if:true
and if:false
, the expression passed in to lwc:if
and lwc:elseif
must use simple dot notation.
More complex expressions (e.g. !foo
, foo?.bar?.baz
, or foo % 2 === 1
) are currently not supported.
# Slots
A developer may want to use if/else-if/else chains to render a <slot>
:
<template>
<template lwc:if={foo}>
<slot></slot>
</template>
<template lwc:else>
<slot></slot>
</template>
</template>
In this case, the template compiler should not warn about duplicate slots with the same name. This is a perfectly valid use case, and the template compiler knows for certain that the <slot>
will not be rendered twice.
You might contrast this with the equivalent code in if:true
/ if:false
:
<template>
<template if:true={foo}>
<slot></slot>
</template>
<template if:false={foo}>
<slot></slot>
</template>
</template>
Today, the template compiler does warn about duplicate slots for this case. But this actually makes sense, because the compiler cannot be certain that the <slot>
will only render once. (For instance, the foo
getter may have an inconsistent return value, returning true
the first time and false
the second time.)
# Attribute values
To avoid typos (e.g. using lwc:else
when you mean lwc:elseif
), lwc:else
must not have an attribute value. Any attribute value is treated as a compile-time error:
<!-- Valid -->
<template lwc:if={foo}></template>
<template lwc:else></template>
<!-- Invalid -->
<template lwc:if={foo}></template>
<template lwc:else={yolo}></template>
Similarly, lwc:if
and lwc:elseif
must have an expression as their value:
<!-- Valid -->
<template lwc:if={foo}></template>
<template lwc:elseif={bar}></template>
<!-- Invalid -->
<template lwc:if="foo"></template>
<template lwc:elseif="bar"></template>
<!-- Invalid -->
<template lwc:if={foo}></template>
<template lwc:elseif></template>
# Semantics
lwc:if
behaves the same as if:true
. lwc:elseif
behaves the same as lwc:if
, except that its behavior depends on any preceding lwc:if
/lwc:elseif
statements.
Similarly, the behavior of lwc:else
depends on its previous siblings.
For example, in a series of sibling lwc:if
/ lwc:elseif
/ lwc:else
directives, the inverse of the previous evaluated expression determines whether we reach the next directive:
<template lwc:if={abra}>
Abra!
</template>
<template lwc:elseif={kadabra}>
Kadabra!
</template>
<template lwc:elseif={hocus}>
Hocus pocus!
</template>
<template lwc:else>
Kadabra!
</template>
Rather than rehash the basics of conditional logic in programming, let's define this as being analogous to the if
statement (and friends) in JavaScript:
if (component.abra) {
// Abra!
} else if (component.kadabra) {
// Kadabra!
} else if (component.hocus) {
// Hocus pocus!
} else {
// Kadabra!
}
Note that this means:
- The property getters are only accessed once per instance of an
lwc:if
orlwc:ifelse
. - Property getter access is determined by the ordering. In the above case, if
component.abra
is truthy, then none of the other property getters will be accessed.
# Drawbacks
This implementation would require the template parser to have knowledge of sibling nodes, rather than treating each sibling node independently.
This implementation would also supersede the existing if:true
and if:false
directives, which may lead to some confusion for developers.
# Alternatives
The impact of not doing this is that it's much trickier to do conditional logic in LWC templates.
# Adoption strategy
This is a non-breaking change, so users can opt-in as they see fit.
# How we teach this
This feature is very similar to the same concept in other frameworks (Vue, Svelte, Angular, etc.). It's also similar to if
and else
in JavaScript. So we can leverage that familiarity when teaching this.
As for the existing if:true
and if:else
statements, we will need to mark them as "legacy," since they are rendered redundant by the new lwc:if
. To ease the transition, we can provide:
- Make the template compiler warn about using
if:true
andif:else
. - Codemods to transform code that uses
if:true
to the equivalentlwc:if
usages instead. It should be possible for this codemod to be consistent and non-destructive. (if:false
is not so easy to replace, but it may be possible with a future, more complex expression syntax.) It may also be possible to replace anif:true={foo}
/if:false={foo}
chain with the equivalentlwc:if
/lwc:else
chain, but this is not 100% semantically equivalent (due to the number of accessor calls), and would be trickier to implement.
# Unresolved questions
The existing if:true
and if:false
directives work on arbitrary elements, not just <template>
s. Should lwc:if
/lwc:elseif
/lwc:else
work the same way?
How does this affect the template AST?