简体   繁体   中英

How are Ember Octane Glimmer component @actions called?

This question is related to Ember Octane Upgrade How to pass values from component to controller

I was struggling to receive and assign values from an HBS form into a component and then pass it to the controller. The working answer showed that I had to create an @action function for each form field. For example:

@action
changeNewPassword(ev) {
    this.newPassword = ev.target.value;
}

But, I do not understand where or how those functions are called and so I do not understand why they work. Does anyone know how these functions are called?

Template Component HBS

<div class="middle-box text-center loginscreen animated fadeInDown">
    <div>
        <h3>Change Password</h3>
        <form class="m-t" role="form" {{on "submit" this.changePassword}}>
            {{#each this.errors as |error|}}
                <div class="error-alert">{{error.detail}}</div>
            {{/each}}
            <div class="form-group">
                <Input @type="password" class="form-control" placeholder="Old Password" @value={{this.oldPassword}} required="true" />
            </div>
            <div class="form-group">
                <Input @type="password" class="form-control" placeholder="New Password" @value={{this.newPassword}} required="true" />
            </div>
            <div class="form-group">
                <Input @type="password" class="form-control" placeholder="Confirm Password" @value={{this.confirmPassword}} required="true" />
            </div>
            <div>
                <button type="submit" class="btn btn-primary block full-width m-b">Submit</button>
            </div>
        </form>
    </div>
</div>

Template HBS

<Clients::ChangePasswordForm @chgpwd={{this.model}} @changePassword={{action 'changePassword'}} @errors={{this.errors}} />

Template Component JS

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class ChangePasswordForm extends Component {

    @tracked oldPassword;
    @tracked newPassword;
    @tracked confirmPassword;
    @tracked errors = [];

    @action
    changeOldPassword(ev) {
        this.oldPassword = ev.target.value;
    }
    @action
    changeNewPassword(ev) {
        this.newPassword = ev.target.value;
    }
    @action
    changeConfirmPassword(ev) {
        this.confirmPassword = ev.target.value;
    }

    @action
    changePassword(ev) {

        ev.preventDefault();

        this.args.changePassword({
            oldPassword: this.oldPassword,
            newPassword: this.newPassword,
            confirmPassword: this.confirmPassword
        });
    }
}

In Ember Octane, you want to use the on modifier for setting up actions.

The line

<form class="m-t" role="form" {{on "submit" this.changePassword}}>

effectively sets up an event listener for the submit event of this form element which will invoke the changePassword function on the component's class (because the this in this.changePassword means that the function is local to the component)

That invokes this action:

@action
changePassword(ev) {

  ev.preventDefault();

  this.args.changePassword({
    oldPassword: this.oldPassword,
    newPassword: this.newPassword,
    confirmPassword: this.confirmPassword
  });
}

This changePassword action is in turn invoking the changePassword function that was passed to the component under the named argument @changePassword

<Clients::ChangePasswordForm @chgpwd={{this.model}} @changePassword={{action 'changePassword'}} @errors={{this.errors}} />

Now, in your Template Component JS you have three other actions

  1. changeOldPassword
  2. changeNewPassword
  3. changeConfirmPassword

which, as far as I can tell from the code you posted, are never used. They look like code you would use to set up a 1-way bound input, but you are using the built-in Input which is the Ember input built-in component (and uses two-way binding between the input value and the @value ). The very important distinction to note is the capital I on Input . All angle bracket components use title-casing (each separate word starts with a capital letter).

Had you instead done something like:

<input type="password" class="form-control" placeholder="New Password" value={{this.newPassword}} {{on 'input' this.changeNewPassword}} required="true">

Then you would have bound the this.changeNewPassword function to the input event of the <input> element (which is the native html <input> . With the changeNewPassword action as you've defined:

@action
changeNewPassword(ev) {
  this.newPassword = ev.target.value;
}

You would have kept the this.newPassword value in sync with the input via one way binding.

There are two ways I see you are using actions in your examples.

  1. Via {{on}} :
<form class="m-t" role="form" {{on "submit" this.changePassword}}>

This case is more straightforward. When you do this in a component template, you are referring to the component's class, so, this.changePassword is called by the template when the submit DOM event happens in the form element.

You can see more information in the {{on}} API docs .

  1. Via {{action}}
<Clients::ChangePasswordForm @chgpwd={{this.model}} @changePassword={{action 'changePassword'}} @errors={{this.errors}} />

In this case, whenever @changePassword is triggered inside Clients::ChangePasswordForm , Ember will search for a changePassword either in the actions hash (classic syntax) or a decorated method ( @action ) in the class of the component that is using Clients::ChangePasswordForm .

You can see more information in the {{action}} API docs .

Hope this helps clarify the action mechanisms.

For extra homework, you might want to check the upgrade guides on actions .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM