samedi 26 février 2022

How to replace `@computed` with setter returning new value with new native setters?

Problem

I've often used this kind of computed properties where the setter simply returns the new value :

  @computed('args.myValue')
  get myValue() {
    return this.args.myValue;
  }
  set myValue(newValue) {
    return newValue; // <==== this is no longer valid with native setter
  }

This does few things :

  1. Set initial value to args.myValue
  2. Allow to change the value (typically through an <Input @value= />)
  3. Restore the default value when args.myValue changes

The problem comes with native setters which can't return any value.

Notice I could probably find a "hackish" solution but I'd like to have code that follows new EmberJS conventions in order to avoid painfull later updates.

Things I tried

Manual caching

  @tracked _myValue = null;

  get myValue() {
    return this._myValue || this.args.myValue;
  }
  set myValue(newValue) {
    this._myValue = newValue;
  }

This does not work because _myValue is always set after the first myValue=(newValue). In order to make it work, there should be some kind of observer which resets it to null on args.myValue change.

Sadly, observers are no longer part of EmberJS with native classes.

helper

<Input @value= />

As expected, it does not work because it just doesn't update myValue.

helper combined with event.target.value handling

<Input @value=  />
  get myValue() {
    return this.args.myValue;
  }

  @action keyPressed(event) {
    this.doStuffThatWillUpdateAtSomeTimeMyValue(event.target.value);
  }

But the Input is still not updated when the args.myValue changes.

Initial code

Here is a more concrete use example :

Component

// app/components/my-component.js

export default class MyComponent extends Component {

  @computed('args.projectName')
  get projectName() {
    return this.args.projectName;
  }
  set projectName(newValue) {
    return newValue; // <==== this is no longer valid with native setter
  }

  @action
  searchProjects() {
    /* event key stuff omitted */
    const query = this.projectName;
    this.args.queryProjects(query);
  }
}


<Input @value=  />

Controller

// app/controllers/index.js

export default class IndexController extends Controller {

  get entry() {
    return this.model.entry;
  }

  get entryProjectName() {
    return this.entry.get('project.name');
  }

  @tracked queriedProjects = null;

  @action queryProjects(query) {
    this.store.query('project', { filter: { query: query } })
      .then((projects) => this.queriedProjects = projects);
  }

  @action setEntryProject(project) {
    this.entry.project = project;
  }
}


<MyComponent 
  @projectName= 
  @searchProjects= />

When the queriedProjects are set in the controller, the component displays them.

When one of those search results is clicked, the controller updates the setEntryProject is called.




Aucun commentaire:

Enregistrer un commentaire