Replies: 163 comments 479 replies
-
Happy to see we have improvements on the side of trackby. The previous implementation was so off putting ! I see this new synthax as a big DX improvement 🔥🔥 |
Beta Was this translation helpful? Give feedback.
-
Does this new syntax cover the case when the same template is used for multiple else blocks?
Right now I see that it can be achieved only via
|
Beta Was this translation helpful? Give feedback.
-
Re: Discussion Question 5A: are there other flavors of looping which would benefit your applications today?
How come the third-party Angular ecosystem will not have the same capabilities as the Angular framework? Until now, features like these and Deferred Loading have been supported by Angular's common directives and pipes that have not had special capabilities. This has led to innovations such as the let directive, the push pipe, and RxAngular Template declarables like |
Beta Was this translation helpful? Give feedback.
-
Is there any chance we could see this applied to content projection ? Generally speaking, is there any plan to extend this structural directive remodeling to other Angular-specific matters ? |
Beta Was this translation helpful? Give feedback.
-
About the syntax not being as HTML elements. ¿Have been there any other considerations about how this affects other IDEs outside VS Code language servers? I have no problem sometimes using a simple HTML editor to edit a template and have collapsible control structures in the editor because they were added to elements, now that they will look like any other CDATA on the HTML file, editors must be adapted to understand that a big {#if.... content can be collapsed. |
Beta Was this translation helpful? Give feedback.
-
Syntax looks powerful, but with this I think we should use new template extension like .angular , so all IDEs can easily identify & format it. |
Beta Was this translation helpful? Give feedback.
-
This is very unfortunate. I think that this change is a great opportunity to make this syntax available for ALL structural directives. This would:
|
Beta Was this translation helpful? Give feedback.
-
I would also vote for @if(a > b)
{{a}} is greater than {{b}}
@elseif(b > a)
{{a}} is less than {{b}}
@else
{{a}} is equal to {{b}}
@endif @switch(condition)
@case(A)
Case A.
@case(B)
Case B.
@case(C)
Case C.
@default
Default case.
@endswitch @for(item of items; track item.name)
<li>{{ item.name }}</li>
@empty
<li>There are no items</li>
@endfor Btw. even if mustache-like syntax will stay - why is it |
Beta Was this translation helpful? Give feedback.
-
i do like the "new" syntax but i'm unsure about a deprecation & future removal of the old syntax. Sometimes if a have a very sinple code like <my-cmp *ngIf="variable" /> it would extend into two more lines because i have to open and close an if 🤔 |
Beta Was this translation helpful? Give feedback.
-
With the switch, could we get some way of getting errors when a case is left out. |
Beta Was this translation helpful? Give feedback.
-
Awesome! however, the new if syntax is a bit complex when you need a simple if case:
can we at least have some shorthand syntax for simple if statement? |
Beta Was this translation helpful? Give feedback.
-
3 differents symbols is too complicated. Keep it simpler with a single symbol, eg
I'd say optional. But really it wouldn't make much of difference for me if it was mandatory.
Do not require track. Because less experienced, or in a hurry developers, will fill it in with some "default value" without actually thinking about it. And then it would be a pain to review every single track to know whether the "default value" is indeed what is best for performance, or if it was just the developer laziness. In contrast, the absence of track is an easy indicator that something could be done to improve performance of that particular template if the need arise, at a later point.
No.
No. Short syntaxPlease, please, please, do come up with an alternative short syntax for simple use of One suggestion already made, looks very reasonable to me: <div @if="users">
<h1>List of users:</h1>
<ul>
<li @foreach="users as user">{{ user.name }}</li>
</ul>
</div> That is different enough for the compiler to do something different than the existing {#if users}
<div>
<h1>List of users:</h1>
<ul>
{#for user of users; track user.id}
<li>{{ user.name }}</li>
{/for}
</ul>
</div>
{/if} |
Beta Was this translation helpful? Give feedback.
-
Am I the only one here that absolutely despises this new syntax? Apparently not, considering that a large chunk of the emoji responses are negative (down thumbs, unsure face). Enforcing this change, and moving away from directives will directly influence my team to migrate away from Angular to Vue for all new projects. I enjoy Angular for it's clean presentation and strong separation of concern. Directives such as What I despise about this is that there is something other than HTML attributes, tags or directives being added into the HTML. It makes it harder to quickly and effectively read the HTML file because now you have to consider nested logic. It's one of the main reasons that I despise React because despite managing multiple React engineers over the last half decade, almost every single code base I've read is practically unreadable and removed from any concept of 'clean code' due to the monstrosity that is JSX. Obviously this is just my opinion, and I accept it being a 'hot take'. RIP Angular 2023. 😭 |
Beta Was this translation helpful? Give feedback.
-
I'm getting Jinja vibes and they are not good at all. 3 Lines of markup instead of 1 plus it looks VERY foreign to angular. I'm getting a feeling that due to signals there will be so many changes, like reactive forms, router, etc. that at some point it would be logical to create a new framework (hello, angular.js story) |
Beta Was this translation helpful? Give feedback.
-
I guess each block has to be a valid view, right? So I'm tempted to write something responsive like (maybe with an own block name for breakpoint conditions) <div
{#if lt-md}
class="col-layout"
{:else}
class="row-layout"
{/if}
other="attribute">
...
</div> If every block has to be a valid template, then a tag like syntax would be clearer. Maybe one tag to rule them all? <ng [if]="condition">
...
<ng else [if]="...">
...
</ng>
<ng else>
...
</ng>
</ng> And like |
Beta Was this translation helpful? Give feedback.
-
1A make it for newcomers easy to understand
How to apply the trackBy function for this case? To my understanding a change of the status would not be recognized if not tracked. |
Beta Was this translation helpful? Give feedback.
-
The proposal of RFC grammar and early many PHP template engine is too similar, but only the last laravel blade survived at https://laravel.com/docs/10.x/blade foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
<li>{{ $user->name }}</li>
@if ($user->number == 5)
@break
@endif
@endforeach |
Beta Was this translation helpful? Give feedback.
-
I like the idea very much however I don't really like the way the syntax looks like. It seems counter intiutive. I'm not saying that it should be different but I would like to ask if we could maybe make it more looking like javascript. if a > b // Can't you make it so we don't have this ugly brackets? Or if it starts with # the hole line if like that maybe? Another idea would by why don't we get just JSX or something simular you would not need backwards compatibility because you could just change the html to be htmlx or something like that... Just some ideas. Great to see we finally are moving towards a more modern approach! |
Beta Was this translation helpful? Give feedback.
-
I think the twig template engine syntax would be easier to read and remember: basically every statement (if, endif, switch, case, etc.) is wrapped in {% %}. This way you dont have to remember when to use "#", ":", etc. |
Beta Was this translation helpful? Give feedback.
-
Good addition to the framework, keep the good work! 😄 1A: They are fine as presented. Additional question for |
Beta Was this translation helpful? Give feedback.
-
I wonder if it would be easier to read when placing the tag ":" at the closing curly braces, instead of "#" or ":" at the opening one. Besides readability, I love this proposal, thanks for the work!
|
Beta Was this translation helpful? Give feedback.
-
Can someone describe how should I use structural directives with the new syntax? Will it look something like this?
If so, how it should work in zoneless app? I mean that structural directive implementation is similar to the old control flow one, so it should call the same problems, isn't it? |
Beta Was this translation helpful? Give feedback.
-
I overall like the idea of the new syntax, the old one was too verbose for writing an
The choice of
Instead of those 3 rune-like characters (
Which would make the syntax more homogeneous.
If parentheses are not needed, I think they shouldn't be made mandatory. The whole new syntax doesn't look like JavaScript anyways, and doesn't need to be: different concept for Besides, this syntax draws inspiration from Svelte, which doesn't enforce parentheses, so it makes more sense to me to try to make it look like Svelte instead of JavaScript.
I would prefer to not make it "required" but optional as it's today. In many cases our app need to display elements that don't have an identity field (and which don't change over time), so forcing to specify something like Thanks. |
Beta Was this translation helpful? Give feedback.
-
The section about
What exactly can can go in these expressions today (for both |
Beta Was this translation helpful? Give feedback.
-
Can I build my custom control flow syntax as I do with structural
directives? 🤔
…On Mon, Jun 26, 2023, 12:45 PM michael faith ***@***.***> wrote:
This is not jsx tho. And personally it's far more readable and easier to
parse, than what we have with the structural directives today where they're
"buried" in the html elements.
—
Reply to this email directly, view it on GitHub
<#50719 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ARSUEW6ZNZRGZ6EOZIKVOCDXNGVALANCNFSM6AAAAAAZGRVMB4>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
First of all, I'm fine with this change. Although I think that it doesn't bring anything valuable, apart from some new syntax stolen from Svelte. And I'm afraid we observe a tendency of bringing in stuff from other frameworks into Angular, smoothing it's distinct style and making it a mash, without any clear benefits. Now, two questions:
|
Beta Was this translation helpful? Give feedback.
-
Overusing brackets for conditions like this creates HTML code that is super awkward and difficult to read. That's why I can't stand writing or reading JSX code, I'd hate for Angular to adopt this approach unless it solves this issue. I'd much rather have some sort of "#" pattern (or some other character) like:
Whether this is technically possible I haven't considered. The above code just feels more natural to write and is super clean. |
Beta Was this translation helpful? Give feedback.
-
Please do not introduce handlebars-like syntax in Angular templating syntax, please! Let's stick with the existing image of Angular 😞 |
Beta Was this translation helpful? Give feedback.
-
If the intent is to be as near as JavaScript as possible, why not considering using template strings: {
template: html`<div>${ this.value() }</div>`
} This IS pure JavaScript. I obviously reference lit with this. And performance wise, it's very efficient. |
Beta Was this translation helpful? Give feedback.
-
Authors: @alxhub @pkozlowski-opensource @jelbourn
Area: Angular Framework
Posted: June 14, 2023
Status: Open
This RFC proposes a new control flow syntax for Angular, and represents a significant change to how we approach control flow in the framework. There are two questions which should be answered up front: why and why now? Let’s start with why:
A core goal of Angular's initial design is that template control flow would be expressed via composition of user-facing concepts only. The structural directive concept serves this purpose: a directive that sits on an
<ng-template>
declaration and determines when & where embedded views are created from that template. To achieve a familiar syntax for control flow similar to that of JavaScript (e.g.let user of users
), microsyntax in the template language provides an alternative binding syntax for configuring a structural directive and its inputs.Our review of developer experience pain points in Angular has highlighted microsyntax-based control flow as having significant weaknesses compared to syntaxes in other frameworks. The proposed built-in control flow syntax addresses these issues and significantly improves the developer experience, in addition to being a foundation for new features.
As for why now, as a part of our ongoing work on implementing the Signals RFC, we’ve known that the existing zone-based control flow directives would not be able to function in zoneless applications. We will need to implement reactive control flow for zoneless applications. We considered modifying the existing control flow directives to support zoneless applications as well, but decided against this option for a couple reasons:
Since every existing Angular application depends on these directives, even the slightest incidental change in behavior could be breaking.
Supporting both signal and zone based patterns in a single directive would greatly complicate the code, and it’s unlikely that we could properly tree-shake either pattern if it wasn’t used.
We knew we wanted to make bigger improvements to control flow anyway.
We also considered implementing new reactive control flow directives just for signal components, but rejected this idea as we didn’t want to create more differences between signal and zone components than necessary.
As a result, we decided to move forward with this design of built-in control flow for templates, to both support the zoneless/signal work as well as address the longstanding DX issues with structural directives.
Goals and Non-Goals
The new syntax for template control flow has several major goals:
if
vsfor
vsswitch
).It is useful to explicitly mention some non-goals of the new syntax:
Overview
Many templating languages in the open source space have control flow primitives, and we’re fortunate to be able to draw on those ideas and designs from the larger web community. The proposed new control flow syntax is heavily inspired by Svelte’s control flow, as well as the
mustache
templating language.The new control flow syntax is introduced in the form of blocks, a new syntactic structure in templates.
Conditional control flow uses blocks with the keywords
if
andelse
:Switch control flow uses blocks with the keywords
switch
,case
, anddefault
:Angular’s
switch
does not have fallthrough (there is nobreak
operation required).Loop control flow uses blocks with the keywords
for
andempty
:Detailed Design
Syntax
The primary syntactic element of the new control flow design is a named block group composed of one or more named blocks. Block groups are template nodes, occupying the same syntactic positions as elements, text nodes, and text bindings. Each block in a block group contains Angular template syntax within it, as a list of child template nodes (potentially including additional block groups).
Each block group begins with a tag of the form
{#name}
and ends with a matching closing tag{/name}
. Tags can contain specific syntax based on their name. As an example, the basicif
control flow structure looks like: \Every block group also defines a named block with the same name as the block group, known as the primary block. The above example thus represents a block group (
if
) with a single primary block (if
), which has the<child-cmp>
element as a child.This syntax is, of course, a blocking feature.
Discussion Question 1A: should we consider other syntax besides curly braces for the block tags, or besides
#
,/
and:
? E.g.[if cond]
?Tag syntax
Each named block group can customize the tag syntax following the tag name and a space. For example, the
if
block group supports an expression following theif
, which is used as the if condition.Different block groups may use this syntactic space in different ways.
Additional named blocks
In addition to the primary named block created by the block group tag itself, additional blocks may be defined within a block group. Additional blocks are specified with a tag that starts with
:
instead of#
. Blocks (including the primary block) follow a set of syntactic rules:For example, an
if
block group may define an optionalelse
block:The above syntax creates an
if
block group with two blocks: the primaryif
block created by the block group (containing the<true-cmp>
) and anelse
block containing the<false-cmp>
.Like the primary block tag, the syntactic space following the name of an additional block is entirely customizable depending on the block group type.
Optional parentheses
In JavaScript, control flow statements require parentheses (
if (cond)
). Because block tags are wrapped in braces already, they don't require parentheses. However, we support including parentheses if desired:If used, the parentheses wrap the entire body of the block tag.
Discussion Question 2A: should parentheses be required to maximize similarity to JavaScript/TypeScript?
Nesting
Block groups are "nodes" in templates and can be children of blocks in other block groups. Control flow structures can therefore be nested.
Why
#
at all?There are two main reasons why we chose to use a hieroglyphic (
#
) instead of bare single-curly statements (e.g.{if cond.expr}
):{...}
is used today for the ICU Message format which supports several i18n features (eg: pluralization).if
block - conditionalsThe
if
block replaces*ngIf
for expressing conditional parts of the UI.The primary
if
block includes an expression representing the condition under which this block will be displayed.{#if a > b} {{a}} is greater than {{b}} {/if}
if
block groups may also contain one or moreelse
blocks. One bareelse
block is allowed, as well as zero or moreelse
blocks with an additional condition (indicated by anif
keyword following theelse
name:Aliasing the conditional expression
The current
*ngIf
supports aliasing of the condition expression viaas
: \To enable direct automatic migration from
*ngIf
to the new built in conditional statement, aliasing will be supported inif
:Aliasing only supported for zone components
Because signals are synchronous and side effect free, there is no harm in reading their value in multiple places in the template. In the case of complex conditions or conditions that involve expensive calculations,
computed
can be leveraged for memoization and de-repetition. For that reason, we feel there isn't a strong use case for aliasing the value of the condition expression in signal components (that isn't better served bycomputed
).Why not
cond as name
, similarly to microsyntax?A long-term goal for future evolution of Angular's template syntax is to make expressions "just TypeScript" (or at least a subset of TypeScript). Because
as
is used in TypeScript to represent typecasts, we want to allow for expressions including typecasts in the future.for
block - repeatersThe
for
block replaces*ngFor
for iteration, and has several differences compared to its structural directive predecessor. A basicfor
block group looks like:track
keys for diffingThe
track
setting replacesNgFor
's concept of atrackBy
function. It determines the row key whichfor
will use to associate array items with the views it creates, moving them around as needed. Becausefor
is built-in, we can provide a better experience than passing atrackBy
function, and use an expression representing the key instead.Migrating from
trackBy
totrack
is possible by invoking thetrackBy
function:The new syntax helps the _run_time by offering you a track and field to run around your loops.
$index
and other variablesWithin
for
row views, there are several implicit variables which are always available:$index
$first
$last
$even
$odd
These variables are always available with these names, but can be aliased via a
let
segment:Aliasing is useful when nesting
for
loops, since the inner loop's implicit variables shadow those from the outer loop.track
is requiredIn our performance research, we've identified
NgFor
loops over immutable data withouttrackBy
as one of the most common causes for performance issues across Angular applications. Because of the potential for poor performance, we’re makingtrack
required forfor
loops.There is one exception to this requirement: for
for
usages in signal components, when the iterable expression is assignable toIterable<Signal<unknown>>
, thentrack
is not required (and in fact, not allowed). That's because when individual rows use signals for reactivity, we want the outer loop to track those signals by their identity and not depend on their values. This allows individual rows to update without triggering diffing of the list.Cross-country is not required though.
Discussion Question 3A: are there technical objections to requiring
track
, or other ways to solve performance issues withNgFor
that we may not have considered?empty
blockNew to
for
is support for a template to render when there are no items in a list. This is a common community feature request.IterableDiffers
Unlike its structural directive predecessor, the new
for
will not use Angular’s customizable diffing implementation (IterableDiffers
), but instead will use a new optimized algorithm (which would not be customizable).Discussion Question 4A: do you have a use case for customizing the diffing algorithm by customizing
IterableDiffers
today?for
in signal componentsIn signal-based components,
for
is reactive. The iteration variable and all of the special variables created for each row are signals instead of plain values, and must be unwrapped via their getter. This allows each row created byfor
to change detect independently of the view containingfor
, and allowsfor
to work in zoneless applications.switch
block - selectionThe syntax for
switch
is very similar toif
, and is inspired by the JavaScriptswitch
statement:#switch
has several major benefits overNgSwitch
:Note that the primary block within the
switch
group is unused. It will be an error to have any child nodes inside that block.switch
does not have fallthrough (there is nobreak
operation required).Migration Considerations
It should be possible to write an automated migration schematic which converts from
NgIf
,NgForOf
, andNgSwitch
to their equivalents in the new syntax, with a few exceptions which this section considers:Observation of structural directive presence
Existing code may look for the presence of
NgIf
,NgForOf
, orNgSwitch
directives explicitly. This could take a few forms:ViewChild
queries to find control flow directives.<ng-template>
.Iterable Differs
Angular makes it possible to customize the diffing algorithm used by
NgForOf
via theIterableDiffer
interface and associated DI token.Theoretically an application could customize diffing in a way that would break if migrated to use
for
. This could result in strange behavior when the iterable changes or, in the worst case, a complete failure to render.Future Opportunities
This section captures potential future improvements which aren't in scope for this design, but could be achieved on top of this syntax in the future.
for..in
and other loop stylesNot in scope for this design is support for other iteration flavors in JS, such as
for..in
loops, async iteration, etc. Such additions would theoretically be possible under the current syntax. So for now, Angular will see these other loop styles as…for-in.Discussion Question 5A: are there other flavors of looping which would benefit your applications today?
Virtual scrolling
We could extend the syntax of
for
to support some kind of abstraction for a viewport and scrolling controller, that would enable virtual scrolling on top of the built in syntax.Destructuring
We could support binding patterns (destructuring) as JS loops do.
Alternatives Considered
HTML-based control flow
Instead of the mustache-inspired syntax
{#if …}
, we considered using HTML syntax such as tags prefixed with theng
namespace:This concept was rejected for several reasons:
For example, the condition of
<ng:if>
would likely be expressed as<ng:if cond="expr">
which looks less like JS control flow.An if-else block would take the form:
This has a lot of visual overhead compared to the proposed block language.
Continuing with Microsyntax
Another option considered and not taken was to continue using Angular microsyntax to express control flow in templates. Our compiler could detect usages of
*ngIf
,*ngFor
, and*ngSwitch
and generate special code for them. We could use this mechanism to circumvent some of the limitations of the directive-based implementations of control flow, while not introducing new syntax to learn.We rejected this option because improving the DX of control flow beyond the microsyntax form is one of the main goals of introducing built-in control flow. Having built in implementations of the original structural directives would also introduce magic which might confuse advanced users, as these directives would no longer behave according to the mental model for regular structural directives.
The microsyntax model also does not support multiple associated sub-templates for a control flow statement, so we would not solve the problem of
*ngIf
'selse
case being extremely awkward to use.Frequently Asked Questions
Q: Will the existing structural directives (
NgIf
, etc) continue to work?Ideally, it will be possible to replace most usages of the existing structural directives from
@angular/common
with the new syntax, and we will have an automatic migration to do so. Given that, we plan to strongly encourage developers to switch to the new control flow syntax to take advantage of the developer experience improvements.That said, we're also aware that all existing Angular content (blogs, books, videos, etc) will continue to reference the existing directives for a long time. Currently the plan is to deprecate the existing directives but not remove them until the ecosystem (including community-authored content) has switched over to the new control flow. Ultimately, we will base this timeline on the feedback we receive from the community.
In the rare cases where automatic migration fails (e.g. for
NgForOf
cases discussed above) we could apply similar techniques that we’ve used before in CDK/Material, such as generating a clone of the structural directive in the user’s project.Q: Will the structural directive concept itself be removed?
No, structural directives are an essential feature for application architecture in Angular, and we have no plans to remove them.
Q: Will the new control flow be syntax highlighted?
Yes, the Angular Language Service will highlight the keywords and expressions within the new control flow blocks.
Q: Will the new control flow affect query results at all?
Query results will work the same way as with the existing
ngIf
,ngFor
, andngSwitch
Q: Will the new control flow still define & create embedded views?
Yes, the technical internals of the new control flow will use the same concepts as the structural directives, including
<ng-template>
s and view containers.Q: Will I still need to import the new control flow into my components?
No, it will be built into the template language and automatically available to all components.
Q: Will the new control flow be as tree-shakable as the previous version?
Yes, if an application doesn’t use a particular block group (e.g.
switch
), its code will not be included in the production bundle.Q: Will the new control flow have better performance?
There may be some marginal improvements, especially for
for
and diffing. We expect larger memory usage improvements.We may also be able to implement compiler optimizations in the future that would not have been possible with the previous user-land control flow. For example, we know that
if
’s embedded view is only created once, which might allow us to optimize some data structures.Q: Why are we planning more changes instead of delivering on signals?
The existing control flow directives are based on zones, and so changes to control flow (particularly
for
) are required to support fully zoneless applications. Given that we need to change control flow anyway, we’re taking the opportunity to update the design and address other commonly reported DX pain points as well.Note the section on
for
exposing its loop variable as a signal in signal-based components above. This is required to properly notify each child row’s view when its data changes (and it must be change detected).Q: Can libraries define their own block groups?
For now, no – block groups will be a template language builtin, and not a user extension point. We are interested in researching whether it makes sense to expose some kind of extensibility here, what use cases there might be, and what form such a feature might take.
Q: Can I add directives to the new control flow blocks?
No. This is another area where we’d like to research potential use cases.
Q: What about the CDK’s virtual scrolling functionality?
The CDK will continue to provide its existing structural directives for virtual scrolling and other use cases for the time being. We will be researching whether it makes sense to either convert some of the CDK’s structural directives to built-in syntax, or to add extension points to the existing syntax for the CDK to build on (for example, to support virtual scrolling in
for
).Q: Is this syntax valid HTML?
It is valid HTML, in the sense that to an HTML parser block groups are indistinguishable from plain text nodes (like Angular text bindings). We did explore using HTML pseudo-elements for control flow instead but rejected that design for several reasons (see the Alternatives Considered section above for details).
Q: Will there be other built-in blocks in the future?
Maybe 😉 we're
#defer
ring this decision.Beta Was this translation helpful? Give feedback.
All reactions