英文原版:https://guides.emberjs.com/v2.13.0/routing/loading-and-error-substates/
Ember Router允许你提供一个读取数据时的反馈–loading状态,同样也提供了error状态。
在beforeModel,model和afterModel钩子函数的执行过程中,数据同时也是需要通过消耗一些时间来读取的。典型的,在每个钩子中的promise对象返回前,router都会暂停当前的transition。
考虑下面的情况:
app/router.js
Router.map(function() {
this.route('slow-model');
});
app/routes/slow-model.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.get('store').findAll('slow-model');
}
});
如果你导航到slow-model路由,那么在model钩子中,查询会需要一段时间才能完成。在这段时间期间,你的用户界面不会有任何的反馈来说明当前正在发生什么事情。如果你从另一个路由导航到slow-model路由,那么在model数据读取完成之前,你将会继续看到前一个路由的模板,并且当slow-model路由的数据读取完成之后,slow-model的模板会猛然的出现。
那么,在transition期间,我们应该怎么来提供一些什么反馈才能不会这么尴尬呢?
只需要定义一个模板,名叫 loading(当然,可以选择性的生成一个响应的路由)。loading子状态的子transition是同步加载,并且URL不会变化,不同于其他的transitions,当前的主transition不会被终止。
一旦主slow-model的transition完成,那么loading路由就会自动退出并且slow-model中的transition会继续进行。
对于嵌套路由,如下:
app/router.js
Router.map(function() {
this.route('foo', function() {
this.route('bar', function() {
this.route('slow-model');
});
});
});
当访问foo.bar.slow-model路由时,Ember会从foo.bar.slow-model-loading层级开始尝试查找与相应路由名字相匹配的routeName-loading模板或者loading模板:
需要注意一下slow-model路由,Ember不会去试着找以slow-model.loading命名的模板,但是在其他层级这种语法是有效的。这在对于在叶子路由创建自定义loading是比较有帮助的。
当访问foo.bar路由时,Ember会寻找:
注意哟,foo.bar.loading现在不被考虑在内哦!
如果beforeModel/model/afterModel没有立即返回resolve,那么这时一个loading事件将会在当前路由触发:
app/routes/foo-slow-model.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.get('store').findAll('slow-model');
},
actions: {
loading(transition, originRoute) {
let controller = this.controllerFor('foo');
controller.set('currentlyLoading', true);
return true; // allows the loading template to be shown
}
}
});
如果在路由中没有定义loading函数,那么这个事件会冒泡到父路由,这就给application路由处理它的机会。
当使用 loading函数时,我们可以利用transition的promise属性来判断loading事件是否结束:
app/routes/foo-slow-model.js
import Ember from 'ember';
export default Ember.Route.extend({
...
actions: {
loading(transition, originRoute) {
let controller = this.controllerFor('foo');
controller.set('currentlyLoading', true);
transition.promise.finally(function() {
controller.set('currentlyLoading', false);
});
}
}
});
Ember提供了一个类似的方法在loading子状态出现错误的时候上场。
与默认的loading事件处理函数的实现相类似,默认的error处理函数会在找一个适当的error状态时进行切入。
app/router.js
Router.map(function() {
this.route('articles', function() {
this.route('overview');
});
});
伴随着loading子状态,如果从articles.overview路由的model钩子(beforeModel或afterModel)中抛出一个错误或者返回一个rejected promise,那么Ember会通过一下流程寻找error模板或者路由:
如果上面的任何一个被找到,那么router会立即transition进入到这个子状态(URL不会变化)。错误的”原因”会被作为参数传入error路由的model钩子。
除非在error子状态中调用setupController()方法,否则model(0钩子不会被调用。看下面的例子:
setupController: function(controller, error) {
Ember.Logger.debug(error.message);
this._super(...arguments);
}
如果没有可用的error子状态,那么error信息会被记录。
如果articles.overview路由的model()钩子返回了一个被rejected的promise,那么error事件将会被触发并且向上冒泡。可以利用这个事件来显示错误信息和重定向到登录页:
app/routes/articles-overview.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.get('store').findAll('privileged-model');
},
actions: {
error(error, transition) {
if (error.status === '403') {
this.replaceWith('login');
} else {
// Let the route above this handle the error.
return true;
}
}
}
});
类似于loading事件,你可以在application中来处理error事件。这样会避免你在多个路由中编写重复代码。
本节完