[英]How should I share data between Ember components?
我的Ember应用程序有一个包含2个不同组件的路由和一个带有index.hbs模板的控制器。
这是它的样子:
1)用户可以从过滤器组件的下拉列表中选择多个过滤器
2) DataGrid是与过滤器分开的组件
3)用户可以通过复选框从DataGrid中选择多行
4)创建自定义报告按钮将“sendAction”激活到路径的控制器
这些数据不是特定于模型的...它只是我可以制作自定义报告之前所需的临时数据。
Ember最佳实践是“Data Down / Actions Up”,根据我的阅读,您不应该尝试从控制器访问组件。
但问题是, 控制器中的createCustomReport方法需要访问在filter-component中选择的所有过滤器以及在grid-component中检查的所有行。
我的第一直觉是在组件本身上设置属性 - 让它保持自己的状态 - 然后从控制器获取对组件的引用,以便在将其传递给报告函数之前获取其状态。
但显然这是禁忌。
这是我目前的解决方案:
每次选择过滤器时,都会有一个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方法?
好吧,我想我已经设法解决了我自己的问题并且采用了Ember的做事方式。
我发现了两种不同的解决方案,每种解决方案都有其优点。 另外,我已经创建了2个关于如何解决状态传播和共享组件数据的小型Ember Twiddle迷你教程。
这两种解决方案完全符合Ember 2.6的做事方式: 无需控制器 。
我建立了一个简单的电影列表,可以在这里查看: 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');
}
}
});
这是Component的把手文件:
<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>
这是Route文件:
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包含一个未定义的状态属性:'currentlySelectedRestaurant'。
这很容易就是具有多个属性或数组的对象。
您还可以使用“componentState”这样的通用名称,并存储您从任何组件发送的任何内容:例如,在筛选列表上选中的选项或网格中的选定项目。
不使用服务的优点:
这样做更容易。 只需在组件中使用sendAction()即可冒泡到路由器。 并且没有创建额外的文件或任何依赖项。
不使用服务的缺点
由于模型数据从路径级别向下流动,因此如果更改路径,则无法访问该状态。
每个解决方案都是可行的,所以我会留给您找到最有效的解决方案。
此外,我还没有将此标记为答案,因为其他人可能有更好的解决方案,并且很高兴得到一些反馈。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.