queryParams from route to component and back


#1

I ran into and issue today with queryParams and realized DDAU was my best solution to solve my issue. I am a advocate of avoiding controllers but with routable components not available yet I have to be creative. So I hope my approach to solving the situation of components talking to routes can help some of you out that are in the same situation.

I made a base class for route because this feature was needed throughout my app.

here is the base route:

import Ember from 'ember';

export default Ember.Route.extend({
  actions: {
    updateParams(param, value){
      let queryParams = this.get('queryParams');
      queryParams[param] = value;
    },
    searchParams(){
      this.model(this.get('queryParams')).then((data)=>{
        return this.setupController(this.controller, data);
      });
    },
    clearParams(){
      const paramsArray = this.get('paramsArray');
      let queryParams = this.get('queryParams');
      paramsArray.forEach((param)=>{
        queryParams[param] = "";
      });
    },
  }
});

And then in my route for the page I use a strategy to bypass the fact query params can’t be arrays inside the route:

export default Base.extend({
  paramsArray: [
    'page_size',
    'sort',
    'created_dt',
    'deadline_dt',
    'completed_dt',
    'status',
    'priority',
    'assigned_to',
    'completed',
    'is_recurring',
    'created_by',
    'completed_by'
  ],

  queryParams: {
    page_size: '',
    sort: '',
    created_dt: '',
    deadline_dt: '',
    completed_dt: '',
    status: '',
    priority: '',
    assigned_to: '',
    completed: '',
    is_recurring: '',
    created_by: '',
    completed_by: '',
  },

  model(params){
    return Ember.RSVP.hash({
      tasks: this.store.query('task', {
        page_size: params.page_size,
        sort: params.sort,
        created_dt: params.created_dt,
        deadline_dt: params.deadline_dt,
        completed_dt: params.completed_dt,
        status: params.status,
        priority: params.priority,
        assigned_to: params.assigned_to,
        completed: params.completed,
        is_recurring: params.is_recurring,
        created_by: params.created_by,
        completed_by: params.completed_by,
      })
    });
  },

  setupController(controller, models){
    this._super(...arguments);
    controller.set('queryParams', this.get('queryParams'))
    controller.set('paramsArray', this.get('paramsArray'))
    controller.set('gridData', models.tasks.toArray());
    controller.set('columns', this.get('columns'));
  },

});

Then from here I interacted with the component via the template.hbs file:

{{filter-system
  paramsArray=paramsArray
  updateParams="updateParams"
  searchParams="searchParams"
  clearParams="clearParams"
}}

Inside the component.js file I have:

export default Ember.Component.extend({
  session: Ember.inject.service(),
  actions: {
    search(){
      this.sendAction('searchParams');
    },
    update(param, value){
      this.sendAction('updateParams', param, value);
    },
    clear(){
      this.sendAction('clearParams');
    }
  },
});

And inside the template.hbs file for the component you can assign values and params from paramsArray that was passed in to map to the queryParams inside the route. i.e:

<button type="button" name="button" {{action "clear"}}>clear</button>
<button type="button" name="button" {{action "update" "page_size" "40"}}>update</button>
<button type="button" name="button" {{action "search"}}>search</button>

Of course these can be input fields or dropdowns instead of buttons.

This approach allows the queryParam values to remain the same without refreshing the entire model which would clear the param value to original state. I took advantage of the model hook being promise aware then passing the value to the setupController() method that will cause the view to update.

any questions or comments please email me at kempt09@gmail.com *Please understand this is not fully tested and may not be the best solution for you, but it worked for me :slight_smile:


#2

Nice! We’re just about to nuke some controller logic in favor of a similar approach, great to see someone’s had luck going that route (no pun intended).

We’ve actually made a mixin that handles logic around chaning filters and updating params, as well as handles infinite scrolling, since we have multiple components that deal with dynamic lists.


#3

I see one problem in your implementation: You use the queryParams property of Ember.Route as if it contained the values of the query parameters. But in fact, they contain the description of the query parameters and how the route should behave when/if they are to change.

See http://emberjs.com/api/classes/Ember.Route.html#property_queryParams for details.

Did you have a look at https://github.com/knownasilya/ember-query-params ?

I haven’t tried it yet, so if you have, I’d love your feedback, as you seem to have gathered valuable experience around queryParams and controller-less apps.


#4

I realize this is an old post but I recently stumbled upon it while trying to do this exact same thing. While the code presented in the first post pointed me in the right direction, what @xcambar mentioned (that the code misused the queryParams hash on the route) seemed important enough to take into account. After a bit of playing around I came up with the following solution which does the job while keeping everything clean:

// app/routes/my-route.js
export default Ember.Route.extend({
    filter: null,

    queryParams: {
        // definition as per:
        // https://emberjs.com/api/ember/2.15/classes/Ember.Route/properties/queryParams?anchor=queryParams
        someParam: {}
    },

    model(params) {
        // Do something with the query params here (they are in params)
        const filter = Ember.Object.create(params);
        this.set('filter', filter);
    },

    setupController(controller, _model) {
        this._super(...arguments);
        // Here we "define" the queryParams in the controller
        controller.set('queryParams', Object.keys(this.get('queryParams')));
    },

    _updateQueryParameter(paramName, paramValue) {
        // The following will ensure the query params are updated in the url
        // https://guides.emberjs.com/v2.15.0/routing/query-params/
        Ember.set(this, 'controller.${paramName}', paramValue); 
        // Might want to refresh the model here
    },

    actions: {
       updateQueryParameter() { this._updateQueryParameter(...arguments); }
    }
});
<!-- app/templates/my-route.hbs -->
{{my-component model=model filter=filter updateFilterAction='updateQueryParameter'}}
// app/components/my-component.js
export default Ember.Component.extend({
    filter: null, // Filter gets passed in or you might want to manage it otherwise
    // ....
    actions: {
      // Only sync values with query parameters once a button is pressed
       updateFilter() { this.sendAction('updateFilterAction', 'someParam', this.get('filter.someFilter')); }
    }
});
<!-- app/templates/components/my-component.hbs -->
<div>
    {{input value=filter.someFilter}}
    <button {{action "updateFilter"}}>Update Filter</button>
</div>

Of course this is a stripped down version of something that actually works but I think it should be enough to get on the right track.