我有一个关于使用Ember加载和缓存远程对象的问题。我正在开发一个Ember应用程序,它通过REST API使用服务器端存储。某些获取的数据很少更改,因此每次加载应用程序时都不需要从服务器获取数据。但对于需要脱机工作但仍将其数据保存到服务器的应用程序而言,这也是一个问题。
Ember Data有一个内置的存储适配器,用于通过REST API保持模型,并且还有an adapter for Local Storage(如下面的Ken所指出的)。问题(如果它是一个问题)是模型只有一个存储适配器,似乎没有任何缓存提取模型的概念,除了将它们保存在内存中。
我在此Ember wishlist和此talk by Tom Dale的评论中发现了类似的请求,但我没有发现任何迹象表明这将是Ember中的现有功能。
我有两个问题(第一个是重要问题):
说到1),我可以想到几个策略:
a)扩展现有适配器并添加自定义远程同步机制:
App.Store.registerAdapter('App.Post', DS.LSAdapter.extend({
// do stuff when stuff happens
}));
b)维护单独的模型类 - 一组用于远程对象,一组用于本地对象 - 并根据需要在它们之间进行同步。使用标准Todo案例:
RemoteTodo –*sync*– Todo
|
UI
我有点希望这是一个真正的菜鸟问题,并且有一个很好的既定模式。
已更新:已找到this similar question。它有一个很好的答案,但它是理论上的。我认为我需要的是一些动手技巧或示例实现的指针。
答案 0 :(得分:5)
只是为了“碰撞”这个帖子一点点,因为当我研究其余的api等的ember本地缓存的解决方案时,这是最好的结果之一:
Dan Gebhardt似乎在使用Orbit.js并将其整合到Ember中做得非常出色: https://github.com/orbitjs/ember-orbit
Orbit是一个独立的库,用于协调对数据源的访问 并且保持其内容同步。
Orbit为构建高级功能提供了基础 客户端应用程序,如离线操作,维护和 同步本地缓存,撤消/重做堆栈和临时编辑 上下文。
Orbit.js功能:
支持应用程序中的任意数量的不同数据源,并通过通用接口提供对它们的访问。
允许不同来源的请求得到满足,包括指定优先级和后备计划的能力。
允许记录同时存在于各个来源的不同状态。
跨源协调转换。尽可能自动处理合并,但允许完全自定义控制。
允许阻止和非阻塞转换。
允许同步和异步请求。
通过跟踪操作的反转来支持事务和撤消/重做。
使用纯JavaScript对象。
不要错过他关于Orbit的精彩演讲和幻灯片:
Introduction to Orbit.js
(更新:我在Orbit页面中添加了一些更具描述性的信息,因为我的帖子因为“只是”引用外部资源并且本身不包含实际解决方案而被低估。但Orbit似乎对我而言像解决方案一样,这里“包含”的唯一方法是通过链接。)
答案 1 :(得分:3)
您可能会发现有用的本地存储适配器的实现。看看https://github.com/rpflorence/ember-localstorage-adapter
答案 2 :(得分:1)
这是一种方法。适配器的mixin,方法localStoreRecord
可用于缓存记录,最后是初始化程序以预加载商店。
本地存储只是一个关键:字符串化对象的值存储,因此我们可以将所有应用程序数据存储在一个键下。
注意:这是使用es6模块
// app/mixins/local-storage.js
import Ember from 'ember';
export default Ember.Mixin.create({
appName: 'myApp',
// how many records per model to store locally, can be improved.
// needed to prevent going over localStorage's 5mb limit
localStorageLimit: 5,
localStoreRecord: function(record) {
var data = JSON.parse(localStorage.getItem(this.appName));
data = data || {};
data[this.modelName] = data[this.modelName] || [];
var isNew = data[this.modelName].every(function(rec) {
rec.id !== record.id;
});
if (isNew) {
data[this.modelName].push(record);
if (data[this.modelName].length > this.localStorageLimit) {
data[this.modelName].shift();
}
localStorage.setItem(this.appName, JSON.stringify(data));
}
}
});
// app/adapters/skateboard.js
import DS from 'ember-data';
import Ember from 'ember';
import LocalStorageMixin from '../mixins/local-storage';
export default DS.RESTAdapter.extend(LocalStorageMixin, {
modelName: 'skateboard',
find: function(store, type, id) {
var self = this;
var url = [type,id].join('/');
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: 'api/' + url,
type: 'GET'
}).done(function (response) {
// cache the response in localStorage
self.localStoreRecord(response);
resolve({ type: response });
}).fail(function(jqHXR, responseStatus) {
reject(new Error(type +
' request failed with status=' + reponseStatus);
});
});
},
updateRecord: function(store, type, record) {
var data = this.serialize(record, { includeId: true });
var id = record.get('id');
var url = [type, id].join('/');
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: 'PUT',
url: 'api/' + url,
dataType: 'json',
data: data
}).then(function(data) {
// cache the response in localStorage
self.localStoreRecord(response);
resolve({ type: response });
}).fail(function(jqXHR, responseData) {
reject(new Error(type +
' request failed with status=' + reponseStatus);
});
});
}
});
// app/initializers/local-storage.js
export var initialize = function(container/*, application*/) {
var appName = 'myApp';
var store = container.lookup('store:main');
var data = JSON.parse(localStorage.getItem(appName));
console.log('localStorage:',data);
if (!data) {
return;
}
var keys = Object.keys(data);
if (keys.length) {
keys.forEach(function(key) {
console.log(key,data[key][0]);
store.createRecord(key, data[key][0]);
});
}
};
export default {
name: 'local-storage',
after: 'store',
initialize: initialize
};