Support arrow functions in template syntax
It's not uncommon for a component to take as input a transformation function or predicate function.
For example, this PR for @angular/forms adds an @Input for a predicate function for determining if two options in a <select> are equal. Right now, this looks something like:
<select [compareWith]="equals" [(ngModel)]="selectedCountries"> ...
equals = (a: Country, b: Country) => {
return a.id === b.id;
};
With this feature, this could be written as
<select [compareWith]="(a, b) => a.id == b.id" [(ngModel)]="selectedCountries"> ...
There are other places in Angular Material where we're looking to add similar API, e.g., datepicker with something like:
<md-datepicker [allowedDateFilter]="d => isWeekend(d)">
Or autocomplete, where the component needs to take an arbitrary value and know what string to write into the text input:
<md-autocomplete [displayWith]="value => value.getFullName()">
This would have to account for:
- Functions with multiple arguments (
(a, b) => a + b) - Functions with param names that already exist in the context (
<my-x #x [uniq]="x => x.id">) - Functions that return an object literal (
x => ({name: x})) - Invoking functions on the context (
x => isActive(x) - Passing functions through a pipe (
x => x.activate() | debounce)
FYI @mhevery for planning
Thought: we could support x => {name: x} (without required "()") as we won't support fn body.
In AngularJS, we used to recommend against adding too much logic in the templates. For example:
[...] The reason behind this is core to the AngularJS philosophy that application logic should be in controllers, not the views. If you need a real conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead.
Is this less of a concern in Angular now?
I know this is meant for good and being able to (a, b) => a + b or x => x.someMethod() can come very handy (and doesn't include too much logic), but I am afraid people will end up doing things more like:
[displayWith]="x =>
haveASideEffect(x) &&
callMyApi({lastValue: x}) &&
doSomethingElse() &&
x.getFullName()"
@gkalpak My opinion on this one is that while we do want to discourage complex logic in the template, ultimately the template syntax exists in the first place to be a convenience and improve developer productivity. This feature makes it simple and clean to capture a somewhat common use-case, especially for functional-style programming, which is worth the fact that some people may go overboard with it.
Found the issue because I needed it to have thunks in my templates.
<comp *structural="() => some.undefinedValueAtViewInit"></comp>
So I think there are valid use cases (mine may seem strange but I can assure you that it makes sense with the thing I'm working on). But I agree with @gkalpak about potential abuses. Personally, I've not been surprised when the JiT compiler blown up when I tried to use an arrow function.
But still, that would be a nice and neat feature to have for responsible developers.
As a related issue, in Material 2 Autocompleter, the displayWith function is not binded to the Component, and I was not able to access the internal variables that I require to figure out the result.
I figured out a workaround that worked, to keep my logic in the Component:
displayFn() {
return (value: StateOption): string => {
return !value ? '' : value.viewValue[this.lang];
};
}
then "inject" it into the template:
<mat-autocomplete [displayWith]="displayFn()" #states="matAutocomplete">
Without wrap the arrow function, this.lang is undefined :(
@matheo you can use .bind(this):
component.ts
displayFn(value: StateOption): string {
return !value ? '' : value.viewValue[this.lang];
}
component.html
<mat-autocomplete [displayWith]="displayFn.bind(this)" #states="matAutocomplete">
this is in reference to the component instance.
Still would like to see this inlined arrow feature though.
Is there any update?
Is there any way to make a workaround for this? Something like a Pipe that supports multiple parameters and returns a function?
@Franki1986: as far as I could tell, there are only two ways to get this:
- Going for @jelbourn syntax. The function in the component has to be declared like
equals = (a: Country, b: Country) =>and notfunction equals(a: Country, b: Country) {or you'll losethis. - Declare the function normally and then inject
thiswithbind, just like @mtraynham said. But this feels so 2014...
Pretty please 👍 🦄 especialy the displayWith usecase sucks 🤢 Maybe Ivy can handle this?
with myfn.bind(this) and multiple emitters (like in a list) you run into trouble, best to use myfn = () => {} to retain a valid reference to this (instead of SafeSubscriber)
@matheo you can use
.bind(this):component.ts
displayFn(value: StateOption): string { return !value ? '' : value.viewValue[this.lang]; }component.html
<mat-autocomplete [displayWith]="displayFn.bind(this)" #states="matAutocomplete">
thisis in reference to the component instance.Still would like to see this inlined arrow feature though.
It works perfectly well enough, but my Visual Studio Code error checking reports "Unknown Method 'bind' " :)
Is it possible to do this kind of .bind() in the *.ts file, instead?
Is it possible to do this kind of .bind() in the *.ts file, instead?
Of course, just do it in the constructor. This will have no impact on type-checking. As bind creates a new function, you also need to reassign it:
public constructor() {
this.displayFn = this.displayFn.bind(this);
}
Ran into the need of an arrow function for a Component Input just now and found this issue. I simply wanted to inject some behaviour in another component. A sytax like this would be nice:
<module-title [helpAction]="() => helpModal.open()"></module-title>
Edit: Yes, yes, I know. I could do this:
<module-title [helpAction]="getOpenHelpModalCallback()"></module-title>
. . .
export class ModuleTitleComponent {
. . .
getOpenHelpModalCallback(): () => {} {
return () => this.helpModal.open();
}
}
But, I find it bothersome.
@jotiheranbnach Yeah, it's just extra, redundant code. That's a major reason to have this feature. I like to put simple stuff like this in the template, both to get rid of unnecessary code (keeping it DRY and KISS) and because it's easier to read, you don't have to go to keep going back to the component to see what each function is really doing.
+1 for usage with matAutocomplete.[displayWith]. This seems very common to be working with objects in memory, but wanting to pluck an attribute for template interpolation.
Any updates on this?
Any updates on this?
If there are any updates you'll see them here or on the team's roadmap.
Maybe one more use case that I stumbled upon today:
When work with observables and the async pipe, I want to do something like this to avoid subscribing in the Component code:
<ng-container *ngIf="(availableAppointments$ | async) as availableAppointments">
<ngb-datepicker [markDisabled]="(event) => isDateDisabledCallback(availableAppointments, event)">
Hi! what about using arrays??? I have this issue
I have to translate this
<label *ngIf="votingElection.userResults.some(r => r.votingOptionId === vOption.id)">Votes:</label>
<table class="table table-sm">
<tr *ngFor="let vResult of votingElection.userResults">
<td> {{ votingElection.userList.find(u => u.userId === vResult.UserId)?.name }} </td>
</tr>
</table>
Into this:
<label *ngIf="anyResult(vOption.id)">Votes:</label>
<table class="table table-sm">
<tr *ngFor="let vResult of votingElection.userResults">
<td> {{ getUserName(vResult) }} </td>
</tr>
</table>
I think this is absolutely necessary, don't you?!! :) cheers!
@LeonardoX77 Angular is not react. Don't write JS in html.
@Lonli-Lokli How's that example is a React? It's not JSX.
It's just expressing yourself in declarative manner, why in a world do I need to create e.g. displayFn method on the component class each time? Or the method as simple as u => u.userId === vResult.UserId ?
I need to open the class each time and find the according methods, it's a waste of time and waste of readability.
I totally agree with you @LeonardoX77 , this is what I would expect from the framework that has the idea of "html templates".
This is being tracked in the aggregate issue of #43485 for which we need to create a project proposal.
Hi! what about using arrays??? I have this issue
I have to translate this
<label *ngIf="votingElection.userResults.some(r => r.votingOptionId === vOption.id)">Votes:</label> <table class="table table-sm"> <tr *ngFor="let vResult of votingElection.userResults"> <td> {{ votingElection.userList.find(u => u.userId === vResult.UserId)?.name }} </td> </tr> </table>Into this:
<label *ngIf="anyResult(vOption.id)">Votes:</label> <table class="table table-sm"> <tr *ngFor="let vResult of votingElection.userResults"> <td> {{ getUserName(vResult) }} </td> </tr> </table>I think this is absolutely necessary, don't you?!! :) cheers!
Nope.
This code is wrong either way. Not only that it takes the first item it finds and ignores the rest (hidden try/catch, random behavior), but it also misuses the "?" operator (which is pure evil and accounts for thousands of bugs, seems like all the develoeprs use it wrong)
The check should have been on the td element and not as late as possible.
This operator should be cancelled.
Well @jjamid, the ? operator used in my example is just to avoid errors but we can decide not to use it if we are sure the find() method will always return a value, that check can be made prior execution of my example, that's not the point of this thread and it's absolutely irrelevant.
The real point of this is to allow support for arrow functions in html templates, which I'm not the only one who thinks it won't break html template philosophy and it will make definitely life easier. if my example is wrong or not, it doesn't matter, it's only illustrating the problem and it could be even illustrated with pseudo-code. Don't focus on what my code is doing and focus on what you could do instead if the arrow feature would be supported in html templates.
Thanks
Regards
I really hope it will never be implemented. It will lead to such an awful code which I usually see just in the JS portion, but it will also lead to performance degradation, which will be visible as a framework issue, while it will be an issue of a developer.
Well @jjamid, the
?operator used in my example is just to avoid errors but we can decide not to use it if we are sure the find() method will always return a value, that check can be made prior execution of my example, that's not the point of this thread and it's absolutely irrelevant. The real point of this is to allow support for arrow functions in html templates, which I'm not the only one who thinks it won't break html template philosophy and it will make definitely life easier. if my example is wrong or not, it doesn't matter, it's only illustrating the problem and it could be even illustrated with pseudo-code. Don't focus on what my code is doing and focus on what you could do instead if the arrow feature would be supported in html templates. Thanks Regards
I don't agree. Commenting on problematic code is always relvant, even if it's not related, since it's public. You cannot "avoid errors"! You're just moving the problem to the next much harder to troubleshoot step. The "?" is responsible personally for thousands of bugs that I've seen in the developers' code (and it usually makes the code extremly hard to understand when used wrong), it's because it's the exact opposite of the Guard Clause idea.
Anyway, I think too that there should be arrow support in the angular html. And not only that, also access to Enums without workarounds and basically to be excatly like .NET's amazing razor files and Web Forms before that. Eventually the html will inherit the component, it's just a matter of time. They now allow accessing protected memebers in the html.
Everything can be abused (like the "?" operator) and it's not a reason to not implement it.
Anyway, I think too that there should be arrow support in the angular html. And not only that, also access to Enums without workarounds (...)
welcome to the pros list and don't worry about my awful code :), probably you will never have to deal with it.
I'd like to make some arguments for this:
- Angular templates are not currently valid HTML anyway. Structural directives, string interpolation, property and event bindings all violate HTML specs (and that's fine).
- you can reduce mental overhead for developers by not having to rely on a mapping function in the typescript code (which should really be ignorant of these rendering details anyway).
- you lower the learning curve for developers coming from other frameworks, particularly React.
- you can deprecate
ng-templateandngTemplateOutletwhich are a bit obscure to most devs, and have unresolved issues around type-safety. - you could still impose restrictions on arrow functions that limit the potential abuse expressed in this thread, for example they should always return HTML, should not allow side-effects, and perhaps some other restrictions too.
Syntax proposal:
<my-select
[renderOption]="data => ( <- Note that anything inside '(' and ')' can be parsed (as if its top-level HTML)
@let name = data.name; <- local variables are legal (as if its top-level HTML)
@return <div>{{name}}</div> <- new @return variable operator
)"
/>