{"action":"created","issue":{"url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/issues/156","labels_url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/issues/156/labels{/name}","comments_url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/issues/156/comments","events_url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/issues/156/events","html_url":"https://github.com/strongloop/loopback-sdk-angular/issues/156","id":88540554,"number":156,"title":"Support Collections and LiveSets","user":{"login":"ritch","id":462228,"avatar_url":"https://avatars.githubusercontent.com/u/462228?v=3","gravatar_id":"","url":"https://api.github.com/users/ritch","html_url":"https://github.com/ritch","followers_url":"https://api.github.com/users/ritch/followers","following_url":"https://api.github.com/users/ritch/following{/other_user}","gists_url":"https://api.github.com/users/ritch/gists{/gist_id}","starred_url":"https://api.github.com/users/ritch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ritch/subscriptions","organizations_url":"https://api.github.com/users/ritch/orgs","repos_url":"https://api.github.com/users/ritch/repos","events_url":"https://api.github.com/users/ritch/events{/privacy}","received_events_url":"https://api.github.com/users/ritch/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/labels/%23sprint73","name":"#sprint73","color":"ededed"}],"state":"open","locked":false,"assignee":{"login":"ritch","id":462228,"avatar_url":"https://avatars.githubusercontent.com/u/462228?v=3","gravatar_id":"","url":"https://api.github.com/users/ritch","html_url":"https://github.com/ritch","followers_url":"https://api.github.com/users/ritch/followers","following_url":"https://api.github.com/users/ritch/following{/other_user}","gists_url":"https://api.github.com/users/ritch/gists{/gist_id}","starred_url":"https://api.github.com/users/ritch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ritch/subscriptions","organizations_url":"https://api.github.com/users/ritch/orgs","repos_url":"https://api.github.com/users/ritch/repos","events_url":"https://api.github.com/users/ritch/events{/privacy}","received_events_url":"https://api.github.com/users/ritch/received_events","type":"User","site_admin":false},"milestone":null,"comments":2,"created_at":"2015-06-15T21:03:13Z","updated_at":"2015-06-18T06:53:14Z","closed_at":null,"body":"This depends on:\r\n - Collections and LiveSets https://github.com/strongloop/loopback/issues/1418\r\n - Angular SDK Change Stream support https://github.com/strongloop/loopback-sdk-angular/issues/155\r\n\r\nBelow is a more detailed proposal. I think we should build this outside the **loopback-sdk-angular** repo / module.\r\n\r\n```js\r\n// basic usage\r\nfunction myCtrl(MyModel, LiveSet) {\r\n  var options = ...; // could include things like filter\r\n  var set = new LiveSet(MyModel, options);\r\n  $scope.items = set.toLiveArray();\r\n}\r\n```\r\n\r\nHere is some very basic high level code that illustrates the important details.\r\n\r\n - using `EventSource` to consume the `ChangeStream`\r\n - need to be able to hook into `AngularPersistedModel`\r\n - need to get and add `AccessToken` to the `EventSource` url\r\n\r\n```js\r\n// in a separate loopback-angular-live-set module / repo\r\nfunction LiveSet(PersistedModel, options) {\r\n  var set = this;\r\n  var url = getApiUrl(PersistedModel);\r\n  var modelUrl = getModelUrl(PersistedModel);\r\n  var options = stringifyAsQs(getOptions(options));\r\n  var fullUrl = url + '/' + modelUrl + '/createChangeStream?' + options;\r\n  var accessToken = getToken();\r\n  if (accessToken) {\r\n    fullUrl += '&access_token=' + accessToken;\r\n  }\r\n  fullUrl += '&_format=event-source';\r\n\r\n  var es = this.eventSource = new EventSource(fullUrl);\r\n\r\n  // when someone calls a PersistedModel method\r\n  // that modifies something (insert, update, delete)\r\n  this._hookPersistedModel(PersistedModel);\r\n\r\n  // apply event stream changes\r\n  es.on('data', function(e) {\r\n    var change = JSON.parse(d.data);\r\n\r\n    // applyChange needs to take into account\r\n    // when a outgoing change conflicts with an incoming change\r\n    // it should ignore the incoming change and after the\r\n    // outgoing change completes, refresh the model in question\r\n    set.applyChange(change);\r\n  });\r\n}\r\n\r\n// called when a PersistedModel method is called\r\n// before it completes\r\nLiveSet.prototype._beforeModify = function() {\r\n  // save the current version\r\n  // apply the modification locally\r\n}\r\n\r\nLiveSet.prototype._modificationSuccess = function() {\r\n  // unref the saved previous version\r\n}\r\n\r\nLiveSet.prototype._modificationError = function() {\r\n  // replace the modified version with the \r\n  // saved previous version\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/issues/comments/113055263","html_url":"https://github.com/strongloop/loopback-sdk-angular/issues/156#issuecomment-113055263","issue_url":"https://api.github.com/repos/strongloop/loopback-sdk-angular/issues/156","id":113055263,"user":{"login":"bajtos","id":1140553,"avatar_url":"https://avatars.githubusercontent.com/u/1140553?v=3","gravatar_id":"","url":"https://api.github.com/users/bajtos","html_url":"https://github.com/bajtos","followers_url":"https://api.github.com/users/bajtos/followers","following_url":"https://api.github.com/users/bajtos/following{/other_user}","gists_url":"https://api.github.com/users/bajtos/gists{/gist_id}","starred_url":"https://api.github.com/users/bajtos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bajtos/subscriptions","organizations_url":"https://api.github.com/users/bajtos/orgs","repos_url":"https://api.github.com/users/bajtos/repos","events_url":"https://api.github.com/users/bajtos/events{/privacy}","received_events_url":"https://api.github.com/users/bajtos/received_events","type":"User","site_admin":false},"created_at":"2015-06-18T06:53:14Z","updated_at":"2015-06-18T06:53:14Z","body":"This looks great! I especially like that we will reuse an existing standard (EventSource) instead of trying to invent our own solution.\r\n\r\n> I think we should build this outside the loopback-sdk-angular repo / module\r\n\r\n+1, small independent modules FTW.\r\n\r\nHaving said that, creating a new project adds extra overhead. As an alternative, we could keep this new feature in a standalone source file living in this repository. Conceptually, keep the implementation as in two modules, but package them in a single npmjs package.\r\n\r\nI think versioning is the most important factor affecting the decision. If we want to version LiveSet implementation independently from the Angular SDK, then it should live in its own module.\r\n\r\nI am ok with both ways.\r\n\r\n> need to get and add AccessToken to the EventSource url\r\n\r\nYou should be able to get the access token via `LoopBackAuth` service and its property `accessTokenId`, see [services.template.ejs](https://github.com/strongloop/loopback-sdk-angular/blob/e7dde54e81fe19e8d9c60a4515f03e23321021e8/lib/services.template.ejs#L329-L330).\r\n\r\n> need to be able to hook into AngularPersistedModel\r\n\r\nThis may be tricky to implement in the right way, without introducing coupling between a LiveSet and a Model resource.\r\n\r\nHere is one possible solution: modify the current code-generator to add a `response interceptor` to selected CRUD methods (create, $save, etc.), this interceptor should emit a generic event e.g. `changed`. The LiveSet can install a listener for this event. See \"actions\" param in [docs](https://docs.angularjs.org/api/ngResource/service/$resource#usage) for more details about the interceptor.\r\n\r\n> ```\r\nfullUrl += '&_format=event-source'\r\n```\r\n\r\nI like this idea. \r\n\r\nPerhaps we can modify the current streaming implementation in strong-remoting to support different response formats? At the moment, we use demux stream (like `&_format=demux-json-newline`). If response format selection turns out to be difficult to implement, then I think it's ok to hard-code the response format in the stream arg description (e.g. replace `json: true` with `event-stream: true`) and force all consumers of `createChangeStream` to use EventSource protocol instead of demux. Anyways, that's just me thinking aloud :)"}}