-
-
Notifications
You must be signed in to change notification settings - Fork 629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lifecycle Events Promise Proposal #367
Comments
We can't provide this across all components. It would be a disaster for performance and would no longer map in any way to web components. If you don't care about web components, you can use the new CompositionTransaction: http://aurelia.io/docs.html#/aurelia/templating/1.0.0-beta.1.1.2/doc/api/class/CompositionTransaction Simply have that injected into your component constructor and then call |
This works really well and was exactly what I was after, @EisenbergEffect! You have my thanks. Here is what @EisenbergEffect said translated into code: import { HttpClient } from 'aurelia-fetch-client';
import { CompositionTransaction } from 'aurelia-framework';
export class YearToDateGauge {
static inject = [HttpClient, CompositionTransaction];
constructor(http, compositionTransaction) {
this.http = http;
// https://github.com/aurelia/framework/issues/367
this.compositionTransactionNotifier = compositionTransaction.enlist();
}
created() {
// retrieve the data
this.http.fetch('/books/')
.then(data => {
this.books = data; // store locally
// done loading data, allow the attached() hook to fire
this.compositionTransactionNotifier.done();
return data;
});
}
// fires only after `compositionTransactionNotifier.done()` is called
attached() {
// update the DOM here, e.g. draw a chart, etc
this.numBooks = this.books.length; // the user is guaranteed that this.books will be availaible
}
} |
Great question and answer - I saved this to my offbrain storage: http://blog.williamhayes.org/2016/03/aurelia-custom-element-async-life-cycle.html http://blog.williamhayes.org/2016/03/aurelia-custom-element-async-life-cycle.html
|
Glad it worked for you! :) |
And Dwayne is adding it to his book as well :)
|
@MarkHerhold |
@atsu85 you are correct. The constructor could be changed to:
|
@MarkHerhold, |
@atsu85 Great point! I updated the code block above to reflect both changes discussed. |
@MarkHerhold Does it work equally well if you enlist inside of the |
@EisenbergEffect please disregard my previous commment (deleted) |
@EisenbergEffect Yes is does. 👍 Here is an equivalent-example of what the code looks like that I tried: import { HttpClient } from 'aurelia-fetch-client';
import { CompositionTransaction } from 'aurelia-framework';
export class YearToDateGauge {
static inject = [HttpClient, CompositionTransaction];
constructor(http, compositionTransaction) {
this.http = http;
this.compositionTransaction = compositionTransaction;
this.compositionTransactionNotifier = null;
}
bind() {
// enlist
this.compositionTransactionNotifier = this.compositionTransaction.enlist();
// retrieve the data
this.http.fetch('/books/')
.then(data => {
this.books = data; // store locally
// done loading data, allow the attached() hook to fire
this.compositionTransactionNotifier.done();
return data;
});
}
// fires only after `compositionTransactionNotifier.done()` is called
attached() {
// update the DOM here, e.g. draw a chart, etc
this.numBooks = this.books.length; // the user is guaranteed that this.books will be availaible
}
} |
what would be the benefit of this approach? The down side of this approach is
|
If you ever wanted to use view caching, the first option would not work because when a view is cached its created callback is only called once on the initial creation, not when it is reused. However, bind, attached, detached and unbind are always called. Also, in many cases, the async composition operation is dependent on some data, which would not usually be available until the bind phase. For the specific example above, it might not matter. |
Good point, thanks! |
any suggestion on howto handle a rejected promise here? can i cancel attaching, unbind myself or something? edit: |
You can't cancel those events. You probably want to plan, from a UX perspective for failure and have the component render differently if that happens. However, my personal recommendation is to not put data access inside of "wdigets" but instead to put that inside "screens" that are controlled by the router. Then you can control navigation if there is a failure. Those screens can then bind the data into the widgets which encapsulate some common rendering. |
@EisenbergEffect Can you expand on:
Does this mean that if I use this method in any of my custom elements, all of my custom-elements will wait until that one promise resolves? |
It means that the the highest level component at the time which is adding nodes to the DOM will wait to add those nodes until after the transaction completes, A common example is the router. The router processes a navigation and eventually wants to add the dom for the new view into the document. However, if there's a compose element in the new view, it may be asynchronously composing another widget. The router uses the above mechanism to know when the internal composition is complete. When it is, it then adds the view to the document. This allows complex compositions that may be async to result in a single batch modification of the document once all internal async compositions are done. |
@EisenbergEffect the doc location for http://aurelia.io/docs.html#/aurelia/templating/1.0.0-beta.1.1.2/doc/api/interface/CompositionTransactionNotifier appear to have been changed. Can you point me to the right direction? |
@EisenbergEffect though the current Aurelia doc didn't mention, component can implement an activate() callback which is called even before created() and attached(). I saw the implementation here https://github.com/aurelia/templating/blob/10ccb740597fee8a42a40c70201c0cacab2fb47f/src/composition-engine.js#L69 I tested a bit, the code above does not run if the component is loaded by router. But router still triggers this activate() callback before created() and attached(). So the behaviour is consistent. I vaguely remember activate() is one of the lifecycle hook in old Aurelia doc. Why it's removed from the doc but still stays in source code? Is it going to be deprecated? Should I use it on component which is not loaded by router? |
The activate callback only works in conjunction with the compose element (or the dynamic composition apis). It's not part of the standard model. |
Rob, I have a scenario like this: AppRouter - tabA | tabB | tabC tabA and tabB have child routers, and the child views depend on the result/Promise of an API call that's made in the parent's (tabA) constructor() method. So, this means that really, we shouldn't display the tabA child, or allow the tabA child bind/attached methods to be called, until the tabA parent Promise resolves - so that the child lifecycle methods can get access to the Promise result. I haven't seen a straightforward way to do this. In Angular, the router provides an optional 'resolve:{}' config object, which awaits for a Promise to resolve before completing the active routing. Is there anything you can suggest to emulate that type of behavior? Or would it make a difference if I moved my API async call from the tabA parent's constructor() to a different method, like attached() or bind() or activate()? Thanks, |
@don-bluelinegrid Definitely move that out of the constructor. If you return a promise from |
Rob, Thanks, I see this in the docs now, which I had missed before. I had tried to attach a new .then() to my existing Promise that returns the required data, which worked. But returning a Promise in the activate() of the parent VM is clearly the correct, and more elegant, way to cause routing to wait for required/resolved data. Thanks, |
Hi @EisenbergEffect,
None of them helped me to reach my goal of having the data in the source code. Here is my implementation of the third solution :
This implementation makes the app lag in the browser without any error server side or client side. Is there a better solution to have the data in the source code before the page is rendered with Aurelia SSR? Thanks a lot for your help. |
@frvncis If you need data to be loaded, and loaded fast, maybe you should load early. I see that you mentioned employing |
I like the concept of the lifecycle events, namely
created()
, bind(),attached()
,detached()
, andunbind()
. I do think that Aurelia could provide more functionality behind these, especially in regards to waiting on async operations.Ideally, I would like to be able to implement a hook for the
created()
callback, return a promise, and have Aurelia wait for it to resolve before invoking theattached()
callback. I am not sure what the idea behavior should be ifdetached()
needs to be invoked before the promise fromcreated()
resolves, but I imagine the promise would be canceled.I feel like there is a disconnect between views that have been routed to via the router, as they get the
activate()
hook which respects and waits for promises, as compared to custom elements which don't respect promises at all.As an end-user of Aurelia, I would expect to be able to do this:
I hope this makes sense. I feel like Aurelia is really well thought out, so it wouldn't surprise me if there is already a way to do this.
The text was updated successfully, but these errors were encountered: