How to track getter change in other getter without computed - Ember js

Viewed 28

I need to track the getter is loaded or not in other getter without @computed property. How can I do?

@computed('current.locationId')
get currentApplications() {
  const query = {
    locationID:     get(this, 'current.locationId')
  };
  
  return this.store.pagedArray(this.store.query('shift-application', query));
}

get applicationsLoaded() {
  return this.currentApplications.isFulfilled
}

I need to track currentApplications.isFulfilled without using @computed('currentApplications.isFulfilled').

1 Answers

You'll have problems tracking the result of promises in getters (computed or not) -- there are a couple libraries to help out with this though, and I highly recommend using one of these, as doing this your self can get complicated and buggy.

The reason for this is that when dealing with network data, you have to model error state, loading state, etc.

ember-resources

GitHub - Docs

This library provides a new reactivity primitive to Ember that looking to become a default in the framework in the future.

In one of the examples, you can provide tracked-state throughout the lifetime of a remote request bi doing something like this:

import { resource } from 'ember-resources';
import { TrackedObject } from 'tracked-built-ins';

const load = resource(({ on }) => {
  let state = new TrackedObject({});
  let controller = new AbortController();

  on.cleanup(() => controller.abort());

  fetch(this.url, { signal: controller.signal })
    .then(response => response.json())
    .then(data => {
      state.value = data;
    })
    .catch(error => {
      state.error = error;
    });

  return state;
})

<template>
  {{#let (load) as |state|}}
    {{#if state.value}}
      ...
    {{else if state.error}}
      {{state.error}}
    {{/if}}
  {{/let}}
</template>

From this page: https://ember-resources.pages.dev/functions/index.resource-1

Additionally, there is a utility, featured on the README, and documented here

import { trackedFunction } from 'ember-resources/util/function';

class MyClass {
  @tracked endpoint = 'starships';

  data = trackedFunction(this, async () => {
    let response = await fetch(`https://swapi.dev/api/${this.endpoint}`);
    let json = await response.json();

    return json.results;
  }),

  get records() {
    return this.data.value ?? [];
  }
}
{{this.records}}

It provides these properties for use in your template: https://ember-resources.pages.dev/classes/util_function.State

RemoteData is an example of how you could build your own resource that takes args, if you wish: https://ember-resources.pages.dev/functions/util_remote_data.RemoteData

ember-data-resources

GitHub

Since it looks like you're using ember-data, you may be interested in this utility from ember-data-resources -- it provides many of the same intermediary states that you'll need to handle.

import { query } from 'ember-data-resources';

export default class MyComponent extends Component {
  blog = query(this, 'blog', () => ({ ...query }));
  // other examples
  // blog = query(this, 'blog', () => [{ ...query }]);
  // blog = query(this, 'blog', () => [{ ...query }, { ...options }]);
}
Available properties:
 - {{this.blog.records}}
 - {{this.blog.error}}
 - {{this.blog.isLoading}}
 - {{this.blog.isSuccess}}
 - {{this.blog.isError}}
 - {{this.blog.hasRun}}

Available methods:
 - <button {{on 'click' this.blog.retry}}>Retry</button>

This library builds on ember-resources.

ember-async-data

GitHub

This is very similar to a "resource" as resources are literally just helpers (but with some nicer ergonomics in JS for interop).

The README has some solid examples, and also a similar API

(This is an excerpt from the README)

Render a promise in a declarative way in template-only code:

{{#let (load @somePromise) as |result|}}
  {{#if result.isResolved}}
    <PresentTheData @data={{result.value}} />
  {{else if result.isPending}}
    <LoadingSpinner />
  {{else if result.isRejected}}
    <p>
      Whoops! Looks like something went wrong!
      {{result.error.message}}
    </p>
  {{/endif}}
{{/let}}

Create declarative data fetches based on arguments to a component in a backing class:

import Component from '@glimmer/component';
import { cached } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { TrackedAsyncData } from 'ember-async-data';

export default class SmartProfile extends Component {
  @service store;

  @cached
  get someData() {
    let recordPromise = this.store.findRecord('user', this.args.id);
    return new TrackedAsyncData(recordPromise);
  }
}

(See the guide below for why this uses @cached!)

{{#if this.someData.isResolved}}
  <PresentTheData @data={{this.someData.value}} />
{{else if this.someData.isPending}}
  <LoadingSpinner />
{{else if this.someData.isRejected}}
  <p>
    Whoops! Looks like something went wrong!
    {{this.someData.error.message}}
  </p>
{{/endif}}

Others and References

Related