Muhammad Hamza
Muhammad Hamza

Reputation: 893

Re render a component from another component Ember

I'm trying to re-render a specific component when another component is clicked. When the user clicks the particular button component, a variable in sessionStorage changes, which is used by another component to display data. Upon click I want that specific component to re-render itself. I have seen Ember Re-render component and Ember Rerendering component within component, but they don't seem to be working in my case. Here are my files:

templates/components/buttons/button-cancel.hbs

{{#each-in metaData as |module definition|}}
  {{#if (eq definition.name "cancel_button") }}
  <button class={{definition.css_class}} {{action "changeToAccounts"}}> Accounts </button>
  {{/if}}
{{/each-in}}
{{yield}}

components/buttons/button-cancel.js

import Component from '@ember/component';
import MD from "../../utils/metadata";

export default Component.extend({
  init: function() {
    this._super(...arguments);
    this.metaData = MD.create().getMetaViewStuff("Leads", "record", "buttons");
    //    console.log(this.metaData);
  },
  actions: {
    changeToAccounts: function() {
      sessionStorage.setItem('module', "Accounts");
    }
  }

});

templates/components/panels/list-panel.hbs

{{buttons/button-save}} <!--This button is same as button-cancel-->
{{buttons/button-cancel}}
{{field-list-headers}}
{{yield}}

components/field-list-headers (the component that needs re-rendering)

import Component from '@ember/component';
import MD from "../utils/metadata"

export default Component.extend({
  init: function(){
    this._super(...arguments);
    this.metaData = MD.create().getMetaViewStuff(sessionStorage.getItem('module'), "list", "panels")
  }
});

function getMetaViewStuff

  getMetaViewStuff: function(module, submodule, item, i18n) {
    if (this.modules[module]["views"][submodule]["meta"] !== undefined && this.modules[module]["views"][submodule]["meta"][item] !== undefined) {
      let meta = this.modules[module]["views"][submodule]["meta"][item];
      return meta;
    }
    return false;
  }

Upvotes: 0

Views: 1082

Answers (1)

mistahenry
mistahenry

Reputation: 8724

My recommendation would be to share the same property via a service and to use this service to update local storage and cache the value on said service. I've provided an example that uses a service to share a value amongst two components (I couldn't use local storage in the twiddle).

So let's assume we want to show a list of programming languages either as a table or as an ordered list. We have two components, the reusable mode-changer button for toggling the mode and the programming-languages component for actually rendering the list, who both interact with the shared mode-service.:

export default Ember.Service.extend({
  init(){
    this._super(...arguments);
    //actually read from local storage here and set default on null
    this.set('mode', 'list');
  },
  toggleMode(){
    let newValue = this.get('mode') === 'list' ? 'table' : 'list';
    //actually store to local storage here before caching on the service
    this.set('mode', newValue);  
  }
});

Imagine this template showing both components:

{{programming-languages languages=languages}}
{{mode-changer}}

The mode-changer injects the mode-service, with which it derives the logical button text and toggle the mode-service's mode property via toggleMode:

export default Ember.Component.extend({
  modeService: inject(),
  buttonText: computed('modeService.mode', function(){
    let mode = this.get('modeService.mode');
    return mode === 'list' ? "Change to table" : "Change to list";
  }),
  actions: {
    toggleMode(){
      let modeService = this.get('modeService');
      modeService.toggleMode();
    }
  }
});

component of mode-changer:

<button onclick={{action 'toggleMode'}}>{{buttonText}}</button>

The programming-languages component bases it's rendering mode off of modeService.mode via a computed property:

export default Ember.Component.extend({
  modeService: inject(),
  renderList: computed('modeService.mode', function(){
    let mode = this.get('modeService.mode');
    return mode === 'list';
  })
});

so that changes to the modeService.mode will cause the computed property to invalidate and trigger a render of the component.

{{#if renderList}}
    <ol>
  {{#each languages as |language|}}
    <li>{{language}}</li>
  {{/each}}
  </ol>
{{else}}
  <table>
    <th>Language</th>
      {{#each languages as |language|}}
      <tr>{{language}}</tr>
    {{/each}}
  </table>
{{/if}}

In Ember, service's are singletons so such sharing is appropriate in many cases. To me, this is much better than any other solution that actually stores/retrieves references to the component itself and invokes its render function.

Upvotes: 2

Related Questions