Skip to content
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

Closed
SuperCuke opened this issue Sep 10, 2014 · 69 comments
Closed

feature request: add afterRender event for knockout components #1533

SuperCuke opened this issue Sep 10, 2014 · 69 comments

Comments

@SuperCuke
Copy link

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

@milaan-muc
Copy link

I'll sign that.

@brianmhunt
Copy link
Member

Related to #1504

@AgCouper
Copy link

+1 from me.

@vamp
Copy link

vamp commented Sep 17, 2014

try use next template (for your component):

<div data-bind="template: {afterRender: HERE_IS_YOUR_FUNCTION}">
...
</div>

@eib
Copy link

eib commented Oct 15, 2014

@vamp Thanks for the (temporary) workaround. It seems to work just fine: http://jsfiddle.net/gLcfxkv6/1/

@richotaylor
Copy link

+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:
http://stackoverflow.com/questions/27157350/knockoutjs-afterrender-callback-when-all-nested-components-have-been-rendered

Also seems to be related to #1475

The existing workaround don't work for child components unfortunately.

@yohanliyanage
Copy link

+1

Having this event would be useful IMO. We are using the above mentioned template afterRender workaround for the time being.

@SteveSanderson
Copy link
Contributor

Yes, I see this would be useful. To make the behaviour more precisely self-descriptive, I suggest the callback would be called something like componentWasDisplayed, an optional method on your component viewmodel that KO invokes only once when the template is first bound.

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).

@brianmhunt
Copy link
Member

It would be possible, though arguably not practical, to have a queue (or Set) of components, with a component being added upon construction, removed just after its bindings are applied (or some hook that defaults to binding, but may be extended to cover other user-land asynchronous elements).

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 afterRender is not a feasible built-in for components. However for an actionable item, perhaps hooks that would permit the user to implement something along the lines of the above – e.g. hooks such as ko.components.beforeViewConstruction and ko.components.afterComponentBinding (which I hope are self-descriptive).

@bjornalm
Copy link

+1

@matthewma7
Copy link

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.

@kjeske
Copy link

kjeske commented Sep 8, 2015

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.

@chinamerp
Copy link

+1

@herbertpimentel
Copy link

+1 it will be so amazing

@Elephant-Vessel
Copy link

+1 This is needed

@miketalley
Copy link

+1 super useful

@brianmhunt
Copy link
Member

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 afterTransitionEnd binding that listens for the event and calls a given function (in this case component_was_rendered when complete.

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.

@krishnaglick
Copy link

I threw up a PR for this.
#1944

@chrisknoll
Copy link

Other than the debug.js file being put into #1994 PR, the change looks pretty straight forward.

@krishnaglick
Copy link

Iirc the auto-build did that, I checked my local copy and I didn't have it.

@AlexGodofsky
Copy link

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.

@chrisknoll
Copy link

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

@brianmhunt
Copy link
Member

@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).

@rabeesh
Copy link

rabeesh commented Nov 23, 2016

+1 for this support

@jacobslusser
Copy link

If it helps anyone else, I've dealt with this issue also. I've come to the conclusion that the component binding serves a different purpose than the template binding and if what you want is the afterRender function then you should use the template binding. At first I was disheartened by that realization because "components" are the greatest thing since sliced bread... except, they're not. What made components cool for me was the require functionality built-in that worked nicely with my RequireJS setup and could dynamically load views/viewmodels on demand.

Did you know that it's actually pretty easy to use require with templates also?

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 'component!<name>' and get either the component OR a template/model.

I have used this technique on large RequireJS/Knockout projects and it works great! Both the component and the template binding are supported. In practice we find that we use the template binding more often. It is just superior in almost every way to the component binding and when the hard part of loading templates dynamically is addressed, using the template binding is just as easy as the component binding but far more flexible.

Use the component and template bindings appropriately. When done right, I would take RequireJS/Knockout over AngularJS any day of the week and twice on Sundays.

@smashraid
Copy link

Any update on this por possible workaround. O have issues executing jQuery plugins

@jonbnewman
Copy link

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:
https://github.com/footworkjs/footwork/blob/master/source/binding/lifecycle-binding.js

The custom component loader that wraps all components with the binding:
https://github.com/footworkjs/footwork/blob/master/source/component/component-lifecycle-loader.js

How it ties into the view model:
http://docs.footworkjs.com/release/2.0.1/viewModel-creation/#afterrender-callback

Just throwing this out there as an example, in-use solution...

@codymullins
Copy link

@mbest have you had any more thoughts on this?

@chrisknoll
Copy link

@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.

@codymullins
Copy link

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.

@caseyWebb
Copy link
Contributor

If you're building a SPA, ko-component-router supports beforeRender, afterRender, beforeDispose, and afterDispose lifecycle hooks.

DISCLAIMER: It's my package.

@codymullins
Copy link

@caseyWebb still waiting on official IE9 support ;)

@mbest
Copy link
Member

mbest commented Aug 29, 2017

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 Promise that will resolve once a component, and its descendants, are displayed. This Promise will be available as part of the componentInfo parameter passed to the createViewModel function.

@chrisknoll
Copy link

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:

<div data-bind="component: {name: someComponentName, onRenderComplete: someCallback}"></div>

<someComponentName params="someParams" onRenderComplete="someCallback"></someComponentName>

@mbest
Copy link
Member

mbest commented Aug 29, 2017

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 createViewModel method. This leaves one of the other three options. I like the callback registration option, using a property on the componentInfo object.

Promise provides a convenient way to register a callback that needs to be called only once when something is complete. It seems that this is the way JavaScript and the DOM are moving. For example, see document.fonts.ready.

@chrisknoll
Copy link

chrisknoll commented Aug 29, 2017

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:

<someComponentName params="someParams" onReady="someCallback"></someComponentName>

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.

@vvs
Copy link

vvs commented Aug 29, 2017

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:
https://vuejs.org/v2/guide/instance.html#Lifecycle-Diagram

There's also Oracle JET's ojModule take (search for "ViewModel's Lifecycle"):
https://docs.oracle.com/middleware/jet210/jet/reference-jet/ojModule.html

@IanYates
Copy link

@chrisknoll - I think what you're asking for is pretty much what @mbest is suggesting.
The difference is that, in your proposal, the callback would be in the parent component whereas @mbest is having the child component (the one you're waiting to render) be the thing to receive the callback.
Note that when I say callback I really mean promise - since, as you described above, a promise is really a nice way of having a handle to which you can attach a callback for completion.
The promise approach certainly makes sense IMHO.

The differing approaches do raise an interesting point - which "component" is interested in the completion of rendering.

We have
<componentA></componentA>

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

<componentA>
    <componentB>
    ...
    </componentB>
</componentA>

With @mbest's suggestion, which is what I thought too, it's the createViewModel function of componentB that will get the notification that componentB, and its children (C, D & E) have all rendered.

ComponentA is unaware. It's componentInfo and createViewModel function ran well before our code even decided that componentB should be displayed.
However you do raise a good point - what if it's useful for componentA to know that componentB has finished rendering?

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.

@chrisknoll
Copy link

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).

@chrisknoll
Copy link

@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....

@mbest
Copy link
Member

mbest commented Oct 6, 2017

After some more thought, I'm leaning again toward just having a callback function on the viewmodel. I've created a pull request: #2303

@mbest
Copy link
Member

mbest commented Nov 22, 2017

I've added #2319, which supports a special callback method on components called koDescendantsComplete that is called once all descendant bindings (including async components) are complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests