javascriptember.jsember.js-view

Dynamic content loading view


I have an ember component with a template that contains a table. In my component - I would like to issue out ajax calls to a 3rd party service and get some data. After getting this data, I would have to make another subsequent ajax call based on the input. Since this would take time, I would like to update the view one after the other when the ajax called finishes. In a visual sense - this would be adding a new row to the table after one request is processed.

Currently, ember only allows us to pass an array object via its model() method in the router.

I looked into several projects like List-view but it's not solving the above-mentioned problem.

EDIT:-

Below is what I am currently doing -

import Ember from 'ember';
export default Ember.Route.extend({
    model() {
        var list = [];
        var promise = $.ajax({
            type: 'GET',
            url: 'thirdPartyService'
        });
        promise = promise.then(function(data){
            data = JSON.parse(data);
            return data[1];
        });
        promise.then(function(data){
            for (var i = 0; i < data.length; i++) {
                var idea = data[i];

                var url = "thirdPartyService";
                var secondPromise = $.ajax({
                    type: 'GET',
                    url: url,
                    dataType: "text"
                });
                secondPromise = secondPromise.then(function(data){
                    var result = x2js.xml_str2json(data);
                    return result;
                });
                secondPromise.then(function(data){
                    list.pushObject(item);
                });
                return secondPromise;
            }
        });
        return list;
    }
});

My template

<tbody>
{{#each model as |idea|}}
  <tr>
    <td><input type="checkbox" name="id" value="{{idea.id}}"></td>
    <td>{{idea.field4}}</td>
    <td>{{idea.field5}}</td>
    <td>{{idea.field}}</td>
    <td>{{idea.field2}}</td>
    <td>{{idea.field3}}</td>
  </tr>
{{/each}}
</tbody>

The Ember view will be rendered when the list changes. What I want to do is to - add a row to the table when list.pushObject(item); is called. Currently - I see that the view is waiting till the everything is returned.

Also, this application is an electron app - due to this I don't have a backend per say and I am not using ember-data. I am calling couple of 3rd party services so there are no ember-models involved.


Solution

  • Update:

    Demo: https://ember-twiddle.com/eb79994c163dd1db4e2580cae066a318

    In this demo, I download your github data, and the list of repos, so the model hook will return when both api call returned... and finally it will provide the list of repo names.

    Using loading.hbs the loading message automatically appears until the model hook promises resolved.

    In your code, probably overwriting the promise variable is not the best approach, because you overwrite a variable which is not resolved yet.

    If you would like a better user experience, the best if you have only one ajax call in the model, and return that promise... so your model contains the first promise result... and you can use this data to fire a promise in the controller and download the list, so the user can see the header of the table and the loading message until downloading the list.

    Demo extended with a second page: https://ember-twiddle.com/eb79994c163dd1db4e2580cae066a318


    If you have to deal with more models in your page, I would download them in the route handler, with RSVP.hash.

    // for example: app/routes/books.js, where books are the main model
    import Ember from 'ember';
    
    export default Ember.Route.extend({
    
      model() {
        return Ember.RSVP.hash({
          books: this.store.findAll('book'),
          authors: this.store.findAll('author'),
          libraries: this.store.findAll('library')
        });
      },
    
      setupController(controller, model) {
        const books = model.books;
        const authors = model.authors;
        const libraries = model.libraries;
    
        this._super(controller, books);
    
        controller.set('authors', authors);
        controller.set('libraries', libraries);
      },
    });
    

    In the above example, you have model (with the list of books), authors and libraries in your controller, so you can pass forward these arrays to components.

    Do you use Ember Data to access your server API or 3rd party service? In this case, you can inject store service in your component and use there as you would use in route handler.

    Other option using closure actions, so your actions would be in controller and you can call these actions from components, so all the component would be responsible only for viewing data and other snippets.

    In the following example, we create a self contained component, where you have a button to fire an action, which will download data from the server, using classic jquery ajax call, which is wrapped in a promise, so you can chain these promises and fire them in a row.

    Create a component

    $ ember g component my-component
    

    The template app/templates/components/my-components.hbs, with a button, a flag and the list.

    <button {{action 'downloadSomething'}}>Download</button>
    
    in progress: {{downloadInProgress}}
    
    {{#each downloaded as |item|}}
      {{item.name}}
    {{/each}}
    

    The connected js file app/components/my-components.js

    import Ember from 'ember';
    
    const { $ } = Ember;
    
    export default Ember.Component.extend({
    
      downloaded: [],
    
      actions: {
    
        downloadSomething() {
          this.set('downloadInProgress', true);
    
          this._getData('http://localhost:8080').then(
            (response) => {
              this.set('downloadInProgress', false);
              this.get('downloaded').pushObjects(response.contacts);
            },
            (error) => {
              // some error handling...
            }
          );
        }
      },
    
      // this function wraps the ajax call in a Promise
      _getData(url) {
        return new Ember.RSVP.Promise((resolve, reject) =>
          $.ajax(url, {
            success(response) {
              resolve(response);
            },
            error(reason) {
              reject(reason);
            }
          })
        );
      }
    
    });
    

    In the ajax response, we push the returned Objects in the downloaded array, so it would always added to the list.