Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to use ember-infinity with ember-concurrency? #314

Open
pkimber opened this issue Aug 9, 2018 · 15 comments
Open

Is it possible to use ember-infinity with ember-concurrency? #314

pkimber opened this issue Aug 9, 2018 · 15 comments

Comments

@pkimber
Copy link

pkimber commented Aug 9, 2018

Is it possible to use ember-infinity with ember-concurrency (I am learning ember and javascript)?

I have the following code in my route:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { isBlank } from '@ember/utils';
import { task, timeout } from 'ember-concurrency';

const DEBOUNCE_MS = 250;

export default Route.extend(AuthenticatedRouteMixin, {
  infinity: service(),

  actions: {
    doNothing() {
      return false;
    }
  },
  contactTask: task(function *(params) {
    if (isBlank(params)) { return []; }
    yield timeout(DEBOUNCE_MS);
    let contact = yield this.infinity.model('contact', params);
    return contact;
  }).restartable(),
  model(params) {
    /*
     * Copied from:
     * Replacing your Route models with Ember Concurrency Tasks:
     * https://medium.com/@AveryBloom/async-or-swim-replacing-your-route-models-with-ember-concurrency-tasks-5a230252893a
     * and:
     * Debounced Type-Ahead Search:
     * http://ember-concurrency.com/docs/examples/autocomplete
     */
    return {
      contact: this.get('contactTask').perform(params)
    };
  },
  queryParams: {
    criteria: {
      refreshModel: true
    }
  }

And this in my hbs file:

        {{#if model.contact.isRunning}}
            <tr valign="top">
              <td colspan="3">
                Loading contact list...
              </td>
            </tr>
        {{else}}
          {{#each model.contact.value as |contact|}}
            <tr valign="top">
              <td>
                {{#link-to "contact-detail" contact.id}}
                  {{contact.companyName}}
                {{/link-to}}
              </td>
              <td>
                {{#link-to "contact-detail" contact.id}}
                  {{contact.title}}
                  {{contact.firstName}}
                  {{contact.lastName}}
                {{/link-to}}
              </td>
              <td>
                {{#each contact.addresses as |address|}}
                  {{contact-address-in-a-table-cell address=address}}
                {{/each}}
              </td>
            </tr>
          {{/each}}
          <tr>
            <td colspan="3">
              {{infinity-loader
                infinityModel=model.contact
                loadingText='Loading records...'
                loadedText='All records loaded.'}}
            </td>
          </tr>
        {{/if}}

It is nearly working, but the infinity-loader never finishes loading and every request to the server is duplicated.

Any thoughts would be appreciated. Thank you.

@snewcomer
Copy link
Collaborator

snewcomer commented Aug 11, 2018

@pkimber Really interesting stuff! I like what you are doing here but I think there could be a bit simpler path. A couple of questions just to help me understand better...

  1. What version of ember-infinity? 1.1.2?
  2. Is there a reason for the yield timeout(...? I would imagine you would want to paint the screen as fast as possible.
  3. There is one path you could try...

a. Move the fetching logic into a component. So no model hook for your route nor controller. What I put here isn't perfect and may/may not work, but should give you an idea of what is possible

// top level component
export default Component.extend({
  tagName: '',

  infinity: service(),

  init() {
    this._super(...arguments);
    this.data = [];
  },
  
  // this fetches contacts again when the query changes
  didReceiveAttrs() {
    let query = get(this, 'query');
    get(this, 'fetchContacts').perform(query);
  },
  
  fetchContacts: task(function *(params) {
    if (isBlank(params)) { return []; }
    yield timeout(DEBOUNCE_MS);
    let infinityModel = yield this.infinity.model('contact', params);
    return set(this, 'data', infinityModel);
  }).restartable(),
});
{{yield (hash
    isRunning=fetchContacts.isRunning
    infinityModel=data)
}}

Then use it like so in a top level template:

{{#contact-top-level query=query as |loader|}}
        {{#if loader.isRunning}}
            <tr valign="top">
              <td colspan="3">
                Loading contact list...
              </td>
            </tr>
        {{else}}
          {{#each loader.infinityModel as |contact|}}
            <tr valign="top">
              <td>
                {{#link-to "contact-detail" contact.id}}
                  {{contact.companyName}}
                {{/link-to}}
              </td>
              <td>
                {{#link-to "contact-detail" contact.id}}
                  {{contact.title}}
                  {{contact.firstName}}
                  {{contact.lastName}}
                {{/link-to}}
              </td>
              <td>
                {{#each contact.addresses as |address|}}
                  {{contact-address-in-a-table-cell address=address}}
                {{/each}}
              </td>
            </tr>
          {{/each}}
          <tr>
            <td colspan="3">
              {{infinity-loader
                infinityModel=loader.infinityModel
                loadingText='Loading records...'
                loadedText='All records loaded.'}}
            </td>
          </tr>
        {{/if}}
{{/contact-top-level}}

@pkimber
Copy link
Author

pkimber commented Aug 11, 2018

Thank you @snewcomer for taking the time to answer my question.

  1. I am using ember-infinity version 1.1.2
  2. I copied the yield code from http://ember-concurrency.com/docs/examples/autocomplete (I don't know enough yet to change too much of the code).
  3. I think it is a great idea to move the code into a component. I will spend some time trying to get the code working (so much to learn).

I will take the time to try and understand. Thank you again for your help. Very much appreciated...

@scottkidder
Copy link
Contributor

I have recently got something very similar to this working. Eventually I'll try to extract the pattern out for my own use and to share but for now I'll just say I've been able to get it working very nicely.

@scottkidder
Copy link
Contributor

I've just compared @snewcomer example to my fully working implementation from a few weeks ago and they are identical in the pattern. I have really seen no downside with this approach. It really cleaned things up by isolating the data loading into the component.

The only difference I see is that I am already debouncing my input to this component so I am not using timeout, but maybe I should?

I also implemented skeleton content placeholders in conjunction with this, taking advantage of ember-concurrency derived state which has made for a really nice UX. We started with ember-content-placeholders but quickly decided to build our own to fit our content exactly.

Maybe we can start a 'Cookbook' section somewhere in the documentation to give things like this pattern a nice home.

@snewcomer
Copy link
Collaborator

I completely agree so let's do it! Perhaps a PR that we can both iterate on (hopefully not too time consuming)? Lmk what you think.

@pkimber
Copy link
Author

pkimber commented Aug 23, 2018

Sounds great to me. I am happy to test it...

@scottkidder
Copy link
Contributor

@snewcomer Are you thinking we add this as a section to README.md or maybe we should use the Wiki?

@snewcomer
Copy link
Collaborator

Great idea. Perhaps a wiki with link in Readme?

@nickschot
Copy link

I've been using ember-infinity in a similar way recently. The pattern works really well. 👍for service based infinity models!

It's not immediately clear from the README yet that this kind of stuff is possible. I'd say that's mainly because the README only focusses on using infinity in the model hook.

Just to comment on the yield example for e-c, that's just meant as a debounce so you don't completely spam your API while typing.

@Duder-onomy
Copy link
Contributor

This is super rad. Now I want to rewrite a bunch of things....

@snewcomer
Copy link
Collaborator

@scottkidder @nickschot @pkimber I think ya'll are onto something 😄. Would anybody like to put up a PR in the README/or paste a shortened version here to show what is possible with e-c and components? I think it would be useful to put this towards the top based on what ya'll are saying!! Perhaps we will move the content to a wiki if it makes sense.

Separately, I'll put up a PR to add navigation to various sections b/c scrolling through the whole README kinda is painful.

@nickschot
Copy link

I just open sourced a WIP of 'ember-model-select' which retrieves options based on a model (and optional filter). It's an ember-power-select wrapper which adds (optional) infinite scroll support through ember-infinity/ember-concurrency.

For a sneak peek of where infinity is hidden see: https://github.com/weddingshoppe/ember-model-select/blob/master/addon/components/model-select.js#L232

@hhff
Copy link
Collaborator

hhff commented Sep 5, 2018 via email

@nickschot
Copy link

There is another possible issue I found with this approach. It seems the infinity service keeps it's array of infinityModels indefinitely and using ember-concurrency to filter it we generate a lot of them. @snewcomer do we clean this up ourselves by overriding the attribute on the service with an empty EmberArray?

@snewcomer
Copy link
Collaborator

@nickschot Do you have a specific example?

There are a few service methods available if that may help. Lmk though! - https://github.com/ember-infinity/ember-infinity#service-methods

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants