javascriptember.jsember-dataember-componentsember-octane

Getting properties of ember component from ember component-class


I have been trying to get the value of a property of a component from action from its class. My main goal is to relate two objects: the "auction" passed into my component (shown in the code below)

new.hbs

<AuctionForm 
    @auction={{@model.auction}}
    @products={{@model.products}}/>

with the selected product on a select tag inside the "AuctionForm" Component as so:

auction-form.hbs

<div class="row">
    <label for="product">Produto</label>
    <select name="product" onchange={{action 'selectProduct' value='target.value'}}>
        <option value="" selected='selected' disabled='disabled'>-------</option>
        {{#each @products as |product|}}
            <option value="{{product.id}}" selected={{if (equalstr product.id @auction.product.id) 'selected'}}>{{product.name}}</option>
        {{/each}}
    </select>
</div>

and I want to bind these two objects on the "selectProduct" action of the class:

auction-form.js

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


export default class AuctionFormComponent extends Component {
    @action selectProduct(product) {
        this.get('auction').set('product', product); // this doesn't work on ember newest version
    }
    @action save(auction) {
        auction.save();
    }
}

Although, whenever I try to access its value on the component-class via "this.get()" function (just as I used to do on previous ember versions) I get an error saying that "this.get is not a function".

After digging a lot on the web I couldn't find a straightforward solution neither the official solution provided by the documentation.

The closest I got to a solution was to declare "auction" as a computed property (https://api.emberjs.com/ember/3.22/classes/Component). But, I could not manage to implement it on a javascript file, once its structure (like the code below) only works on TypeScript files.

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


export default class AuctionFormComponent extends Component {
    auction: computed('auction', function() {
        return this.auction;
    }
    @action selectProduct(product) {
        debugger;
        this.get('auction').set('product', product);
    }
    @action save(auction) {
        auction.save();
    }
}

Does anyone knows the correct way of performing such a task in ember 3.22?

Thank you very much.


Solution

  • Since you are using the modern Glimmer components (imported from @glimmer/component),

    1. The arguments have to be accessed via the args property inside the js class, like, this.args.auction.

    2. The arguments (auction and products) are not mutable inside the component. To change the value of an argument, we can send an action to the parent to change the value.

    3. The get and set methods are not available in glimmer components. Those methods are part of the classic ember component. You can access the properties of a class just using the dot [.] notation like: this.auction and reassign values using the assignment statement like this.property = 'value'

    4. Since you are using native class syntax, action: computed(..) is not a valid declaration.

    5. To bind events, it's recommended to use on modifier and fn helper

    By combining all the points,

    import Component from '@glimmer/component';
    import { action } from '@ember/object';
    
    export default class AuctionFormComponent extends Component {
        @action 
        selectProduct(event) {
            // This `updateProduct` has to be implemented in parent class 
            // which mutates the `auction` object.
            this.args.updateProduct(event.target.value); // -> `on` modifier will capture the native event
        }
    
        @action 
        save(auction) {
            auction.save();
        }
    }
    
    <div class="row">
        <label for="product">Produto</label>
        <select name="product" {{on "change" this.selectProduct}}>
            <option value="" selected='selected' disabled='disabled'>-------</option>
            {{#each @products as |product|}}
                <option value="{{product.id}}" selected={{if (equalstr product.id @auction.product.id) 'selected'}}>{{product.name}}</option>
            {{/each}}
        </select>
    </div>
    

    and the invocation would be like,

    <AuctionForm 
        @auction={{@model.auction}}
        @products={{@model.products}}
        @updateProduct={{this.updateProduct}}
    />
    

    Here, updateProduct needs to be implemented properly to update the product.


    EDIT: As @BPorto mentioned in a comment, this Ember octane migration cheat sheet will be handy while migrating the codebase from classic ember model to Octane model.