One of the things I'm still hung up on with Angular is understanding all of the lifecycle hooks and when to use what when.
I often need to stick a little plain'ol JS into a component to deal with some DOM issue that, alas, I can't handle via Angular (usually because I'm working within a component that needs to access some elements from a parent component's rendered DOM that we have no access to the Angular code for...I do realize this isn't the 'proper' Angular way but...).
An example right now is a few pages I'm working on that use a component need that needs to hide a DOM element on the page that isn't a part of this component. I need to use JS for this (a whole other story why CSS isn't the solution for this one).
But I only want to do this once the DOM is fully rendered.
Sometimes this seems to work when inserted into ngAfterViewInit -- but sometimes not. It seems that there's no guarantee the full DOM is ready using that lifecycle.
Moving that logic into ngAfterViewChecked does work. However, the issue with ngAfterViewChecked is that it's getting called dozens of times on some pages--and the first few times it's called, the DOM isn't even ready. Not the end of the world, but there's no reason for me to be attempting to grab the same DOM object 40 times per page render. I somewhat remedy this by adding a boolean flag to tell this bit of JS to stop running once it finds the DOM elements but that's hacky.
So my question is: What is the proper (if there is one) way to handle JS manipulation of the DOM after the DOM is fully rendered in the context of an Angular component? Is it to use one of the angular lifecycle events? Something else? Or this whole idea of manipulating DOM objets outside of the component I'm working in just anathema to the 'Angular way' so just isn't something accommodated within Angular?
Related
I'm creating an app using Nodejs and Vuejs 3. In this app I have made a sidebar that gets all links from a routes file and present them. This sidebar consists in a component that parents a list of other recursive link components.
Since the links are recursive and many, I find it hard to deal with class toggling (active, showing, collapsed, etc.) on each of them and relate them to one another (if one is active the others shouldn't be) using only Vue. Should I use querySelector or any frameworks such as JQuery to handle them or should I try to stick with a pure Vuejs approach?
Edit:
I don't want to gather the community's opinion on it. My aim is to understand pragmatically why I should or shouldn't manipulate the DOM "outside" of Vue.
If you're using Vue then let it be in control of the DOM; mucking around directly will only create conflicts and woe.
(The same applies to other SPA frameworks such as React and Angular.)
The main reason not to touch the DOM is that Vue works by modifying the DOM on its own, and expects to have complete control over it: when rendering components the framework is removing old DOM elements, adding new ones, updating event bindings, etc; and a lot of it is optimized to only update the DOM nodes that need to be updated.
If you go in there and start making direct changes that Vue doesn't know about, then it's likely that your own changes will get overwritten by Vue the next time it needs to render, or that your changes will overwrite bindings that Vue is depending on.
If you're very knowledgeable about Vue's lifecycle and know how to control when it does and does not render, it is possible to work with both together -- but even then it's still not a great idea. Vue and jQuery do very similar things, but in utterly different ways. In jQuery you build up the page and then use DOM traversals and event handlers to modify it; everything lives inside the DOM. In Vue you build up a bunch of components that manage their own state and rendering; the DOM is basically a side effect of the application state.
By trying to use both together you lose most of the advantages of each of them in isolation, and introduce a lot of complexity in having to manage two competing theories of state and render management (not to mention dealing with communicating data between them). Every time I've contemplated embedding a jQuery widget inside a Vue app, it's turned out to be much easier to just rewrite the widget in Vue directly.
This does mean changing a lot of habits about working with the DOM that you may have built up from past jQuery work. It sounds like you're trying to draw the whole DOM and then build your control structure into it afterwards, which is a natural way to think if you're used to jQuery; in Vue you'll want to build all of that logic into components so the framework can do the work for you. I'd suggest making one Vue component for a link that manages its own state for open / closed / active etc, that recurses to its children only when "open". Then just call that once with the top of your nav data instead of trying to manage the whole tree directly after the fact as you would in jQuery.
This always puzzled me. If I said "Thank you React, your state hooks are awesome, but I'm just gonna do my direct dom manipulation here", would react still do the virtual dom comparison in order to update only that specific item? would I still benefit from the virtual dom 'situation'?
Is there any difference between using react to directly manipulate dom without states, and using a standard HTML file with imported vanilla js code?
For clarity, here's an example,
Let's say I have function printHellol() triggered by a button 'click' in my JSX. The function targets the ID of an element and changes the text content.
I couldn't find the answer anywhere. Thanks!
Anything that his held in state becomes part of an object that react renders as a detached element from the dom. It's in essence creating a separate environment for all states. Anything that is used outside of state can be considered to be part of the direct dom object, causing the page to be rerendered when updates occur. In other words you would need to use the react specific state if you want to access the virtual dom specific environment.
Came across below statement in one or other form on google
Each time the underlying data changes in a React app, a new Virtual
DOM representation of the user interface is created.This is where
things get interesting. Updating the browser’s DOM is a three-step
process in React.
Whenever anything may have changed, the entire UI will be re-rendered
in a Virtual DOM representation.
The difference between the previous
Virtual DOM representation and the new one will be calculated.
The
real DOM will be updated with what has actually changed. This is very
much like applying a patch.
I am new to React and would like to understand above how above three points used to to work in pre-React Era say in jQuery (or native JS).
jQuery
HTML was constructed on server side, sent back to browser. Browser will parse, render, layout and paint it.
Say any new DOM element is created or hidden either on some user event or on load.
Will jQuery recreate the complete DOM? From third point stated above looks like React just update the DOM for only the part that has been changed but other system (primarily jQuery or native JS) will recreate the complete DOM. Is that correct?
Is the third point true only for DOM changes or even when state of any UI component changes like filling the text box/dropdown etc?
Will jquery recreate the complete DOM ? From third point stated above looks like React just update the DOM for only the part that has been changed but other system(primarily jquery or native js) will recreate the complete DOM. Is that correct?
jQuery
No, jQuery doesn't recreate the DOM. Instead it navigates the DOM tree with selectors provided to make the appropriate changes. This makes jQuery very similar to React, but its performance issues come with the way the code was designed and it's heavy usage of the facade design pattern. This is normal for any larger library that needs to support multiple browsers like Chrome, Firefox, Opera, etc.
Angular 1
A framework that used to repaint the entire DOM was Angular 1. The client would make some changes, and rerender whenever $scope.apply or $scope.digest was called. This, combined with a huge number of listeners on large pages for two way data-binding was one of the big reasons why Angular had to undergo significant changes to stay competitive. Angular 8 is probably equivalent to React, but one has seen more adoption then the other.
React
React only updates the DOM that was changed. This is part of its "secret sauce". Along with its component centric architecture and early adoption of one way data-binding, it's seen a lot of success. Arguably React is starting to get more bloated since there's such wide adoption. This is normal for any project that gets mainstream usage. It's only a matter of time until people view React as a ton of performance problems and we'll create a new framework.
Alternatives
There are even faster frameworks than React, such as Elm lang. Nothing ever beats pure Javascript (e.g. document.querySelector()) since at their core, all frameworks use it. At that point you start hitting other trade offs such as lack of external libraries you can depend on, or difficulty in maintaining large front end codebases.
Is the third point true only for dom changes or even when state of any UI component changes like filling the text box/drop down etc ?
For jQuery or pure JS the third point is not true. There's a custom on-click handler of some sort that will run a function that makes a small change.
For something like Angular, that could be true if there are changes to scope that trigger a repaint. The same applies for React. If your submit button is supposed to redirect you to a completely different page, it will basically be repainting the DOM.
Background
I've been working with Polymer for a while. I've been converting from .5 and building new elements for a production app. We are currently using Polymer 1.0.6, and this particular issue is also using jQuery 2.x.x and typeahead.js.
Issue
We have an element that builds a dynamic list of label and inputs provided by a data source. in the ready function we get a list of input data, and set that to a local list variable that is bound to a foreach template to create the labels and inputs.
I was unable to find a Polymer element I really liked for typeahead, for Polymer 1.0. So I defaulted to using typeahead.js. my problem is that I cannot find a lifecycle event or workaround, to call the typeahead function after the dom has processed setting the bound list in the ready function.
Code
The easiest way to demonstrate this issue, was to create a HEAVILY trimmed down version in a jsbin. I know the element looks bad, it was cut down as much as possible to demo the core issue I'm facing.
http://jsbin.com/zivano/edit?html,output
What Have I Tried?
I've tried using the attached event, and while it does process after the ready function, the dom changes from ready have not taken effect. I found similar issues on SO domReady vs ready - Migrating to Polymer 1.0 I've tried both suggestions, the second is still being used in the jsbin, without success.
I have also bound the click event of my inputs to a function calling the typeahead setup code, to prove that if the calls are made after the dom is rendered it will work correctly.
Summary
If update a data bound, local variable in the ready function, is there a lifecycle event I can call that will guarantee that those dom changes will be rendered, so I can make a dom query against those new items? Or is there a work around that will let me call a js function on a dom element, one time after the element dom fully renders?
my problem is that I cannot find a lifecycle event or workaround, to
call the typeahead function after the dom has processed setting the
bound list in the ready function.
I think I had a problem like this . For my problem I found a solution using the following :
var self = this;
window.addEventListener('WebComponentsReady', function(e) {
// imports are loaded and elements have been registered
/*Example*/
console.log('Components are ready');
var p = self.getElementsByTagName("paper-item");//paper-item created dynamically
console.log(p);//can access and use this paper-item
/*Finish example*/
//here you can call typeahead because the dom has been processed
});
Sorry for my English or if I dont understand your question, my English is bad.
The Issue I had was that the data-bound list was populated through an ajax function, which was completed after the attached function, even if I made an async call inside of the attached function, it would still fail because of race conditions.
It's worth noting the answer by Flavio Ochoa, will work. I personally preffered to not have my custom elements add listeners to the Window. So i went a different route.
Since my issues we're predicated on guaranteeing that the bound list was updated, I wrapped the ajax call in a Promise, and added the typeahead init logic to the then statement. That solution appears to be working.
I do have some concerns whether the promise can guarantee that the bound list will have propagated to the DOM by the time the then statement is processed. But so far it has worked consistently. I'll edit this answer if I can prove otherwise.
Some JQuery plugins don't just add behavior to DOM nodes, but change them. For example, Bootstrap Switch turns
<input type="checkbox" name="my-checkbox" checked>
into something like
<div class="bootstrap-switch bootstrap-switch-wrapper bootstrap-switch-on bootstrap-switch-large bootstrap-switch-animate">
<div class="bootstrap-switch-container">
<span class="bootstrap-switch-handle-on bootstrap-switch-primary">ON</span>
<label class="bootstrap-switch-label"> </label>
<span class="bootstrap-switch-handle-off bootstrap-switch-default">OFF</span>
<input type="checkbox" name="download-version" checked="" data-size="large" data-on-text="3" data-off-text="2.0.1">
</div>
</div>
with
$("[name='my-checkbox']").bootstrapSwitch();
Which doesn't jive with React:
Uncaught Error: Invariant Violation: findComponentRoot(..., .0): Unable to find
element. This probably means the DOM was unexpectedly mutated (e.g., by the
browser), usually due to forgetting a <tbody> when using tables or nesting <p> or
<a> tags. ...<omitted>...`.
Is there a recommended technique for incorporating these plugins into React components? Or do they fundamentally break the assumptions of React and cannot work with it?
No, react will react (haha) badly to anything that modifies its own component dom structure outside of react. This is something you don't ever want to do. The recommended solution would be to replicate the functionality of whatever you're trying to do with a jquery or similar plugin, in react.
Having said that, there is a reasonable way to do this for specific instances where you just can't do without it, but it essentially means wrapping some non-react dom inside react.
Example:
var Example = React.createClass({
componentDidMount: function() {
var $checkboxContainer = $(this.refs.checkboxContainer.getDOMNode());
var $checkbox = $('<input />').prop('type', 'checkbox');
$checkboxContainer.append($checkbox);
$checkbox.bootstrapSwitch({});
},
render: function() {
return (
<div>
<div ref="checkboxContainer"></div>
</div>
)
}
});
Now of course you are rendering a component with a nested div. The nested when mounted to the dom for the first time that nested div will get a checkbox appended to it by jquery, which will then also execute our jquery plugin on it.
This particular example component has little point to it, however you can see how this might integrate into a more complex component while still allowing you to re-render and react to state changes etc. You just lose the ability to directly react to events/modify things inside of the checkbox in question which as far as react is concerned, doesn't exist.
Now with the above example if you were to have some react logic to add/remove the nested div, you'd have to have the same logic around that div being inserted be responsible for re-inserting the checkbox and re-initializing it with the jquery plugin. However because react only modifies the dom when needed, this inserted dom content wont be removed unless you do something that modifies the container div in a way that causes it to be removed/re-rendered to the dom. This means you can still access all of the events within react for that container div etc.
You could also make use of the componentDidMount function in react to bind events or callbacks to specific interactions on the checkbox itself. Just make sure to unbind them correctly in componentWillUnmount or wherever it makes sense to do so in the component lifecycle in your specific case.
In this great ryanflorence's tutorial you'll get an idea on how to do this:
Wrapping DOM Libs
Methodology
DOM libs usually manipulate the DOM
React tries to re-render and finds
a different DOM than it had last time and freaks out
We hide the DOM
manipulation from React by breaking the rendering tree and then
reconnecting around the DOM the lib manipulates.
Consumers of our
component can stay in React-land.
Sure, there is such a technique. We're doing these things all the time.
You create React component to wrap jQuery plugin.
Inside of your render(), you return an empty <div ref="placeholder" />
In your componentDidMount method, you retrieve this element by its ref, and initialize your jQuery plugin there.
In your componentWillUnmount, you clean it up. Calling 'destroy', or anything else required to avoid memory leaks.
That's it. Fortunately, it's completely safe to modify DOM in this way in React.
If you want this plugin to react on props changes, things get a bit more tricky. You need to override other lifecycle methods, like componentWillReceiveProps, check whenever props actually changed, and call corresponding plugin methods. I can explain in more details, if you will have specific questions, overall topic is too broad for the comment.
This is more of a philosophical question
React was created to optimize DOM manipulations and has a lot of wiring behind the scenes to do so when a component's state changes via setState
Doing so will cause said wiring to traverse its virtual DOM to find the nodes that need to be updated
If you must use React, whether to try to keep a level of consistency in your coding, your best bet is to apply the JQuery DOM manipulation inside the componentDidMount like so...
componentDidMount(){
this.node = $("#"+this.props.id); // Keep a reference to the node
this.chart = this.node.easyPieChart(); // Apply JQuery transformation and keep a reference
this.percentTitle = this.chart.find(".percent"); // Keep a reference to the title
}
Having done so, on whatever your "refresh" method is, do NOT make any calls to setState, instead, call whatever update method your JQuery component may have, like so...
componentWillMount(){
this.interval = setInterval(this._doRefresh.bind(this), 1500);
}
_doRefresh( percentage ){
// Note how setState is NOT being called here
percentage = percentage || Math.floor (Math.random() * 100) // simulate since we're not getting it yet
this.chart.data('easyPieChart').update(percentage); // call easyPieChart's update
this.percentTitle.text(percentage);
}
At this point, if you're asking why use React at all, well, in my case, this component is an item in a list of other React components and was used to maintain consistency throughout the application... You may have a similar dilemma
If, unlike me, you are unlucky enough that your component doesn't have an update method, and you can't make one, it might be time to rethink the approach altogether