How to remove and object from an array in Ember - ember.js

I do not understand why the object is not removed from the array? I tried to implement this solution Ember.js how to remove object from array controller without ember data but, the object is not removed from the previousParent.
changeCauseLinkParentRt(newParent, causeLink) {
if(!newParent) {
causeLink.get("parent").then(previousParent => {
previousParent.get("children").then(children =>{
var _newParent = children.findBy("order", causeLink.get("order")-1);
if(!!_newParent) {
var objRemove = null;
previousParent.get("children").then(children => {
objRemove = children.findBy("id", causeLink.get("id"));
children.removeObject(objRemove);
children.save();
causeLink.set("parent", _newParent);
causeLink.save();
_newParent.get("children").pushObject(causeLink);
_newParent.save();
});
}
});
})
} else {
//...
}
}

I found that Ember-Data (or the Firebase adapter) is returning a duplicate of the same record in some cisconstances. So removeObject() was deleting once instance the of the duplicate the record with the final result that the remaining other version was persisted back to the backend.

Related

Apollo client mutation with writeQuery not triggering UI update

I have a mutation to create a new card object, and I expect it should be added to the user interface after update. Cache, Apollo Chrome tool, and console logging reflect the changes, but the UI does not without a manual reload.
const [createCard, { loading, error }] = useMutation(CREATE_CARD, {
update(cache, { data: { createCard } }) {
let localData = cache.readQuery({
query: CARDS_QUERY,
variables: { id: deckId }
});
localData.deck.cards = [...localData.deck.cards, createCard];
;
client.writeQuery({
query: CARDS_QUERY,
variables: { id: parseInt(localData.deck.id, 10) },
data: { ...localData }
});
I have changed cache.writeQuery to client.writeQuery, but that didn't solve the problem.
For reference, here is the Query I am running...
const CARDS_QUERY = gql`
query CardsQuery($id: ID!) {
deck(id: $id) {
id
deckName
user {
id
}
cards {
id
front
back
pictureName
pictureUrl
createdAt
}
}
toggleDeleteSuccess #client
}
`;
I managed the same result without the cloneDeep method. Just using the spread operator solved my problem.
const update = (cache, {data}) => {
const queryData = cache.readQuery({query: USER_QUERY})
const cartItemId = data.cartItem.id
queryData.me.cart = queryData.me.cart.filter(v => v.id !== cartItemId)
cache.writeQuery({query: USER_QUERY, data: {...queryData}})
}
Hope this helps someone else.
Ok, finally ran into a long Github thread discussing their solutions for the same issue. The solution that ultimately worked for me was deep cloning the data object (I personally used Lodash cloneDeep), which after passing in the mutated data object to cache.writeQuery, it was finally updating the UI. Ultimately, it still seems like there ought to be a way to trigger the UI update, considering the cache reflects the changes.
Here's the after, view my original question for the before...
const [createCard, { loading, error }] = useMutation(CREATE_CARD, {
update(cache, { data: { createCard } }) {
const localData = cloneDeep( // Lodash cloneDeep to make a fresh object
cache.readQuery({
query: CARDS_QUERY,
variables: { id: deckId }
})
);
localData.deck.cards = [...localData.deck.cards, createCard]; //Push the mutation to the object
cache.writeQuery({
query: CARDS_QUERY,
variables: { id: localData.deck.id },
data: { ...localData } // Cloning ultimately triggers the UI update since writeQuery now sees a new object.
});
},
});

How to get the changed object of an array when using ember oberver

I am using following code to get the details of the object whose property is changed.
getChangedObject:function(){
console.log('changed name is' , can i get the object here );
}.observes('model.#each.rollNo')
Thanks in advance.
Inside observer you cant get the exact object which caused observer to trigger, but you can try the below approach, like iterating array of model and watch for changed attributes alone.
getChangedObject: function() {
let model = this.get('model');
model.forEach(function(item, index) {
var changedAttributes = item.changedAttributes();
item.eachAttribute(function(item, meta) {
var temp = changedAttributes[item];
if(temp){
console.log(' old value ',temp[0],' new value ',temp[1]);
}
});
})
}.observes('model.#each.rollNo')

Ember - Within action, result is defined, returnvalue of same action logged in parent action is undefined? Why?

Quick and shortly I have following problem:
I have following two actions within a component in Ember:
createData: function(user) {
let collection = [];
for (let i = 0; i < user.posts.length; i++) {
let data = this.send('createSingleData',user.posts[i], user, 'post');
console.log(data);
collection.push(data);
}
return collection;
},
createSingleData: function(data, user, type) {
let entitySkeleton = {
name: data.place.name,
belongsTo: user.id,
position: {
data.place.location.longitude,
data.place.location.latitude
}
};
console.log(entitySkeleton);
return entitySkeleton;
}
the first log - within createSingleData, right before returning the logged value - writes the entitySkeleton as Object into the console - as expected.
However, the console.log(data) - within createData - writes 'undefined' to the console.
Is there any aspect of asynchrounosity I didn't respect?
P.S.:
I also logged any paramater within createSingleData, they are all set properly.
The variable collection also only gets pushed 'undefined'.
You cannot return the value from action, instead you can set property from the action.
how to return values from actions in emberjs
actions: {
PrintSomething: function() {
let obj = [{a: 'raj'}, {a: 'Prudvi'}, {a : 'thimappa'}]
console.log('before', obj);
this.send('returnSomething', obj);
console.log('after calling action', this.get('returnvalue'));
},
returnSomething: function(obj) {
obj.push({a: 'FSDFSDF'})
var data = obj;
this.set('returnvalue', data);
}
}

How to properly setup a store that acts as a single pointer across your web app

I have a home grown store that has a simple identityMap. When I return an array of models from this and bind it to a controllers "model" it reflects what you'd expect
the first time you hit a route it reflects this in the template as
you'd expect
But later if I get this same store instance (it's a singleton) and push an object into the identityMap it doesn't automatically update the previous template
The store itself is super basic (no relationships/ just push objects and get by id)
function buildRecord(type, data, store) {
var containerKey = 'model:' + type;
var factory = store.container.lookupFactory(containerKey);
var record = factory.create(data);
var id = data.id;
identityMapForType(type, store)[id] = record;
return record;
}
function identityMapForType(type, store) {
var typeIdentityMap = store.get('identityMap');
var idIdentityMap = typeIdentityMap[type] || {};
typeIdentityMap[type] = idIdentityMap;
return idIdentityMap;
}
var Store = Ember.Object.extend({
init: function() {
this.set('identityMap', {});
},
push: function(type, data) {
var record = this.getById(type, data.id);
if (record) {
record.setProperties(data);
} else {
record = buildRecord(type, data, this);
}
return record;
},
getById: function(type, id) {
var identityMap = identityMapForType(type, this);
return identityMap[id] || null;
}
getEverything: function(type) {
var identityMap = identityMapForType(type, this);
var keys = Object.keys(identityMap);
var values = [];
for (var i = 0; i < keys.length; i++)
{
var val = identityMap[keys[i]];
values.push(val);
}
return values;
}
});
Ember.onLoad('Ember.Application', function(Application) {
Application.initializer({
name: "store",
initialize: function(container, application) {
application.register('store:main', Store);
application.inject('controller', 'store', 'store:main');
application.inject('route', 'store', 'store:main');
}
});
});
In my model hook (in the find all route lets say) I simply query for each item and push them into the store
//inside my model find method lets say ...
find: function(store) {
var url = "/api/foo";
$.getJSON(url, function(response) {
response.forEach(function(data) {
var model = store.push("foo", data);
}
}
return store.getEverything("foo");
}
So I assumed my controllers' model was this bound array (using a single pointer in memory for this array of models)
Yet when I do this inside a controller submit action it won't re-render that prev view (to show the new item that was added to that store's array)
actions: {
submit: function() {
var foo = {}; // assume this is a real json response or js object
var store = this.get("store");
store.push("foo", foo);
}
}
Because of this today, I'm forced to get the parent controller and "set" / "push" this new object to it's content/model property :(
Anyone know what I'm doing wrong here?
I like homegrown solutions, they generally are easier to work with and meld around what you're working on.
So I'm actually surprised this part is working:
//inside my model find method lets say ...
find: function(store) {
var url = "/api/foo";
$.getJSON(url, function(response) {
response.forEach(function(data) {
var model = store.push("foo", data);
}
}
return store.getEverything("foo");
}
If I read through it I see you make an ajax call, and then return store.getEverything immediately after (without a guarantee that the ajax call has completed). Then inside of getEverything you create a new array called values then iterate the identity map linking up all of the currently available records and return that. At this point your store is unaware of this array going forward. So any changes to your store wouldn't get pushed out to the array, they might make it into the identity map, but it isn't feeding the getEverything array.
There are a couple of solutions, one would be to keep track of your everything array. That collection would be super cheap to build, more expensive to search, so keeping the identity map as well would be super beneficial. You could follow your same pattern, but one collection would be the map, whereas the other would be an array of everything.
Modified Build Record
function buildRecord(type, data, store) {
var containerKey = 'model:' + type;
var factory = store.container.lookupFactory(containerKey);
var record = factory.create(data);
var id = data.id;
identityMapForType(type, store)[id] = record;
everythingArrayForType(type, this).pushObject(record);
return record;
}
Copy paste, possibly could be refactored
function everythingArrayForType(type, store) {
var everythingArrays = store.get('everythingArrays');
var arr = everythingArrays[type] || [];
everythingArrays[type] = arr;
return arr;
}
Slightly modified Store
var Store = Ember.Object.extend({
init: function() {
this.set('identityMap', {});
this.set('everythingArrays', {});
},
push: function(type, data) {
var record = this.getById(type, data.id);
if (record) {
record.setProperties(data);
} else {
record = buildRecord(type, data, this);
}
return record;
},
getById: function(type, id) {
var identityMap = identityMapForType(type, this);
return identityMap[id] || null;
}
getEverything: function(type) {
return everythingArrayForType(type, this);
}
});

Ember.js global routed/transitioned events

Is it possible to subscribe to all transition events in the application? Or alternatively some observable property containing the current route?
I'm integrating with a third-party UI component that needs to be synchronized to the current route.
The application controller has a currentRouteName property, as explained here. It's mostly for debugging, but I imagine that it's a fairly stable property that could be used in production.
EDIT: If you need to be alerted of all changes, use the hashchange event like Ember does internally. This will only work if you're using hash based routing though. If you're using Ember's history API based routing, you'll have to use that.
In your app_controller you can add this snippet which fires on every path/route change
currentPathDidChange: function currentPathDidChange() {
var path = this.get('currentPath')
}.observes('currentPath')
I solved this by hooking into Router.didTransition
Live example: http://jsbin.com/yuzedacu/5/edit (modified the example found here)
App.Router.reopen({
updateCurrentRoute: function(infos) {
var appController = this.container.lookup('controller:application');
if (!('currentRoute' in appController)) {
Ember.defineProperty(appController, 'currentRoute');
}
if (infos && infos.length > 0) {
// The last part of the route contains the route name
var route = infos[infos.length - 1].name;
// Collect the dynamic route parameters
var params = infos.reduce(function(a, b) {
// Parameter can be named anything
// assume there are 0 or 1 parameters
for (var name in b.params) {
if (b.params.hasOwnProperty(name)) {
// 1 parameter
return a.concat(b.params[name]);
}
}
// 0 parameters
return a;
}, []);
var path = [route].concat(params);
Ember.set(appController, 'currentRoute', path);
} else {
Ember.set(appController, 'currentRoute', []);
}
},
didTransition: function(infos) {
this.updateCurrentRoute(infos);
return this._super(infos);
}
});