Add model data from cotroller

#1

hello , I am trying to implement skeleton loading in the ember app. This requires me to move the data loading from route to controller. Now the challenge is to load the model ‘heros’ from controller.

controller.js

fetchData(queryParams) { const res = fetch(https://someurl?heroes=${*queryParams*.heros})

.then(res=> res.json())
.then(res=>{
 console.log(res);
/*{heroes:[{hero1:xx},{hero2:xx},{hero3:xx}]*/
/* from here i want to load the res to a model name hero*/

});

#2

Hi, can you define “skeleton loading” please?

#3

Hello tkraak, Please refer to this link

#4

@light44 this is a common desire and yet I’ve also found there to be a lack of clear best practice strategy. That article is pretty good, though I’d say you don’t necessarily need to move your data loading out of the route, you just need to return a non-promise from the model hook. What I would say is that you should almost certainly use ember-concurrency tasks to do this. They give you a much nicer wrapper around async tasks. Where all this gets pretty tricky is if you have nested routes.

Anyway, I’ve recently been writing an experimental addon for doing this sort of thing. Not necessarily suggesting you use the addon itself but you can copy the “needs-async” component as it’s a pretty generic provider component that works with an ember concurrency task instance to render loading, error, and loaded states. There’s not much to it. It’s just an example of how you might do this sort of skeleton loading. Here’s an example of how you COULD do it, even keeping your data fetching in a route:

// routes/some-route.js
import Route from '@ember/routing/route';
import { task } from 'ember-concurrency';

export default Route.extend({
  fetchHeroesTask: task(function * (queryParams) {
    return yield fetch(...);
  }),

  model(/*params, transition*/) {
    return {
      heroes: this.fetchHeroesTask.perform({queryParam1:"foo", queryParam2:"bar"})
    };
  },
});

Then in your template, assuming you had a component like in the addon called needs-async you could do this:

{{!-- templates/some-template.hbs --}}
{{#needs-async needs=this.model.heroes as |states|}}
  {{!-- any markup you want to wrap loading state AND actual data goes around them --}}
  {{#states.loading}}
    {{!-- loading state goes here --}}
    {{loading-spinner}}
  {{/states.loading}}
  {{#states.loaded as |heroes|}}
    {{!-- loaded state goes here --}}
    {{#each heroes as |hero|}}
      {{!-- render a hero here --}}
    {{else}}
      <span>Uh oh, no heroes are here to stop the spread of evil</span>
    {{/each}}
  {{/states.loaded}}
  {{#states.error as |error|}}
    There was a problem loading the heroes: {{error}}
  {{/states.error}}
{{/needs-async}}

Anyway, that’s a basic example. There are plenty of ways to go about it, and lots of nuance depending on the situation (again, nested routes are hard). The main takeaways are that you should definitely consider using ember-concurrency tasks, and you don’t really have to move everything out of the route, you just have ot make sure that you don’t return promises from any of the hooks (the above example just returns an object, which won’t block rendering).

1 Like
#5

Can you explain why “nested routes are hard” in this situation?

#6

Could provide some more detail on the component #needs-async

#7

If you’re using traditional “blocking” routes then child routes don’t have to worry about the data they need (usually from the parent route) being loaded before they attempt to load their models. But once you stop blocking the parent model then it hits the child model hook immediately and then you have to come up with your own mechanism for waiting to load the child data. For example if you had a parent route that loaded a thing, and then the child route needed some data from the thing that the parent route was loading (an id or a relationship or another attribute from “thing”) to load its data, the child has to wait for the parent model (or at least part of it) to load before requesting its model. Then you’re in an async spaghetti type of situation. Certainly not impossible to deal with but there are edge cases and your code gets more confusing.

1 Like
#8

It’s very simple. Honestly I don’t think I explain it any better than just looking at the code:

Javascript: https://github.com/dknutsen/ember-needs-async/blob/master/addon/components/needs-async.js

Template: https://github.com/dknutsen/ember-needs-async/blob/master/addon/templates/components/needs-async.hbs

The component that it yields is literally just a tagless component that renders a block and yields whatever you tell it to from the taskInstance.

The documentation, with another example, is here

1 Like
#9

You can do skeleton loading and still do all the data loading in your routes.

It’s a very good fit for loading templates.

You can choose any point in the route hierarchy to put a loading template, and make the template look like a skeleton version of its real sibling route.

For example, if you have:

this.route('dashboard', function() {
  this.route('special');
});

And you want a skeleton loading screen while the special route is loading, you add:

this.route('dashboard', function() {
  this.route('special');
  this.route('special-loading');
});

And make the special-loading.hbs look like a skeleton version of special.hbs.

3 Likes
#10

model(/params, transition/) {

return {
  *heroes*: this.fetchHeroesTask
 .perform({queryParam1:"foo", queryParam2:"bar"})
};

},

});

The data is still not getting loaded in my ember model … i have checked through ember inspector my model name is “hero-list”

I am pretty new to ember world…

#11

Ah so you’re trying to load the data into the Ember Data store also? Ok… There’s a little ambiguity with the word “model” in Ember so let’s start there. There are Ember Data models, which of course are a description of the records that you want to keep in the ember data store. Then there is your route’s model, which is the data that your route needs to render its view, fetched and returned from the routes model hook. Much of the time your route model hook will fetch and return Ember Data records, so it can be a little confusing to talk about them sometimes because it’s an overloaded word. But anyway hope that’s clearer now. It sounds like you want to fetch data in your route model hook AND want that data to be put into the ember data store in the form of ember data records.

Typically you’d make all data requests via the ember store methods (findRecord, findAll, query, etc). Depending on your backend this may require setting up an adapter or serializer. The nice thing is everything just works once you do this, and data gets loaded into the store and you can retrieve it later, etc.

However if you just want to use fetch instead of using the store methods to get your data you can totally do that, you’ll just need to do another step to get it into the store first. This can be done with store.pushPayload() (see the ember guides here for more details). Note that this may still require some serializer configuration.

Hope that helps a little bit. Let us know if you get stuck or have any other questions. There’s a lot to take in with Ember Data but it’s very powerful and once you “get there” it tends to work very nicely.

EDIT: I’d also encourage you to go more in the direction that @ef4 suggested, especially if you’re newer to Ember. Non-blocking route hooks work but you give up a lot by not using routes as intended…

1 Like
#12

Thank you dan, You have cleared my misconception of ember model:star_struck:… I have tried @ef4 method but the challenge is i have multiple route and creating loading route for each route will cause trouble(create complexity) in the future. Now i am trying pushpayload to push the response

model(/*params, transition*/) { 
        return {
          ['hero-lists']: this.get("fetchHeroesTask").perform({queryParam1:"foo", queryParam2:"bar"})          
          .then(res=>{
            console.log(res);
//{'hero-list':[{'name':'xxx'},{'name':'superxxx'}]}
            console.log(res['hero-list']);
            this.store.pushPayload('hero-list',res);
//create a serilaizer for the same but the method is not calling the serializer
          })
        };
      },

Also which serializer is should use?

#13

I thin i solve the issue need do some test…

Thank you @dknutsen and @ef4