@action
didUpdate() {
this.myArray = [1, 2, 3, 4, 5, 6, 7];
scheduleOnce('afterRender', () => {
// .. get the height of ul or do some dom manipulation
// The items are not rendered yet
}
}
So my question is, why aren’t the items rendered yet? Is this the expected behavior? If yes, then how can I do some DOM manipulation, after a did-update function call, in which I want to modify some tracked attributes, that will alter the DOM element I am trying to manipulate. Thanks.
One key thing to know about {{did-update}} is it will not rerun whenever the contents or attributes on the element change. For instance, {{did-update}} will not rerun when @type changes here:
If {{did-update}} should rerun whenever a value changes, the value should be passed as a parameter to the modifier. For instance, a textarea which wants to resize itself to fit text whenever the text is modified could be setup like this:
In this case it’s obviously the contents that are changing but not causing a re-render. Also I think in general it’s best to avoid the runloop if possible. I think part of the problem is that the parent element is what you’re “watching” but the child elements are actually what you’re interested in. So obviously you could move the did-update (or did-insert) modifier to the children. You’d have to debounce or use some other mechanism to only run the handler once per “update” but that’s closer to what you’re actually trying to accomplish anyway. You could also consider writing a custom modifier (maybe using ResizeObserver?) and still using it on the parent.
I can’t say definitively what the best solution is but IIRC the did-update modifier was more intended to be a fill-in for component lifecycle hooks and arg updates rather than DOM changes per-se. That said a modifier still feels like the right abstraction for this.
Hi, thanks for the info. I am actually using the did-update modifier more as a lifecycle hook. My actual component does this:
it receives a @profile of the user
when the @profile changes, I want to update a list of items from the new profile
then, after the list is updated, I want to update the scroller (I use perfect-scrollbar library for this), so the scroller sees the new height of the content
So, I used to have an observer on the @profile . As I want to refactor away from this, I has hoping I can use a did-update modifier. I will have a look how I can modify my component with your advice, but if you guys have any better ideas for the task I am trying to accomplish, it would be really helpful for me, understanding how I should migrate away from these observers.
I have very few observers, very very few, but they do multiple things, all at once:
they set state on the component, but on multiple properties
they also trigger async state fetching from the server
they apply updates on third party dom manipulation libraries ( like updating a progress circle plugin, or a scroller)
Edit: So, following your advice, this is another version:
did-update and did-insert are very generic modifiers that are really just a crutch for when people are trying to literally translate their classic components into glimmer components.
What I would suggest is to write a perfect-scrollbar modifier. Modifiers are designed for this case – they have direct access to the DOM, they have the lifecycle hooks you need, and they can take arguments from the rest of your app and react when those arguments change. Scrolling is even the example used in the README for ember-modifier.
If you need to wait to be sure a data change has already flushed out to the DOM, you can schedule for afterRender.
Here’s a sketch of what the modifier would probably look like:
import Modifier from 'ember-modifier';
import { schedule } from '@ember/runloop';
import PerfectScrollbar from 'perfect-scrollbar';
export default class PerfectScrollbarModifier extends Modifier {
didInstall() {
this.ps = new PerfectScrollbar(this.element);
}
didUpdateArguments() {
schedule('afterRender', () => this.ps.update());
}
}