Is it a good way to call API calls from component classes in emberjs?

Viewed 126

I am learning emberjs right now and i was wondering if it is a common way to make fetch request inside a function (for example for button clicks) in component classes and store the data in the class component. For example:

export default class ExampleComponent extends Component {
  @tracked items;
  @action async toggleCall() {
    await fetch(...)
    .then(...)
  }
}

Or do you do this in a different way in emberjs?

1 Answers

Yes, that's a perfectly reasonably approach.

however, you need to somehow manually call toggleCall somehow.

My favorite way to load things or do anything async in a component, is to do so lazily and reactively via derived-data using "Resources".

There is a utility called trackedFunction from: https://github.com/nullvoxpopuli/ember-resources Specific docs here

Your example could be re-written as:

import Component from '@glimmer/component';
import { trackedFunction } from 'ember-resources';

export default class Example extends Component {
  fetchItems = trackedFunction(this, async () => {
    let response = await fetch( /* ... */);
    return await response.json();
  });

  get items() {
    // specify a default value of empty array when value is undefined
    // (value is undefined before the function finishes running)
    return this.fetchItems.value ?? [];
  }
}

Key differences:

  • the trackedFunction will automatically be invoked upon access
  • repeat-accesses will not re-invoke the function
  • any @tracked data used before the await in the trackedFunction will "entangle" so that changes to that @tracked data will cause the function to run again, for example, if you had a query parameter
  @tracked filter = 'name:starts_with=a';

  fetchItems = trackedFunction(this, async () => {
    let url = `...?${this.filter}`;
    let response = await fetch(url /* ... */);
    return await response.json();
  });

fetchItems is reactive, in that now when filter changes, fetchItems will re-invoke itself.


However, if you plan on keeping the request-on-click behavior (which is common!), you may be interested in ember-concurrency, which provides some nice ergonomic utilities for handling / protecting your data from the sillyness that use human-computer-interaction -- such as not re-requesting after a user has clicked a button, and clicked that button again before the request finishes.

Your example would then be:

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

export default class Example extends Component {
  @dropTask
  *fetchItems() {
    let response = await fetch( /* ... */);
    return await response.json();
  }

  get items() {
    return this.fetchItems.lastSuccessful.value ?? [];
  }

  // click handler can be called any number of times,
  // which would, in turn, call fetchItems any number of times,
  // but only one `fetch` request will ever run at a given time.
  @action
  handleClick() {
    this.fetchItems.perform();
  }
}

Docs on ember-concurrency here: https://ember-concurrency.com/

Related