ember.jsember-dataember-cliember-controllersember-components

Sync the states of multiple instances of same Ember Component


Here's the situation:

I wrote a component facility-search which searches through some fixture data. I put multiple instances of {{facility-search}} on same template (Tab Pages). This component has some input boxes where we can write search keywords. I want to observe change in value of input box and update the same to another instance of component so that both of them will be in sync.

This is what I'm doing in components/facility-search.js

import Ember from 'ember';
import Em from 'ember';

var FacilitySearchComponent = Ember.Component.extend({

  // Tag name appears in HTML
  tagName: 'div',

  // RandomNumber
  searchBoxId: null,

  // All Facilities
  allFacilities: null,

  // Search Values
  textFacility: "",
  textCountry: "",
  textSpecies: "",

  // Text Input Ids needed for <label for="id"/>
  // Elements in multuple components should not have same id
  textFacilityId: 'text-facility',
  textCountryId: 'text-country',
  textSpeciesId: 'text-species',

  // Initialize Ids
  randomNumber: function(){
    this.set('searchBoxId',(Math.floor(Math.random() * 100) + 1));
    this.set('textFacilityId', this.get('textFacilityId') + "-" + this.get('searchBoxId'));
    this.set('textCountryId', this.get('textCountryId') + "-" + this.get('searchBoxId'));
    this.set('textSpeciesId', this.get('textSpeciesId') + "-" + this.get('searchBoxId'));
  }.on('init'),

  // When component is inserted
  didInsertElement: function() {
    this.set('filteredFacilities', this.get('allFacilities'));
  },

  // Observe Search Values
  watchForFilterChanges: function() {
    this.filterResults();
  }.observes('textFacility', 'textCountry', 'textSpecies'),

  // Filter Data
  filterResults: function() {
    var facilities = // Some mechanism to filter data
    self.sendAction('updateFacilities', facilities);
  }.on('allFacilities'),

  actions: {
    clearSearch: function() {
      this.set('textFacility', null);
      this.set('textCountry', null);
      this.set('textSpecies', null);
      this.filterResults();
    },

  }
});

export default FacilitySearchComponent;

This is my templates/components/facility-search.hbs

<div class="card">
  <div class="card-content directory-search">
    <div class="card-title grey-text text-darken-3">
      <h4>Search Facilities</h4>
      <h4><small class="teal-text">{{filteredFacilities.length}} total</small></h4>
    </div>
    <form {{action "textSearch" this on="submit" data="lol"}}>
      <div class="row">
        <div class="input-field col s12">
          <label for="{{textFacilityId}}">Search by facility</label>
          {{input value=textFacility type="text" id=textFacilityId label="Facility Name"}}
        </div>
        <div class="input-field col s12">
          <label for="{{textCountryId}}">Search by country</label>
          {{input value=textCountry type="text" id=textCountryId label="Facility Country"}}
        </div>
        <div class="input-field col s12">
          <label for="{{textSpeciesId}}">Search by species</label>
          {{input value=textSpecies type="text" id=textSpeciesId label="Facility Species"}}
        </div>
      </div>
    </form>
    <a {{action 'clearSearch'}} class="waves-effect waves-light btn"><i class="material-icons right">clear_all</i>clear search</a>
  </div>
</div>

This is my controllers/map.js

import Ember from 'ember';
import pagedArray from 'ember-cli-pagination/computed/paged-array';

export default Ember.Controller.extend({

  // Facility to be shown in modal
  selectedFacility: null,

  // Facilities to be shown in search
  filteredFacilities: [],

  // Initialize filteredFacilities to model
  initializeFacilities: function() {
    this.set('filteredFacilities', this.get("model"));
  }.observes('model'),

  actions: {
    showFacilityInModal: function(facility){
      this.set('selectedFacility', facility);
    },
    updateFacilities: function(facilities){
      this.set('filteredFacilities', facilities);
    },
  }
});

This is routes/map.js

import Ember from 'ember';

export default Ember.Route.extend({
    model: function() {
        return this.store.find('facility');
    },
});

And this is how I'm using component in templates/map.hbs

{{facility-search allFacilities=model updateFacilities='updateFacilities'}}

I learned that if we put component multiple times; it will have complete new instances. So updating variables textFacility and others cannot be observed in another instance of same component. But I want to update those values in another instance as well. Any idea how we can sync the states of multiple instances of same component?


Solution

  • If I understand you question you want to share values between all component so if you change it in one, it changes in the other.

    You can do this:

      text : {
        facility: "",
        country: "",
        species: "",
      }
    

    instead of

      textFacility: "",
      textCountry: "",
      textSpecies: "",
    

    Declaring it as an object means it will be shares across all component instances like a static variable.