I have a large array that I am using in a component (component A) with *ngFor with a nested *ngFor.
Component B initialises a jquery plugin which registers a document mousemove event handler, I am using this.zone.runOutsideAngular to init the plugin and I am calling this.ref.detectChanges() in the callback as I need to update the UI on mousemove inside the component B.
Component A is not a child of component B.
As soon as the component A is rendered change detection becomes very slow. the array does not change and I am using the ChangeDetectionStrategy.OnPush strategy for component A but when I fire ref.detectChanges() inside component B, ngDoCheck gets called on component A and I can see a noticeable jank on mousemove.
Is there a way to tell angular to completely ignore the large array of items in component A and allow me to handle when the UI should be updated? I thought that using ChangeDetectionStrategy.OnPush would give me what I need but I have tried removing all #Input()s from component A and anytime I call this.ref.detectChanges() inside component B it is still firing ngDoCheck and it is obvious that this is very slow.
I can scroll through the list of items no issue, but it is when I am triggering the detectChanges inside the mousemove on component B that is causing the issue. I know I could manually update the DOM but I think this would just be a workaround as it would only address the jank on mousemove and not the issue around the change detection being slow.
I have got to the bottom of this issue.
The problem was that inside component A for the nested *ngFor I was using a child component to render each sub item which meant that although I was using the ChangeDetectionStrategy.OnPush strategy, it still required a ref check for each item.
I have now moved the html from the child component into component A directly and this has had a huge impact on performance.
this.ref.detach() to remove the detector from from the tree completely, that should stop the checking. Then you can still call detectChanges to do it manually, and reattach to bring it back online.
Maybe also debouncing the mousemoves (rxjs debounceTime()) might help, unless you really need to track every mousemove?
One more optimization if you already didn't, add trackBy: yourTrackByFn to the ngFor(s).
Related
I was working on a component that has a button which toggles a boolean. This boolean is supposed to determine if a child component in the HTML need to re-render or not, since I want the ngOnInit function in the child to be re-run.
The situation is described in the app component here: https://codesandbox.io/s/angular-qxtm8
The app.component is the parent and second.component is the child.
I have tried three different solutions. They are onTestClickOne, onTestClickTwo, and onTestClickThree in app.component.ts. onTestClickOne and onTestClickTwo successfully re-triggers the ngOnInit in the child component. We can see the console log in it is printed on the console whenever I click the corresponding buttons. However, onTestClickThree didn't work.
I'm not 100% sure why onTestClickThree didn't work, and onTestClickTwo did.
My guesses are the following:
onTestClickTwo works because the change detection in Angular is run after the event handler has been executed. So, it will detect the boolean has been set to true. After that, the event loop will get the callback of the setTimeout and put it into the stack. Angular will execute change detection after finishing the callback.
onTestClickThree didn't work because, by the time Angular runs change detection, the boolean is already true. Angular doesn't know that it has been changed.
Let's tackle the main issue there, which is your design : why would you re-render the component to trigger ngOnInit again ?
Sure, in the case of your example, that's no big deal. But what happens for a fully coded component, making http calls, having children and all ? That will cause some severe performance issues.
Instead of re-rendering the component, you should use a function to do that.
If the event (that is initially supposed to re-render the component) comes from the child, then use an #Output. If it comes from the parent, use a #ViewChild reference.
As you can see it works well, without any detection issue.
lHello everyone, here is the problem.
We have a grid component with some filtering enabled. When the filtering is applied, if a certain callback-prop exists, it is called with the filtered data as an argument.
The problem is this. If said datagrid is wrapped by a parent component and the parent component saves the filtered data in it's state, it causes the parent to rerender, but also the datagrid. However, when the datagrid renders it runs it's filtering logic, which causes the callback(which is setState() call) to run.
So, to avoid the loop I introduced a variable to the parent component class and save the data there, but it doesn't seem so good to me.
Another option would be redux, just add a new action and dispatch it when the filtering runs.
Any other ideas?
Since you're also asking for other ideas, may I suggest React hooks. They allow finer-grained control, such as multiple states, reducers, memoized callbacks, effects that are only called when inputs change, etc.
I've wrote a plugin for Vue and have some issues on rerendering. In fact there is a new filter, which translate a given text by the global defined language. When the language changes, the text should be translated again. Cause there is no event queue for filters here, I want to force all components to rerender themselves, if the language changes. So the filter function would be evaluated new.
I know that I can rerender a component itself with vm.$forceUpdate(). But how can I told the whole Vue component tree to rerender? Cause this only happens, if the user change the language, the performance issues should not be a problem, cause at least the user do not have to reload the whole page and can do this offline.
Any suggestions?
You can use a key attribute in the component root. Change it and the component tree will be rerendered.
Per docs (bold is mine):
It can also be used to force replacement of an element/component
instead of reusing it. This can be useful when you want to:
Properly trigger lifecycle hooks of a component
Trigger transitions
For example:
<transition>
<span :key="text">{{ text }}</span>
</transition>
When text changes, the <span> will always be replaced instead of
patched, so a transition will be triggered.
I've an HTML table generated by React render() which is tied to a websocket for realtime updates.
What I'm looking to do is attach a React event handler "onClick" to the cell which replaces the value in the cell with a custom piece of DOM such as some Bootstraps dropdown HTML so the user can update that cell.
I can use .getDOMNode() in the click event handler and then manipulate the DOM manually and insert the code, however if render() on this component was called due to a new updated state from a websocket event it would be overridden. If this race condition occurs, I need to inform the user instead of the DOM simply being replaced. I've just seen componentWillUpdate() though this still feels like I'm not using React correctly.
Is there a better approach ? It feels dirty to be manipulating the DOM and incorrect to change the state.
Updating internal component state is there for exactly this case. This component should be in charge of determining whether it is active or not. If you update state on the component when the click handler is triggered with something like active: true, then you trigger a re-render. In your render function add the additional DOM elements if active is true. Then you have three different possibilities that should be accounted for:
onBlur
onUpdate which can
be triggered in two ways. either way you'll likely want to set
active: false on state.
websockets - you will need to inform your
user here probably regardless of if the component is active or not
user - which will just update normally
Doing it this way means that React is always in full control of the DOM, which is a really good way to avoid issues with React.
I have just started using React, and a couple of times I have thought to myself: "Why is there no componentDidRender event?".
Say that I have a component that renders a table to the DOM, and I want to use bootstrap-sortable on this table to allow the user to sort on whatever column he wants. In the case of bootstrap-sortable you need to run $.boostrapSortable() after the table is drawn, in order to initialize the plugin.
As I see it, there are two handlers on a React component that would be logical to consider to use for this purpose:
componentDidMount: This does not work because the DOM does not seem to be updated at this point of the execution.
componentDidUpdate: This could possibly work, but it does not fire on the initial render.
I am not saying that React is actually missing a componentDidRender function, because I assume that there is a perfectly logical explanation as to why it is not there. I am just asking if someone could explain why such a function is not present, and what would be the "React way" to handle a case like the one above.
In componentDidMount you can do: this.getDOMNode() to get a reference to the underlying DOM for that component. So if you do want to use your mounted component with jQuery you can do:
componentDidMount: function() {
$(this.getDOMNode());
}
http://facebook.github.io/react/docs/working-with-the-browser.html
Here's a fiddle which shows jQuery acting on the DOM node of a react component:
http://jsfiddle.net/sa5e88ys/1/
As you can see, it adds a border to the div as expected. If you're still having problems I guess it could be with the plugin you're using rather than jQuery or react?
Although there's no componentDidRender, you can make a method with the desired behavior and call it in both componentDidMount (which is only called after the first render) and componentDidUpdate (which is called after every render but the first).
Also, this is the preferred way of getting a ref to a DOM node from within the component:
https://facebook.github.io/react/docs/refs-and-the-dom.html