Observe Ember Data store changes in component - ember.js

I have a component which creates record for a specific model like this:
export default Ember.Component.extend({
store: Ember.inject.service(),
addRecord(account) {
this.get('store').createRecord('update', {
authUid: account.get('authUid'),
service: account.get('platform')
});
}
});
I have another component that needs to observe changes done to a particular model (i.e. if records are added or deleted), and show them in that component.
export default Ember.Component.extend({
store: Ember.inject.service(),
observeStoreChanges: /*What should I write so that every time `addRecord`
pushes record in the store, a function is executed in this component*/
});

If you're a fan of the observer pattern:
// store.js
export default DS.Store.extend(Ember.Evented, {
createRecord() {
const record = this._super.apply(this, arguments);
this.trigger('recordCreated', record);
return record;
}
});
// component.js
export default Ember.Component.extend({
observesStoreChanges: function(record) {
}.on('store.recordCreated')
});

Related

Share service variable between controllers

I have a variable in a service that I want two share between two controllers.
As I know, Ember services are singleton.
I created a service file: app/services/data-manipulation.js:
import Ember from "ember";
export default Ember.Service.extend({
init() {
console.log('initService');
},
shouldManipulate: false
});
In first controller that is called one screen before the second controller:
dataManipulation: Ember.inject.service(),
init() {
this.updateManipulate();
},
updateManipulate: function() {
this.set("dataManipulation.shouldManipulate", true);
var currentValue = this.get("dataManipulation.shouldManipulate");
console.log(currentValue); // log true as expected
}
In second controller:
dataManipulation: Ember.inject.service(),
init() {
// it inits the service again so 'initService' is logged again.
var currentValue = this.get("dataManipulation.shouldManipulate");
console.log(currentValue); // log undefined
}
What is the problem and how can I make it works?

Use Mixin property in a Controller

This is a crappy example, but I am merely trying to use a mixin's property in a controller. I did the same thing in a route and could access that property. I've tried every way to reference a property I know... What am I misunderstanding?
// app/mixins/author-data.js
import Ember from 'ember';
export default Ember.Mixin.create({
authorName: 'Example author name',
});
// app/controllers/application.js
import Ember from 'ember';
import AuthorDatas from 'app-name/mixins/author-data';
export default Ember.Controller.extend(AuthorDatas, {
siteTitle: `Site title`,
fromAuthorData: this.get('authorName'),
// returns 💩 - what is the proper syntax?
actions: {
showAuthor() {
var author = this.get('fromAuthorData');
console.log(`Author from controller: ${author}`);
},
},
});
// app/templates/application.hbs
{{fromAuthorData}}
This works...
// app/routes/application.js
import Ember from 'ember';
import AuthorDatas from 'app-name/mixins/author-data';
export default Ember.Route.extend(AuthorDatas, {
afterModel() { // arbitrary
var intro = `Author from route:`;
console.log(`${intro} this.authorName`, this.authorName );
console.log(`${intro} this.get('author-name')`, this.get('authorName') );
},
});
(I would have made an ember-twiddle - but I wasn't sure if Mixins would work the same way ~ since they aren't on the list and there is 0 documentation)
The fromAuthorData property on your controller should be defined like this (I think):
fromAuthorData: Ember.computed('authorName', function() {
return this.get('authorName'); // or whatever derived value you need
}
To understand the problem we need to talk about scope, when you extend/create an object you are merely passing in options, your code is no different than:
let options = {
siteTitle: `Site title`,
// `this` is undefined since we are in strict mode
fromAuthorData: this.get('authorName'),
actions: {
showAuthor() {
var author = this.get('fromAuthorData');
console.log(`Author from controller: ${author}`);
},
}
};
export default Ember.Controller.extend(AuthorDatas, options);
Now to access properties that rely on this being the object holding it you will need a function that is run with the object as it's context that returns that value, enter computed properties.
Your code becomes:
// app/controllers/application.js
import Ember from 'ember';
import AuthorDatas from 'app-name/mixins/author-data';
const { computed } = Ember;
export default Ember.Controller.extend(AuthorDatas, {
siteTitle: `Site title`,
// We add `authorName` as the dependent key, should it change `fromAuthorData` will update
fromAuthorData: computed('authorName', function() {
// your author data stuff
let authorName = this.get('authorName');
// ...
return authorDetails;
}),
actions: {
showAuthor() {
var author = this.get('fromAuthorData');
console.log(`Author from controller: ${author}`);
},
},
});

Ember : fetch data afterwards

I get some data from my API through model in route.js. This data contains somewhere an id on its own, with no relationships or included stuff to get details. So I have to make another API request to get the object with that id.
I did it with a component (to be able to send the id argument) and it works, but I would like to know if that's the way to go and if so, if I did it right and it cannot be simplified (because it looks complex to me for such a simple task):
I call my component with {{resource-name id=evaluation.manager}}
Component template just contains {{name}}
component.js:
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
_getResource(id) {
return this.get('store').findRecord('resource', id);
},
resource: Ember.computed('id', function() {
const id = this.get('id');
const proxy = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin);
return proxy.create({
promise: this._getResource(id)
});
}),
name: Ember.computed('resource.isFulfilled', function() {
if (this.get('resource.isFulfilled')) {
return `${this.get('resource.lastName')} ${this.get('resource.firstName')}`;
}
else {
return "...";
}
}),
didReceiveAttrs() {
const id = this.getAttr('id');
Ember.assert('resource-name must have an "id" attribute!', !Ember.isBlank(id));
}
});

How to pass errors in template from route?

I do 'server side validation'. In route in method 'catch' get errors from server. And I want pass this errors in template.
How to pass errors in template from route?
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
model() {
return this.store.createRecord('project');
},
actions: {
save(project) {
var router = this;
var errors = router.controllerFor('project.new').get('errors')
project.save().then(function(project){
router.transitionTo('projects.show', project);
}).catch(function(resp) {
// how to pass this errors in template ????
console.log(resp.errors);
});
}
},
});
From router.js
this.route('projects', function() {
this.route('new');
this.route('show', { path: '/:project_id' });
});
From Component
import Ember from 'ember';
import DS from 'ember-data'
export default Ember.Component.extend({
actions: {
save() {
this.project.set('colors', colors);
this.sendAction('save');
}
},
...
});
Closure Actions! (Assuming a recente version of - Ember 1.13+). Closure actions can have a return value, unlike regular actions.
On your template you do:
{{my-component mySave=(action 'save')}}
And in your component you do
import Ember from 'ember';
import DS from 'ember-data'
export default Ember.Component.extend({
actions: {
save() {
this.project.set('colors', colors);
let result = this.attrs.mySave();
//do something with result
}
},
...
});
And then in your controller:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Controller.extend({
actions: {
save(project) {
var router = this;
var errors = router.controllerFor('project.new').get('errors')
project.save().then(function(project){
router.transitionTo('projects.show', project);
}).catch(function(resp) {
return resp.errors;
});
}
},
});
I would also recommend this article on Closure Actions which is very helpful.
EDIT: I initially replied with the action being on the route (as in your example) but #Kitler correctly reminded that closure actions communicate with the controller or another component. I don't know if that's an option for the OP?

Ember .save() only saving one attribute

I am trying to create and save a model in Ember but only the first entry in my form is saving leaving the others blank.
Before saving all of the models attributes are correct. (shown with logs)
Any help greatly appreciated!
My Model is:
import DS from 'ember-data';
export default DS.Model.extend({
endpoint: DS.attr('string'),
playerVersion: DS.attr('string'),
createdAt: DS.attr('date'),
updatedAt: DS.attr('date')
});
My Route is:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('account');
}
});
My Controller is:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
save: function() {
var _this = this;
var model = this.get('model');
console.log(model.get('endpoint')); //Shows correct endpoint
console.log(model.get('playerVersion')); //Shows correct playerVersion
model.save().then(function(account) {
console.log(model.get('endpoint')); //Shows correct endpoint
console.log(model.get('playerVersion')); //Shows nothing
_this.transitionToRoute('accounts.index');
});
return false;
}
}
});
UPDATE - Was some settings needed on the custom Serializer needed.
export default JsonApiSerializer.extend({
keyForAttribute: function(key) {
//return Ember.String.dasherize(key);
return Ember.String.camelize(key);
},
keyForRelationship: function(key) {
//return Ember.String.dasherize(key);
return Ember.String.camelize(key);
},
keyForSnapshot: function(snapshot) {
//return Ember.String.dasherize(snapshot.typeKey);
return Ember.String.camelize(snapshot.typeKey);
},
});