NullVoxPopuli / ember-resources

An implementation of Resources. Supports ember 3.28+
https://github.com/NullVoxPopuli/ember-resources/blob/main/docs/docs/README.md
MIT License
91 stars 37 forks source link

Simple example not working #91

Closed MichalBryxi closed 2 years ago

MichalBryxi commented 2 years ago

I was hoping to use this library to remove weird patterns I've been using where I have a service to load data from the API and then provide them across the whole app. But I'm failing to figure out what to do.

import { LifecycleResource } from 'ember-resources';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class CompanyResource extends LifecycleResource {
  @service store;
  @tracked company;

  setup() {
    this.doAsyncTask();
  }

  update() {
    this.doAsyncTask();
  }

  async doAsyncTask() {
    this.company = (await this.store.findAll('company'))[0]; // weirdness of the API, don't ask
  }
}

And then use it in my service without the weird tricks:

import Service, { inject as service } from '@ember/service';
import { useResource } from 'ember-resources';
import CompanyResource from 'frontend/resources/company';

export default class SettingsService extends Service {
  @service store;

  companyResource = useResource(this, CompanyResource, () => []);

  get company() {
    return this.companyResource.company;
  }

  get foo() {
    return this.company.foo;
  }
}

And finally usage of the service:

import { inject as service } from '@ember/service';
import Controller from '@ember/controller';

export default class SomeController extends Controller {
  @service settings;
}
{{this.settings.foo}}

Which gives me:

helpers.js:114 Uncaught TypeError: Cannot read property 'foo' of undefined
MichalBryxi commented 2 years ago

Also the thing here is that I don't have (yet) a case where the data would need to be recalculated. i.e: It's not dependent on anything. Load once and provide everywhere kind of pattern.

NullVoxPopuli commented 2 years ago

companyResource.company won't have a value until after findAll resolves, so you'll need to provide a default value within your custom resource, maybe like:

import { LifecycleResource } from 'ember-resources';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class CompanyResource extends LifecycleResource {
  @service store;
  @tracked _company;

  get company() {
    return this._company ?? {};
  }

  setup() {
    this.doAsyncTask();
  }

  update() {
    this.doAsyncTask();
  }

  async doAsyncTask() {
    this._company = (await this.store.findAll('company'))[0]; // weirdness of the API, don't ask
  }
}

Also, if you're doing vanilla async functions manually, I'd recommend looking at the logic in the FunctionRunner resource to see what common reactivity pitfalls could be around the corner: https://github.com/NullVoxPopuli/ember-resources/blob/main/addon/-private/resources/function-runner.ts#L62

MichalBryxi commented 2 years ago

Hmm, does not really work for me. If I do:

export default class CompanyResource extends LifecycleResource {
  @service store;
  @tracked _company;

  get company() {
    console.log('company');
    return this._company?.firstObject ?? {};
  }

  setup() {
    this.doAsyncTask();
  }

  update() {
    this.doAsyncTask();
  }

  async doAsyncTask() {
    console.log('before');
    this._company = await this.store.findAll('company'); // weirdness of the API, don't ask
    console.log('after');
  }
}

I will get following order:

before
company
after

So the getter for the company is never re-kicked even though the value of @tracked _company was changed.

MichalBryxi commented 2 years ago

What do you mean by "Also, if you're doing vanilla async functions manually"? I just copied LifecycleResource from the README because that sounded like the thing I want. Do you have a suggestion that this code should use different pattern @NullVoxPopuli ?

NullVoxPopuli commented 2 years ago

I made some sample tests (which all pass on default ember-source) that kinda take your example: https://github.com/NullVoxPopuli/ember-resources/pull/97/files

I'm freely admitting the README needs some work 🤔 but I'm not sure what exactly it needs -- partially because I'm not sure how to organize the information

MichalBryxi commented 2 years ago

I went through a few hoops here, but I think the problem in my case was that the place that was using the resource was not consuming it correctly, because of a legacy code that had something like:

@computed('someNonsenseProp')
get companyName() {
   return this.resource.company.name;
}

And because the @computed was not pointing at respective prop, the result was not working as expected.