Comments (12)
@viniciussbs hey, thanks for writing this up. Quick question… You said:
Taking a look at #20, it does not solve this problem. I can't create my own observer since
managedWatchQuery
does not return the observable query.
In #20, the ObservableQuery is available on the result object (returned from apollo.watchQuery
) as _apolloObservable
. Were you unaware of that, or does that not give you what you need?
from ember-apollo-client.
Hi, @bgentry.
I wasn't aware of that. I think I can't achieve part of the results with it. I say "part" because I'll still need to handle the deeply frozen result data. This non-extensible result object breaks Ember. A deep copy of the result object solves the problema, but is not straightforward.
I'll test a solution using the branch from #20. Thanks for the tip.
from ember-apollo-client.
Hi, @bgentry. Let me share the results of my tests.
How I solve my problem using #20:
return this.apollo.watchQuery({ query, variables, fetchPolicy }).then((data) => {
data._apolloObservable.subscribe({
next: (result) => {
let model = this.buildModel(copy(result.data, true));
let currentModel = get(this, 'controller.model');
if (currentModel) {
currentModel.setProperties(model);
}
},
error(e) {
if (get(this, 'controller.model')) {
this.send('error', e);
}
}
});
return this.buildModel(data);
});
buildModel()
is just a function to extract the nodes from a Relay connection. Application specific.
Some points:
- I have to check
get(this, 'controller.model')
because the observer is called before the promise is resolved. I guess I could solve this issue storing_apolloObservable
somewhere and subscribing to it insideafterModel()
. - I have to do a deep copy of the result object.
- I have to handle the error case manually.
- I guess I would have to put the side effects inside a
run
call.
How I solve my problem using v0.3.1
:
let observable = get(this, 'apollo.client').watchQuery({ query, variables, fetchPolicy });
return new Ember.RSVP.Promise((resolve, reject) => {
observable.subscribe({
next: (result) => {
let model = this.buildModel(Ember.copy(result.data, true));
let currentModel = get(this, 'controller.model');
if (currentModel) {
currentModel.setProperties(model);
} else {
resolve(model);
}
},
error(e) {
if (get(this, 'controller.model')) {
this.send('error', e);
} else {
reject(e);
}
}
});
});
Some points:
- I have to check
get(this, 'controller.model')
here too, but now as an expected scenario. - I have to do a deep copy of the result object.
- I have to handle the error case manually.
- I guess I would have to put the side effects inside a
run
call. - I have to clean the observer and the subscription by myself.
- It look likes it's more code, but it's just 4 lines longer - the
resolve
andreject
insideelse
blocks.
How I would like to solve my problem:
model() {
// vars ...
return this.apollo.watchQuery({ query, variables, fetchPolicy }).then((data) => {
return this.buildModel(data);
});
},
// It's called only when it exists.
updateModel(model, data) {
model.setProperties(this.buildModel(data));
}
Or maybe:
let updateModel = (model, data) => {
model.setProperties(this.buildModel(data));
};
return this.apollo.watchQuery({ query, variables, fetchPolicy, updateModel }).then((data) => {
return this.buildModel(data);
});
What do you think?
from ember-apollo-client.
@viniciussbs I'm a little confused because it is my understanding that refetchQueries
should work here for i.e. refetching a list to account for a deleted entry. The Apollo Client docs make me think that if you include a query or a query name in refetchQueries
, any subscribers on that query should automatically receive the new result when that updated list is put into the store.
In #20, the function returned by newDataFunc
should get called both when the initial data is loaded and when it is refetched. That function maintains a reference to the object (either an Ember.A
or Ember.Object
) that it returns when it initially resolves the promise. On subsequent calls, that function will update the object using .setProperties()
for an Ember.Object
or .setObjects()
for an Ember.A
. If that's not actually happening, then it's either a bug or a big misunderstanding on my part of how Apollo Client works.
One thing I will say about newDataFunc
is that it's not updating arrays in an efficient way. This goes both for array result sets and for any nested arrays within a result object. So it might be the case that when you get an updated list, Ember re-renders your whole UI because it thinks the entire list has changed (even if only one item disappeared). If that is indeed happening (as I think you mentioned on Slack), it's something we should be able to fix by making newDataFunc
smarter.
Is there another use case you're trying to satisfy here other than deletion from a list?
Some docs that might be useful are the guides for doing this sort of thing react-apollo:
- http://dev.apollodata.com/react/cache-updates.html - especially the
update
part as it relates to updating the cache without refetching - details on
update
- the
fetchMore
section on handling pagination. My understanding here is that when you callfetchMore
on the observable, the providedupdateQuery
gets called. In there you specify how to merge and/or replace the new and previous results. Whatever is returned by that function then gets passed into thenext()
function on the subscriber (newDataFunc
in #20).
I will add that I am already using #20 in my own app and refetchQueries
seems to be working for me as expected when a new item is added to a list. I don't yet have deletion, though, so can't verify if that's somehow different. I should also note that I'm providing query hashes to refetchQueries
, such as { query, variables }
rather than just the name of the query, but as I understand it that shouldn't matter.
from ember-apollo-client.
ah, I'm remembering now that your use case involves doing further manipulation on the result data (such as restructuring the relay association edges into a simple array of related object IDs). Is that the problem here?
I'm wondering if the right way to handle this would be for you to create a model abstraction on top of the result object returned by ember-apollo-client. Essentially you'd do something like MyObject.create({ rawData: apolloResultObject })
and would rely on computed properties within MyObject
to do any transformation you need.
Does that address your use case?
from ember-apollo-client.
Hi, @bgentry. Sorry for the late update. At least I've tested your suggestions.
@viniciussbs I'm a little confused because it is my understanding that refetchQueries should work here for i.e. refetching a list to account for a deleted entry. The Apollo Client docs make me think that if you include a query or a query name in refetchQueries, any subscribers on that query should automatically receive the new result when that updated list is put into the store.
(...)
ah, I'm remembering now that your use case involves doing further manipulation on the result data (such as restructuring the relay association edges into a simple array of related object IDs). Is that the problem here?
Yep, that's the point. The refetchQueries
works, but I need to manipulate the result when there's new data.
In #20, the function returned by newDataFunc should get called both when the initial data is loaded and when it is refetched. That function maintains a reference to the object (either an Ember.A or Ember.Object) that it returns when it initially resolves the promise. On subsequent calls, that function will update the object using .setProperties() for an Ember.Object or .setObjects() for an Ember.A. If that's not actually happening, then it's either a bug or a big misunderstanding on my part of how Apollo Client works.
(...)
I'm wondering if the right way to handle this would be for you to create a model abstraction on top of the result object returned by ember-apollo-client. Essentially you'd do something like MyObject.create({ rawData: apolloResultObject }) and would rely on computed properties within MyObject to do any transformation you need.
Does that address your use case?
Yes, it does. 🤓 I've tested it and it works. It's more like a view class than a model, though.
One thing I will say about newDataFunc is that it's not updating arrays in an efficient way. This goes both for array result sets and for any nested arrays within a result object. So it might be the case that when you get an updated list, Ember re-renders your whole UI because it thinks the entire list has changed (even if only one item disappeared). If that is indeed happening (as I think you mentioned on Slack), it's something we should be able to fix by making newDataFunc smarter.
This is still happening. I can solve this issue using a observer instead of a computed property inside that model/view abstraction that you've suggested.
Thank you for your suggestions.
from ember-apollo-client.
@viniciussbs I'm in a similar situation.
I have a user form broken down into several components and those are scattered across a few nested routes within the 'user' route. All update mutations are handled within the component. However, I'm handling the delete mutation in the root user route (for easier transitions once the user is deleted).
Update / cancel button visibility is based on the 'initial' model vs. the modified model. ie. the buttons only show if the state is different. This is trivial with ember-data since it has attributes built into it to handle this. (I should probably spend time digging into Redux to see if I'm over complicating this).
That all being said, I'm using ember-concurrency tasks for everything related to my mutations and queries (not using Ember promises at all).
My solution to updating the model after any sort of mutation is to have the ember concurrency task (successful) fire off a this.refresh()
on the root user route. I'm not sure if this is any better or worse than the observer / computed property route. Prior to this I was doing a lot of manual property resetting instead of just refreshing the model after a refetchQueries
.
I should note that I'm not doing anything in the before/model/after hooks other than my initial query, so refreshing has no implication there.
Anyhow, my case doesn't seem as complex as yours but thought I'd throw out it there. Also, I know nothing about Relay other than its existence.
from ember-apollo-client.
@dephora I've not used ember-concurrency yet, so I can't tell much about it. If you share some piece of code, that would be great.
I'm handling this situation following similar to Blake's suggestion: controller.model
holds the object returned by this.get('apollo').query
and, every time the query is refetched, controller.model
is updated. All manipulation of the returned data is done on after model, or as computed properties or inside components. Keeping controller.model
as it was returned, though.
Are you using this.get('apollo').query
? It won't work if you are querying calling queryOnce
.
from ember-apollo-client.
@viniciussbs Blake's suggestion sounds better than the route refresh I'm doing.
I have a full users listing which is 'display' only. The individual user route(s) contains the components which handle the mutations (see section-email.js below)
I'm using query
as opposed to queryOnce
.
I'm new to ember-concurrency as well (and apollo as noted earlier) so it's very possible there are issues with what I'm doing here but so far everything seems to be working well.
Users Route
// users.js
model() {
return get(this, 'loadModelTask').perform();
},
setupController(controller, model) {
this._super(...arguments);
set(controller, 'usersModel', model);
},
actions: {
refreshCurrentRoute() {
this.refresh()
},
gotoRoute(id) {
this.transitionTo('main.admin.organization.members.user.account', id)
}
},
loadModelTask: task(function* () {
let query = get(this, 'query');
try {
let modelQuery = yield get(this, 'apollo').query({
query,
fetchPolicy: 'cache-first'
}, 'users');
return modelQuery;
} catch (e) {
// TODO handle
console.log(e)
}
}).restartable(),
});
Individual User Email Component
// section-email.js
id: alias('userModel.id'),
email: alias('userModel.email'),
emailInitial: alias('initialState.email'),
showFormUpdateBtns: notEqual('email', 'emailInitial'),
isSaveDisabled: computed('email', function () {
return Validator.emailFormat(get(this, 'email'));
}),
didReceiveAttrs() {
this._super(...arguments);
set(this, 'initialState', getProperties(this, 'email'));
},
updateMutationTask: task(function* () {
let mutation = get(this, 'mutation');
let notify = get(this, 'notify');
let variables = {
id: parseInt(get(this, 'id')),
email: get(this, 'email'),
};
try {
let updateMutation = yield get(this, 'apollo').mutate({
mutation,
variables,
}, 'updateUser');
notify.success(`Email successfully updated to ${updateMutation.email}.`);
this.refreshRoute(); // passed in route-action
} catch (e) {
// console.info('Error', e);
// TODO handle
notify.error(`There was a problem updating the user's email address.`, {
closeAfter: null
});
}
}).drop(),
// resetTask currently isn't doing async operations but might in the future
resetTask: task(function* () {
let notify = get(this, 'notify');
yield timeout(1); // this yield is unnecessary - testing
set(this, 'email', get(this, 'emailInitial'));
notify.warning(`Email reset: no changes made.`);
}).drop(),
});
from ember-apollo-client.
@dephora you have a route that list users and another route to see an individual user, right? If I'm right, that may be the reason why refetchQueries
isn't working.
In order to use refetchQueries
, the query to be refetched needs to be active, still in observation. If your index route extends UnsubscribeRoute
mixin, than Ember will unsubscribe the query after a route transition. So you won't have the query to be refetched.
You can check active query subscriptions using Apollo Client Developer Tools or calling __APOLLO_CLIENT__.queryManager.observableQueries
and __APOLLO_CLIENT__.queryManager.queryDocuments
from console.
In my app I always use fetchPolicy: 'network-only'
in my index routes. That's why I don't have this problem when I'm coming from another route.
from ember-apollo-client.
@viniciussbs Right, sorry for the confusion. I was shifting my users / user routes around and stopped using the refetch on my user route as it wasn't absolutely necessary for how I was handling the updates. I too was using network-only
but I'm still experimenting. My users listing isn't a big deal for now so a fetchPolicy: network-only
doesn't hurt but I will have an image listing that will be far larger and I haven't yet messed with pagination (or auth, or file uploads).
Anyhow, appreciate the feedback and suggestions. I still need to work in the computed property model.
from ember-apollo-client.
Since the solution suggested by @bgentry solves the problem, I'm closing the issue.
from ember-apollo-client.
Related Issues (20)
- Subscription implementation questions HOT 2
- ember install ember-apollo-client broken
- ember-simple-auth example is out of date HOT 2
- Disable "Download the Apollo DevTools for a better development experience" warning HOT 2
- TS errors due to no react present HOT 1
- Cache is updating but computed property is not. HOT 2
- ReferenceError: react is not defined HOT 3
- Question: Ember Apollo Client and file uploads HOT 1
- Error using typescript HOT 1
- Can't build app due to dependencies placement in package.json
- Add a service override environment variable
- Build error when using Embroider HOT 3
- Update watchQuery results in the next runloop
- Could not find module `@apollo/client/core` imported from `ember-apollo-client/services/apollo` HOT 1
- Out of date client.
- @defer support HOT 1
- V4 service extension / v4 fails on ember 3.28 HOT 1
- Much bigger bundle size after upgrading @apollo/client HOT 1
- Define the types of the parameters from Apollo service's most used methods
- Service implementation question HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ember-apollo-client.