-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
feature request: add afterRender event for knockout components #1533
Comments
I'll sign that. |
Related to #1504 |
+1 from me. |
try use next template (for your component):
|
@vamp Thanks for the (temporary) workaround. It seems to work just fine: http://jsfiddle.net/gLcfxkv6/1/ |
+1 from me. Please also consider that it could be important to get notified after child components have been rendered as well. See my recently posted stack overflow question for more info if you're curious: Also seems to be related to #1475 The existing workaround don't work for child components unfortunately. |
+1 Having this event would be useful IMO. We are using the above mentioned template afterRender workaround for the time being. |
Yes, I see this would be useful. To make the behaviour more precisely self-descriptive, I suggest the callback would be called something like It's important to note that this would not have anything to do with child components, as it's much less clear what it means to say the child components are loaded or displayed (the set of child components can change over time, and in general considering that component loading is asynchronous, there's no way even theoretically to know when you've finished adding child components, unless someone can give a more useful definition for "child components have finished loading" than I can think of presently). |
It would be possible, though arguably not practical, to have a queue (or If components are the only asynchronous part of the process, then watching for the queue being empty should cover the component binding process. Not only is this probably not performant, but it is also very "rough", in the sense that there is no way to easily track the component hierarchy completion – one can only discern whether some component, somewhere, is between starting and binding. Building a hierarchy would likely add substantial performance overhead. In the ordinary course, this may differ from the halting problem because the component lifecycle has a definite start (construction) and end (binding). It is correct to say that where there is an aberration like a browser error, there may be no end – which is the halting problem. (For a Javascript error one could set up a timeout to check after a reasonable period for the components that did not complete– but that is way outside what the library ought to do, IMHO.) Given how "rough" the apparent solution is, and the potential issues with performance, my sense is that |
+1 |
I could be wrong, I think Durandal already implemented a kind of component that could track rendered state even with nested components. IMHO, this is a common problem, even with Web Component standard, this could be an issue without proper life-cycle notification. |
I'm using my custom binding for that purpose. Details here: http://codepen.io/kjeske/pen/vNOjwp/ But of course it would be better to have access to this event inside the createViewModel function using the factory pattern. |
+1 |
+1 it will be so amazing |
+1 This is needed |
+1 super useful |
Another thought here is to add a listener for the transitionend event, since that bubbles. This would allow handling of animation transitions, too. So one could have something like: <component-name data-bind='afterTransitionEnd: component_was_rendered'>
</component> for some I feel like this allows for a neat separation of state and logic with JS/CSS. Unfortunately it is IE10+, as far as I can see. |
I threw up a PR for this. |
Other than the debug.js file being put into #1994 PR, the change looks pretty straight forward. |
Iirc the auto-build did that, I checked my local copy and I didn't have it. |
I think the afterRender function needs to receive the constructed viewModel (and possibly the element) as an argument. Otherwise it's actually pretty tricky to get access to either. |
How is this problem unlike the observable dependency tracking we already have in knockout? When a top level container starts rendering, it opens up a dependency tracker. Each component that gets created attaches to the open tracker, such that as child components open their own contexts additional dependencies are tracked. As the leaf children components finish their rendering, they notify their dependent trackers of completion. When the entire chain is 'resolved' a callback can be fired. Isn't this pretty much the same mechanic around computed observables and how they set up dependency tracking based on what observables are invoked during evaluation? -Chris |
@chrisknoll While I haven't delved into it, it feels like the same mechanic to me, too. The only question, in my mind, is when components are "complete" – it may be we want them to self-report, or simply after they are rendered (subject to any sub-components). |
+1 for this support |
If it helps anyone else, I've dealt with this issue also. I've come to the conclusion that the Did you know that it's actually pretty easy to use Rather than repeat myself in two different threads, I'll just refer you to my comments in issue #1475 and the Gist I put together demonstrating how to dynamically load templates: https://gist.github.com/jacobslusser/2d9210606a8ab64f3a4df7747ee40404. Using a simple RequireJS plugin, it can be as easy as adding a dependency to I have used this technique on large RequireJS/Knockout projects and it works great! Both the Use the |
Any update on this por possible workaround. O have issues executing jQuery plugins |
Throwing in the solution we use in FootworkJS...essentially it is solved via a custom $lifecycle binding that is wrapped around all rendered components which handles the afterRender capability via the update callback on the binding. The $lifecycle binding: The custom component loader that wraps all components with the binding: How it ties into the view model: Just throwing this out there as an example, in-use solution... |
@mbest have you had any more thoughts on this? |
@mbest, I haven't forgotten about this, but am inclined to trust that you know the best forward-thinking approach to implementing the feature, so if you have something in hand that no one has objections to, I'm totally behind it. |
Yeah, I'd love to weigh in more on implementation too but I'm unfortunately still not proficient in understanding the entire implementation of Knockout - I trust you here too. |
If you're building a SPA, ko-component-router supports beforeRender, afterRender, beforeDispose, and afterDispose lifecycle hooks. DISCLAIMER: It's my package. |
@caseyWebb still waiting on official IE9 support ;) |
I'm coming back to this since I want to include this feature in some sort in the next release. My idea now is to use a |
Promises make sense when the code looks like you are initiating something and then you are promised a result....making an ajax request results in a promise...in this case, however, you don't exactly initiate a 'initiate component rendering', that happens a bit under the covers. Instead, this feels more like an event hook or some type of event that you want to subscribe to or register a callback handler for...similar to what a promise is for, of course, but different in that the 'initiating the action' doesn't get started and you don't get a handle on that to-be-completed action (int he form of a promise). Maybe defining a standard method (like we have for disposal) on the component model that you can specify that will be called (if specified) once the component (and children) are done. I like the form:
|
Hey @chrisknoll, thanks for the feedback. I'm wary about linking this feature to a method on the viewmodel, as it would introduce binding-level information to the component viewmodel, which we've already specifically tried to avoid by distinguishing between a viewmodel construction and a
|
Hey, @mbest , so I think your document.fonts.ready nails it: it's a promsie that isn't actually created by the 'user' (ie: the dev) instead it's just known to exist and you can do a .then() on it. Maybe that's exacly what you should do on the component just not sure exactly which option this falls under, but maybe using the syntax options i desribed above:
and in the guts of knockout: every initlaized component gets a 'ready' promise attached to it, and the aboe binding will invoke 'someCallback' when the ready promise is resolved. I know I am re-hashing up what I just said before, but my confusion is where you look for the 'ready promise' in the API. In the example with the fonts, some underlying framework (ie: the browser's dom) set up the ready promise and attached it to document.fonts.ready. It's like a standard pre-defined place to find it. In the case of components, I'm not sure how that looks. I think I've tried to get a reference to a component, usually it means I pass in a observiable to the component and then in the constructor function I assign 'this' back to the passed in observable..and it felt dirty...and also 'roll-your-own'. So my current feeling is I don't quite see how it 'works' maybe a code example of how it would work would help. my discionnect is where the promise is created, and then how it's referenced later so you can .then() to it. |
While we are at it, maybe a proper lifecycle for the components should be defined, not only a single onReady/afterRender? Take a look at Vue.js take on component's lifecycle: There's also Oracle JET's ojModule take (search for "ViewModel's Lifecycle"): |
@chrisknoll - I think what you're asking for is pretty much what @mbest is suggesting. The differing approaches do raise an interesting point - which "component" is interested in the completion of rendering. We have In that component some {{#if}} binding's condition becomes true and we end up needing to show componentB (which may well have componentC, D and E within it) - that componentB is going to be inside of componentA, like this
With @mbest's suggestion, which is what I thought too, it's the ComponentA is unaware. It's componentInfo and Is there merit in handling the latter approach in addition to the componentInfo/createViewModel approach suggested by @mbest? The latter approach can be done manually via a component parameter provided by A that B accepts - it's just whether KO should take over this responsibility or if it should be explicit in component B's contract. |
I hadn't considered the temporal case where A finishes rendering at one point in time, then something happens below A where A needs to know about it. You raise an interesting case! Since that is more of an asynchronous and repeatable action, I've seen an event system employed to raise events about what's happening for a component (and children), and this is more akin to a lifecycle like @vvs is saying. Re: promises: I'm down with promises and love them a lot. A nice feature of them vs. callbacks is that you can check a promise after it's been resolved and proceed with the function in then() whereas in the callback case you need to be waiting for the action to happen before it happens. Using promises also makes sense in the case of wanting to know when a component has been initalized, since that happens once, and you may be looking for it to be finished either before or after it actually initialized. Callbacks would make sense if you needed repeated notification of things happening inside the component (like event notification of component changes). |
@vvs: The oracle lifecycle thing (that's actually a knockout implementation too!) looks really really good. I wonder if you can just drop that into an existing app or if there's some other dependencies on oracle jet that you just can't use that ojModule binding.... |
After some more thought, I'm leaning again toward just having a callback function on the viewmodel. I've created a pull request: #2303 |
I've added #2319, which supports a special callback method on components called |
Hello,
I propose to add an afterRender event to components binding - the event to be fired after viewModel and template are loaded and bound. Event will be useful for example to make area visible after component is loaded.
Best regards
The text was updated successfully, but these errors were encountered: