2

How can I insert a function call via *ngFor, while this call comes as an inserted String from the Array I'm iterating over?

My array contains a list of user actions that are described by their function names (among others). The template has a part that shall list these actions for each user entry, using Ionic's ion-fab directive. Instead of writing down each action I want to iterate over the list using *ngFor and insert each function name into a (click) attribute.
My current solution doesn't work, though.

Here's my code:

Class

constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    private usersProv: Users
) {
    this.userActions = [
        {
            'label'     : 'Edit',
            'function'  : 'editUser',
            'icon'      : 'ios-create-outline',
            'color'     : 'primary'
        },
        {
            'label'     : 'Remove',
            'function'  : 'removeUser',
            'icon'      : 'ios-trash-outline',
            'color'     : 'yellow'
        },
        {
            'label'     : 'Send message',
            'function'  : 'sendMessageToUser',
            'icon'      : 'ios-send-outline',
            'color'     : 'secondary'
        }
    ];
    console.info('userActions', this.userActions);
}

Template

<ion-fab right>
    <button ion-fab mini color="light">
        <ion-icon name="ios-arrow-dropleft"></ion-icon>
    </button>
    <ion-fab-list side="left">
        <div *ngFor="let action of userActions">
            <button ion-fab mini color="{{action.color}}" title="{{action.label}}" (click)="action.function(user)">
                <ion-icon name="{{action.icon}}"></ion-icon>
            </button>
        </div>
    </ion-fab-list>
</ion-fab>

And this is the error I get:

ERROR TypeError: "_v.context.$implicit.function is not a function"

Inserting with curly brackets ((click)="{{action.function}}(user)") doesn't help, either. The error then is:

`ERROR Error: "Uncaught (in promise): Error: Template parse errors: Parser Error: Got interpolation ({{}}) where expression was expected at column 0 in [{{action.function}}(user)]`

Is this possible at all?
And is it recommendable to approach it the way I'm doing?

Thanks in advance!

3
  • 2
    Instead of a string, hold a reference to the function, so 'function' : this.editUser for example. Note that there are no parentheses, as we want a reference to the function, not to invoke it Commented Sep 29, 2018 at 17:47
  • 2
    As @user184994 says want a function, not a string. You cannot call a string, as in "f"(1), in JavaScript or in an Angular template. Commented Sep 29, 2018 at 17:54
  • Why don't you create a method like executeAction(action:string, user: any): void and inside call the right method using switch(action){ case 'editUser': this.editUser(user); break; ... }? Commented Sep 29, 2018 at 17:59

2 Answers 2

4

Instead of using a string, you should hold a reference to the function, like so

userActions = [
    {
        'label'     : 'Edit',
        'function'  : this.editUser.bind(this),
        'icon'      : 'ios-create-outline',
        'color'     : 'primary'
    },
    {
        'label'     : 'Remove',
        'function'  : this.removeUser.bind(this),
        'icon'      : 'ios-trash-outline',
        'color'     : 'yellow'
    },
    {
        'label'     : 'Send message',
        'function'  : this.sendMessageToUser.bind(this),
        'icon'      : 'ios-send-outline',
        'color'     : 'secondary'
    }
];

Here I'm using bind, so we don't lose the context of this when we invoke the function.

Then, in your HTML, you can call it like so:

<button (click)="a.function(user)">{{a.label}}</button>

Here is a StackBlitz demo

Sign up to request clarification or add additional context in comments.

Comments

1

I recommend to use @user184994's solution, bind this context and use a reference, but if you still wanting to use strings, you can get access to component's methods via this['methodName'](parameter), in my example it looks like this[a.function](user).

Here is a STACKBLITZ.

6 Comments

Just watch out for minification / uglification in the build, as the function name may be changed and this[a.function] will be undefined
@user184994, are you sure? a.function is an object property, will it be minifyed only in component, and in template it will not? I always thought, that object properties are untouchable in default webpack build for angular.
So a.function will still be set to methodName, but the method itself has been uglified, and is now called x to save bytes, therefore this[a.function] is looking for this.methodName, which no longer exists
@user184994, by the way, we use the following structure in current project and it works fine with both dev and prod builds for a long time, so looks like the uglyfication is safe, we haven't got any errors with this part
Ah okay, that's worth knowing. I thought that the build process uglified the TS class function names as part of the build, but I've just checked and as you say its fine
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.