8000 Support arrow functions in template syntax · Issue #14129 · angular/angular · GitHub
[go: up one dir, main page]

Skip to content

Support arrow functions in template syntax #14129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jelbourn opened this issue Jan 27, 2017 · 29 comments
Open

Support arrow functions in template syntax #14129

jelbourn opened this issue Jan 27, 2017 · 29 comments
Labels
area: core Issues related to the framework runtime core: binding & interpolation Issue related to property/attribute binding or text interpolation feature Issue that requests a new feature hotlist: components team Related to Angular CDK or Angular Material
Milestone

Comments

@jelbourn
Copy link
Member
jelbourn commented Jan 27, 2017

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

@jelbourn jelbourn added area: core Issues related to the framework runtime hotlist: angular-core-team hotlist: components team Related to Angular CDK or Angular Material feature Issue that requests a new feature labels Jan 27, 2017
@vicb
Copy link
Contributor
vicb commented Jan 27, 2017

Thought: we could support x => {name: x} (without required "()") as we won't support fn body.

@gkalpak
Copy link
Member
gkalpak commented Jan 27, 2017

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()"

@jelbourn
Copy link
Member Author

@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.

@toverux
Copy link
toverux commented Oct 28, 2017

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.

@matheo
Copy link
matheo commented Dec 8, 2017

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 :(

@mtraynham
Copy link

@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.

@antontrafimovich
Copy link

Is there any update?

@frankiDotNet
Copy link
frankiDotNet commented Jan 29, 2019

Is there any way to make a workaround for this? Something like a Pipe that supports multiple parameters and returns a function?

@Guatom
Copy link
Guatom commented Feb 26, 2019

@Franki1986: as far as I could tell, there are only two ways to get this:

  1. Going for @jelbourn syntax. The function in the component has to be declared like equals = (a: Country, b: Country) => and not function equals(a: Country, b: Country) { or you'll lose this.
  2. Declare the function normally and then inject this with bind, just like @mtraynham said. But this feels so 2014...

@gotwig
Copy lin 8000 k
gotwig commented Mar 7, 2019

Pretty please 👍 🦄 especialy the displayWith usecase sucks 🤢 Maybe Ivy can handle this?

@mjolk
Copy link
mjolk commented Apr 8, 2019

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)

@EliezerB123
Copy link

@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.

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?

@toverux
Copy link
toverux commented Jul 28, 2019

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);
}

@jotiheranbnach
Copy link
jotiheranbnach commented Jan 23, 2020

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.

@kesarion
Copy link

@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.

@pkozlowski-opensource pkozlowski-opensource added the core: binding & interpolation Issue related to property/attribute binding or text interpolation label Mar 11, 2020
@evictor
Copy link
evictor commented Jan 13, 2021

+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.

@haskelcurry
Copy link

Any updates on this?

@PeterChen1997
Copy link

Any updates on this?

@jelbourn
Copy link
Member Author

If there are any updates you'll see them here or on the team's roadmap.

@MetroMarv
Copy link

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)">

@LeonardoX77
Copy link
LeonardoX77 commented Jul 30, 2021

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!

@Lonli-Lokli
Copy link

@LeonardoX77 Angular is not react. Don't write JS in html.

@haskelcurry
Copy link
haskelcurry commented Jul 30, 2021

@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".

@petebacondarwin
Copy link
Contributor

This is being tracked in the aggregate issue of #43485 for which we need to create a project proposal.

@jjamid
Copy link
jjamid commented Aug 8, 2023

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.

@LeonardoX77
Copy link
LeonardoX77 commented Aug 9, 2023

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

@BestOfTheBestSir
Copy link

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.

@jjamid
Copy link
jjamid commented Aug 9, 2023

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.

@LeonardoX77
Copy link
LeonardoX77 commented Aug 9, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: core Issues related to the framework runtime core: binding & interpolation Issue related to property/attribute binding or text interpolation feature Issue that requests a new feature hotlist: components team Related to Angular CDK or Angular Material
Projects
None yet
Development

Successfully merging a pull request may close this issue.

0