admin管理员组

文章数量:1323735

My Ember app has a route that contains 2 different ponents and one controller with an index.hbs template.

Here's what it looks like:

1) A user can select multiple filters from the dropdowns of the Filter Component

2) The DataGrid is a separate ponent from the filter

3) A user can select multiple rows from the DataGrid by checking boxes

4) Create Custom Report button fires "sendAction" to the route's controller

This data is not model-specific... it's just temporary data that is required before I can make a custom report.

Ember best practices are "Data Down / Actions Up", and from what I read, you shouldn't be trying to access a ponent from a controller.

The problem, though, is that the createCustomReport method in the controller needs to have access to all of the filters that were selected in the filter-ponent along with all of the rows that were checked in the grid-ponent.

My first instinct is to set the properties on the ponent itself - have it maintain its own state - then get a reference to the ponent from the controller in order to get its state before passing it off to the report function.

But apparently that is a no-no.


Here's my Current Solution:

Each time I select a filter, there is a sendAction that bubbles up to the controller from the ponent and sets a custom property on the controller.

Also, each time I select a checkbox from the grid, another sendAction goes to the ponent, then bubbles up to the controller and sets a custom property on the controller for selected grid rows.

Then, when I click "createCustomReport" the method that fires in the controller has access to the properties that I set earlier - because they are all on the controller now.

So it looks something like this:

import Ember from 'ember';

export default Ember.Controller.extend({

    myFirstFilter: undefined,
    mySecondFilter: undefined,

    actions: {
        createCustomReport() {
            // do something with all those ponent properties you've been setting
        },

        // These are triggered by the sendAction on the respective ponent
        firstFilterMethod(myProperty1) {                
            this.set('myFirstFilter', myProperty1.name);
        },

        secondFilterMethod(myProperty2) {               
            this.set('mySecondFilter', myProperty2.name);
        },

        ... etc...


    }
});

Here's My Problem With This

I'm not directly accessing the ponents from the controller, but by using the "Actions Up" principle, I'm setting properties on the controller that are view specific.

Coming from a Sencha ExtJS background where controllers have references to their views, I find this very weird.

By not getting references to ponents, I'm supposed to be decoupling my controller from its views... but since all the properties I'm setting would normally be on the view, the controller ends up being even more coupled to the view than it would be if I were to just get a reference to the ponent.

Is this considered "best practice" in Ember or is there a better way for me to get the data of all these separate ponents in order to fire off the createCustomReport method?

My Ember app has a route that contains 2 different ponents and one controller with an index.hbs template.

Here's what it looks like:

1) A user can select multiple filters from the dropdowns of the Filter Component

2) The DataGrid is a separate ponent from the filter

3) A user can select multiple rows from the DataGrid by checking boxes

4) Create Custom Report button fires "sendAction" to the route's controller

This data is not model-specific... it's just temporary data that is required before I can make a custom report.

Ember best practices are "Data Down / Actions Up", and from what I read, you shouldn't be trying to access a ponent from a controller.

The problem, though, is that the createCustomReport method in the controller needs to have access to all of the filters that were selected in the filter-ponent along with all of the rows that were checked in the grid-ponent.

My first instinct is to set the properties on the ponent itself - have it maintain its own state - then get a reference to the ponent from the controller in order to get its state before passing it off to the report function.

But apparently that is a no-no.


Here's my Current Solution:

Each time I select a filter, there is a sendAction that bubbles up to the controller from the ponent and sets a custom property on the controller.

Also, each time I select a checkbox from the grid, another sendAction goes to the ponent, then bubbles up to the controller and sets a custom property on the controller for selected grid rows.

Then, when I click "createCustomReport" the method that fires in the controller has access to the properties that I set earlier - because they are all on the controller now.

So it looks something like this:

import Ember from 'ember';

export default Ember.Controller.extend({

    myFirstFilter: undefined,
    mySecondFilter: undefined,

    actions: {
        createCustomReport() {
            // do something with all those ponent properties you've been setting
        },

        // These are triggered by the sendAction on the respective ponent
        firstFilterMethod(myProperty1) {                
            this.set('myFirstFilter', myProperty1.name);
        },

        secondFilterMethod(myProperty2) {               
            this.set('mySecondFilter', myProperty2.name);
        },

        ... etc...


    }
});

Here's My Problem With This

I'm not directly accessing the ponents from the controller, but by using the "Actions Up" principle, I'm setting properties on the controller that are view specific.

Coming from a Sencha ExtJS background where controllers have references to their views, I find this very weird.

By not getting references to ponents, I'm supposed to be decoupling my controller from its views... but since all the properties I'm setting would normally be on the view, the controller ends up being even more coupled to the view than it would be if I were to just get a reference to the ponent.

Is this considered "best practice" in Ember or is there a better way for me to get the data of all these separate ponents in order to fire off the createCustomReport method?

Share Improve this question edited Mar 13, 2018 at 11:12 Liam 29.8k28 gold badges138 silver badges202 bronze badges asked Jul 6, 2016 at 21:24 PhillipKreggPhillipKregg 9,3588 gold badges50 silver badges63 bronze badges 6
  • Interesting post. When you have designed this what was the main reason to use ponents and why you have ended up with two of them? – Mirza Memic Commented Jul 6, 2016 at 22:19
  • @MirzaMemic The reason I'm using ponents is because the application will grow in plexity and I want to be able to reuse functionality without it being tightly coupled. For instance, other parts of the app will use the filter ponent - but there will not be a grid. And there will be places in the app that use a datagrid without filters. – PhillipKregg Commented Jul 6, 2016 at 22:33
  • @PhillipKregg This is the ember way. The only thing is that createCustomReport should not be inside controller, since controller should be decorators of the view. Officially controllers are deprecated in favour of routable ponents but they have not landed yet. Since you are reusing them later it makes sense to create them as ponenst and what you have here is the "ember" way. Another thing to consider is whether some of the filters should be query parms - so that you have url driven filter selection #route?filter1=value => once you visit the route the filter is preselected. – Mirza Memic Commented Jul 7, 2016 at 4:09
  • 1 Officially controllers are not deprecated yet, but there plans to replace them at some point :-) – acorn Commented Jul 7, 2016 at 11:49
  • @acorn Would you remend that I replace my controllers with something like the ember-route-action-helper add on until routable ponents e out? Here's the repo: github./DockYard/ember-route-action-helper – PhillipKregg Commented Jul 8, 2016 at 0:29
 |  Show 1 more ment

1 Answer 1

Reset to default 8

Alright, I think I've managed to solve my own problem and e around to the Ember way of doing things.

I found 2 different solutions, each of which have their merits. Plus I've created 2 small Ember Twiddle mini tutorials on how to solve state propagation and sharing ponent data.

Both solutions are fully pliant with the Ember 2.6 way of doing things: No Controllers Needed.

The first one is using an Ember Service.


I built a simple movie list that can be viewed here: https://ember-twiddle./c91e98cd255a556311417ac603ab0315

By following the ments inside the files and looking over the Ember Twiddle above, all your questions should be answered on how to implement this.

Since a Service is a singleton, I can inject it into my ponents and into my route and its sole purpose will be to maintain the data of its associated ponent.

Here is what the ponent looks like:

import Ember from 'ember';

export default Ember.Component.extend({
  movieService: Ember.inject.service('movie-displayer-service'),
  currentSelectedMovie: '',

  didInsertElement: function() {
    // When the ponent is inserted into the DOM tree, use the model to set
    // the 'currentSelectedMovie' property.
    this.set('currentSelectedMovie', this.get('model').currentSelectedMovie);   
  },

  actions: {
    selectMovie: function(movie) {
      // Instead of saving state in the ponent itself, let's
      // save it in a service that can be consumed anywhere
      // in the application.

     this.get('movieService').setupCurrentSelectedMovie(movie);

     // When the movie changes, we can override the 'currentSelectedMovie' property
     // that is being populated with the 
     this.set('currentSelectedMovie', movie);   

    }
  }
});

Here is what the Service looks like:

import Ember from 'ember';

export default Ember.Service.extend({
  currentSelectedMovie: undefined,

  setupCurrentSelectedMovie: function(movie) {
   this.set('currentSelectedMovie', movie); 
  },

  showSelectedMovie: function() {
    if (this.get('currentSelectedMovie')) {
        alert("The current selected movie of the movie-displayer ponent is:  \n" + this.get('currentSelectedMovie'));
    } else {
        alert('Please Select a Movie First');
    }
  }
});

Here is the Component's handlebars file:

<div class="movie-list-container">
    <h4 class="movie-list-title">Movie List</h4>

  <ul>
    {{#each model.movies as |movie|}}

        {{!--   'eq' is a helper function that I made
                    to pare two values.  You can check it out in
              the 'helpers' folder.
      --}}
        <li class="{{if (eq movie currentSelectedMovie) "selected" "not-selected"}}" {{action 'selectMovie' movie}}>{{movie}}</li>
    {{/each}}
  </ul>

</div>

And here is what the route looks like:

import Ember from 'ember';

export default Ember.Route.extend({
  movieService: Ember.inject.service('movie-displayer-service'),

  model: function() {
    return {
        currentSelectedMovie: this.get('movieService').currentSelectedMovie,

      movies: ['Captain America: Civil War', 'Guardians of the Galaxy', 'Ant Man']
    }
  },

  actions: {
    showServiceState: function() {
        this.get('movieService').showSelectedMovie();
    }
  }
});

Pros of Service solution:

Being a singleton, I can access the data of this ponent anywhere in the application.

Cons of Service solution:

I have to inject this into every file that I want to use it in - thereby creating dependencies as I go. The other solution is to use an Ember Initializer class that will automatically inject it into Routes, Controllers, or Components upon app startup. Of course, this means that it would go into every single instance of what it is injected into which could be overkill.


The second solution sends state from the ponent to the router without a service


The second Ember Twiddle is a simple restaurant list that shows how to propagate state without the need of a service:

https://ember-twiddle./dffc679fb96434ba6698161ba7617d15

Here is the ponent's handlebars file:

<div class="restaurant-list-container">
  <ul>
    {{#each model as |restaurant|}}
      <li class="{{if (eq currentlySelectedRestaurant restaurant ) 'selected' 'not-selected' }}" {{action 'selectRestaurant' restaurant}}>{{restaurant}}</li>
    {{/each}}
  </ul>

</div>

Here is the Route file:

import Ember from 'ember';

export default Ember.Route.extend({  
  // Properties Here
    currentlySelectedRestaurant: 'Please Select a Restaurant',

  model: function() {
    return ['Taco Bell', 'McDonalds', 'Dennys']
  },

  actions: {
    setupRestaurantState : function(restaurant) {
        this.set('currentlySelectedRestaurant', restaurant);
    },

    getComponentState: function() {
     alert(this.get('currentlySelectedRestaurant'));
    }
  }
});

And here is the Component file:

import Ember from 'ember';

export default Ember.Component.extend({

  currentlySelectedRestaurant: undefined,

  actions: {
    selectRestaurant: function(restaurant) {

      // The 'sendAction' method is where the magic happens.
      // A method called 'stateSetter' references a function
      // that lives either on the controller or the route.
      // This was setup when the ponent was instantiated in the
      // fancy-restaurants.hbs file.
      this.sendAction('stateSetter', restaurant);
      this.set('currentlySelectedRestaurant', restaurant);
    }
  }
});

Notice that the Route contains an undefined state property: 'currentlySelectedRestaurant'.

This could easily be an object with multiple properties or an array.

You could also have a generic name like "ponentState" and store whatever you choose to send up from any ponent: options checked on a filtered list or selected items from a grid for instance.

Pros of not using a Service:

It's easier to do. Just use sendAction() in your ponent to bubble up to the router. And there are no extra files created or any dependencies.

Cons of not using a Service

Since the model data flows down from the route level, you won't be able to access the state if you change routes.


Each solution is viable so I will leave it up to you to find what works best.

Also, I'm not marking this as the answer just yet because someone else may have a better solution and it would be nice to get some feedback on this.

本文标签: javascriptHow should I share data between Ember componentsStack Overflow