我应该如何在Ember组件之间共享数据?

时间:2016-07-06 21:24:17

标签: javascript ember.js ember-cli

我的Ember应用程序有一个包含2个不同组件的路由和一个带有index.hbs模板的控制器。

这是它的样子:

enter image description here

1)用户可以从过滤器组件的下拉列表中选择多个过滤器

2) DataGrid是与过滤器

的单独组件

3)用户可以通过复选框从DataGrid中选择多行

4)创建自定义报告按钮会将“sendAction”触发到路径的控制器

此数据不是特定于模型的...它只是我可以制作自定义报告之前所需的临时数据。

Ember最佳做法是“Data Down / Actions Up”,根据我的阅读,您不应该尝试从控制器访问组件。

问题是,控制器中的createCustomReport方法需要访问过滤器组件中选择的所有过滤器以及在网格组件中检查的所有行

我的第一直觉是在组件本身上设置属性 - 让它保持自己的状态 - 然后从控制器获取对组件的引用,以便在将其传递给报告函数之前获取其状态。

但显然这是禁忌。

这是我当前的解决方案:

每次选择过滤器时,都会有一个sendAction从组件冒泡到控制器,并在控制器上设置自定义属性。

此外,每次从网格中选择一个复选框时,另一个sendAction将转到组件,然后冒泡到控制器并在控制器上为选定的网格行设置自定义属性。

然后,当我点击“createCustomReport”时,在控制器中触发的方法可以访问我之前设置的属性 - 因为它们现在都在控制器上。

所以它看起来像这样:

import Ember from 'ember';

export default Ember.Controller.extend({

    myFirstFilter: undefined,
    mySecondFilter: undefined,

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

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

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

        ... etc...


    }
});

这是我的问题

我不是直接从控制器访问组件,但是通过使用“Actions Up”原则,我在控制器上设置了特定于视图的属性。

来自Sencha ExtJS背景,其中控制器引用了他们的观点,我发现这很奇怪。

由于没有获得对组件的引用,我应该将我的控制器与其视图分离...但由于我设置的所有属性通常都在视图上,因此控制器最终会甚至如果我只是获取对组件的引用,则与视图的耦合

这被认为是Ember中的“最佳做法”还是我有更好的方法来获取所有这些单独组件的数据以启动createCustomReport方法?

1 个答案:

答案 0 :(得分:6)

好吧,我想我已经设法解决了自己的问题,并且采用了Ember的做事方式。

我找到了两种不同的解决方案,每种解决方案都有其优点。此外,我还创建了2个关于如何解决状态传播和共享组件数据的小型Ember Twiddle迷你教程。

这两种解决方案完全符合Ember 2.6的做法:无需控制器

第一个是使用Ember服务。

我制作了一个简单的电影列表,可以在这里查看: https://ember-twiddle.com/c91e98cd255a556311417ac603ab0315

通过跟踪文件中的注释并查看上面的Ember Twiddle,您应该回答有关如何实现此问题的所有问题。

由于服务是单身人士,我可以将其注入我的组件和我的路线中,其唯一目的是维护其相关组件的数据。

以下是该组件的内容:

import Ember from 'ember';

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

  didInsertElement: function() {
    // When the component 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 component 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);   

    }
  }
});

以下是服务的内容:

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 component is:  \n" + this.get('currentSelectedMovie'));
    } else {
        alert('Please Select a Movie First');
    }
  }
});

以下是组件的把手文件:

<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 compare 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>

这就是路线的样子:

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();
    }
  }
});

服务解决方案的优点:

作为一个单身人士,我可以在应用程序的任何地方访问该组件的数据。

服务解决方案:

我必须将它注入我想要使用它的每个文件中 - 从而创建依赖项。另一种解决方案是使用Ember Initializer类,它将在应用启动时自动将其注入路由,控制器或组件。当然,这意味着它会进入注入的每个实例,这可能是过度的。

第二种解决方案在没有服务的情况下将状态从组件发送到路由器

第二个Ember Twiddle是一个简单的餐馆列表,显示如何在不需要服务的情况下传播州:

https://ember-twiddle.com/dffc679fb96434ba6698161ba7617d15

以下是组件的把手文件:

<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>

以下是路线档案:

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'));
    }
  }
});

以下是组件文件:

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 component was instantiated in the
      // fancy-restaurants.hbs file.
      this.sendAction('stateSetter', restaurant);
      this.set('currentlySelectedRestaurant', restaurant);
    }
  }
});

请注意,Route包含一个未定义的状态属性:&#39; CurrentlySelectedRestaurant&#39;。

这很容易就是具有多个属性或数组的对象。

您还可以使用&#34; componentState&#34;等通用名称。并存储您从任何组件发送的任何内容:例如,在筛选列表上检查选项或从网格中选择的项目。

不使用服务的优点:

这样做更容易。只需在组件中使用sendAction()即可冒泡到路由器。并且没有创建额外的文件或任何依赖项。

不使用服务的缺点

由于模型数据从路线级别向下流动,如果更改路线,您将无法访问该状态。

每种解决方案都是可行的,所以我会留给你找到最有效的方法。

此外,我还没有将此标记为答案,因为其他人可能有更好的解决方案,并且很高兴得到一些反馈。