I have a simple question. Whats the main difference doing a MyModel.beforeRemote('create') hook for a create method and a MyModel.observe('before save'). I already read the docs and I know that operation hooks are not tied to a particular method, but rather are triggered from all methods that execute a particular high-level operation (ex. create). But in this particular example, MyModel.beforeRemote('create') will work as same as I do MyModel.observe('before save'), right? Or this will execute on other "state" of the api flow?
Remote hook:
MyModel.beforeRemote('create', (ctx, next) => {
console.log("beforeRemote");
next();
}
Operation hook:
MyModel.observe('before save', (ctx, next) => {
console.log("before save");
next();
}
MyModel.beforeRemote('create') would only be invoked for the 'create' remote method, but MyModel.observe('before save') would be invoked for any of these:
create
upsert
findOrCreate
updateAll
prototype.save
prototype.updateAttributes
See the table here for all the remote methods that would invoke each operation hook: https://docs.strongloop.com/display/APIC/Operation+hooks
Related
I have a use case where the action should get the value from another component, based on that I need to do some actions.
Initially, I used sendAction (instead of promiseAction()) to do some actions. But closeDataModal() runs immediately after finishing the sendAction. I want the first function to finish up and then run the second one.
saveAction() {
promiseAction()
closeDataModal() -> Run after resolving the promiseAction
}
Use an async function so that you can await an async operation like an async action.
async saveAction() {
await promiseAction()
closeDataModal() -> will run after resolving the promiseAction
}
If you want to use the result of promiseAction then:
async saveAction() {
let result = await promiseAction()
closeDataModal(result) -> will run after resolving the promiseAction
}
As mentioned in the comments, this will not work with sendAction which is deprecated.
See https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/async_function for more infos on how async/await works and how error handling is done.
I'm working with emberjs during some time and now I want to refine my understanding of ember's nature.
Question 1.
Am I right that the route's model() hook is the best place to write asynchronous code (to get all needed data)? Should I try to place all my network requests (or at least most of them) inside routes' models?
Question 2.
The component hooks have synchronous nature. Does that mean it's a bad decision to write async code inside hooks?
Say, I have async init() hook where some data is calculated and didRender() hook where I expect to use that data. When ember calls init() it returns a Promise, so it's moved from a stack to a special queue and ember doesn't wait until event loop returns that code back to a stack. So ember runs next hooks, and when didRender() is being executed the init() hook may not be fulfilled and the expected data may not exist. Is that right?
Question 3.
Services hooks should also be synchronous. Because when a service is injected inside a component and is used ember also doesn't wait until the async hook is fulfilled.
Say, I have a shopping cart service with products property. The products ids are stored in localstorage and I want to get those products from a server to set them into products property.
import Service from '#ember/service';
import { A } from '#ember/array';
import { inject as service } from '#ember/service';
export default Service.extend({
store: service(),
init(...args) {
this._super(args);
this.set('products', A());
},
async loadProducts() {
const cartProducts = A();
const savedCartProducts = localStorage.getItem('cartProducts');
if (savedCartProducts) {
const parsedSavedCartProducts = JSON.parse(savedCartProducts);
const ids = Object.keys(parsedSavedCartProducts);
if (ids.length > 0) {
const products = await this.store.query('product', {id: Object.keys(parsedSavedCartProducts)});
products.forEach(p => {
p.set('cartQuantity', Number(parsedSavedCartProducts[p.id].qty));
cartProducts.pushObject(p);
});
}
}
this.products.pushObjects(cartProducts);
},
...
});
If I call loadProducts() from service's init() I can't use this.cart.products in controllers/application.js, for example. Because service is ready, but async init() is still executed. So, should I call it in routes/application.js model() hook?
Question 4.
If there is some general component that doesn't refer to any route, but this component needs some data from a server where should I make async requests? Or are computed properties and observers are the only solutions?
Thanks a lot.
Good questions here, you’re on the right track!
Question 1: yes, the general rule of thumb is that until you are familiar with things, doing async work in the route hooks (beforeModel, model and afterModel) make it easier to think about what is going on. Eventually you may want to start bending the defaults to suit your custom UI needs, but it’s simplest to start with this rule
Questions 2-4: you’re asking several questions here about async code, so will answer here more broadly.
First off, services can do async or not but unless you call those methods from a hook (like a route hook) you are responsible for handling the asynchronous results. For instance, the Ember Data store is a service that does return data asynchronously
But if you hit cases where you do need to do async in a component, the recommended addon to help with that is Ember Concurrency: http://ember-concurrency.com/docs/introduction/
Ember Concurrency helps solve many of the async bugs and edge cases that you haven’t yet hit, but will, if you start doing async code in components. So I’d highly recommend learning more about it.
Good luck!
I am trying to do the following thing:
I have a model, say myModel which has some method calculateSomething. I defined that function by writing something like this in the MyModel.js file:
MyModel.prototype.calculateSomething = function(cb){
...
return cb(null,result)
}
Now I want to include the result of calculateSomething in the json whenever an instance of MyModel is returned from the api.
How do I do this? I tried using the "loaded" hook, but I believe this hook gets executed before the MyModel instance is created, so I can't call the calculateSomehing method there.
EDIT: It turns out that I can just use the "loaded" hook. I can use the ctx.instance in the hook to get the object.
I was confused by the documentation :
"LoopBack invokes this hook after the connector fetches data, but before creating a model instance from that data". Is the documentation wrong or am I misunderstanding it?
How about using Remote Hooks (on mymodel.js):
// run before any static method eg. MyModel.find
MyModel.beforeRemote('*', function(ctx, myModel, next) {
myModel.calculateSomething(function(err, something) {
if (err) throw err
myModel.something = something
next()
})
});
OR
If you need to do it on object initialization phase (while operation hook loaded seems to be not working) maybe you can try the model hook afterInitialize assuming no async call invoked from calculateSomething:
MyModel.afterInitialize = function() {
this.something = this.calculateSomething(function(err, result) {
return result
})
}
OR
As discussed below, if you need do async call and/or want to have this logic on subclasses, I think you should consider implementing createSomething not as object/prototype method but as a mixins. I haven't tried this personally though but it looks quite suitable to your need.
I'm trying out operation hooks http://docs.strongloop.com/display/public/LB/Operation+hooks
Here is what I did in app code:
mymodel.observe('before save', doSomething);
//after some time elapses or based on an event we want to change the behaviour
mymodel.observe('before save', doSomethingElse);
var doSomething = function (ctx, next) {
//do something
next();
};
var doSomethingElse = function (ctx, next) {
//do something else
next();
};
When I test this code I find that always doSomething is executed which makes me wonder if the observer function can be registered only once per model or is it a bug?
If it is as per design, could you please tell the reason behind it?
Disclaimer: I am a core developer of LoopBack and the author of Operation hooks.
after some time elapses or based on an event we want to change the behaviour
The Operation hooks do not support unregistering of handler functions yet. Each call of observe() adds the handler to the list of methods invoked when a hook is triggered.
When I test this code I find that always doSomething is executed which makes me wonder if the observer function can be registered only once per model or is it a bug?
You can register multiple observers. Once you have registered doSomething, it will be always called. When you register doSomethingElse, it will be called too, after doSomething returns via next().
You can now unregister all observers with the clearObservers method and remove a single observer with the removeObserver method. See the ObserverMixin documentation.
According to the changelog, this feature was added in version 2.23.0 of the datasource juggler.
I have a situation where as soon as app starts i have to call a web service to post unsaved data back to the server while i keep on accessing the app. I should be able to navigate to different views, perform UI tasks.
I can use Task
Task CallWebService()
{
return Task.Factory.StartNew(() => {
// make your service call.
});
}
CallWebService().ContinueWith(task => {
if(task.isFaulted)
throw new AggregateException(task.Exception.InnerException.Message);
// Runs when the task is finished
InvokeOnMainThread(() => {
// Hide your activity indicator here.
StopActivityIndicator();
});
});
I dont know where to call InvokeOnMainThread as user could be on any view. How do we handle that.
I would create the downloader as a static class with event handlers (or use dependency resolver). In your view controllers override ViewDidAppear and ViewDidDisappear where you will subscribe and unsubscribe to the events.
In your AppDelegate.cs, You can add your "Task" to the FinishedLaunching override and the OnActivated override, assuming you have a way to determine if there is any "unsaved" data, that needs to be sent to the server.
public override void OnActivated (UIApplication application)
{
}
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
}