Javascript required
Skip to content Skip to sidebar Skip to footer

Angularitics Masonry "Cannot Read Property 'childnodes' of Undefined"

The other twenty-four hours at InVision, nosotros launched some lawmaking that worked fine locally, fine in QA, fine in Staging, and fine in Product - except for a small fix of users. For only a few people, the page was completely breaking with the AngularJS-initiated JavaScript mistake, "TypeError: Cannot Read Property 'childNodes' Of Undefined." Afterwards the client back up team dug into the tickets, they noticed a tendency that most of the users were in Europe; and, that almost of the users had the "CookiesOK" Google Chrome plugin installed. It turns out, this was just one of a number of Google Chrome plugins that can [potentially] disrupt the AngularJS compile and linking lifecycle.

I've tried to follow the AngularJS compile and linking code to figure out exactly what is going wrong. Simply, to be honest, the code is a fleck as well complicated for me to trace finer. I understand, at a high level, what is going incorrect; simply, I cannot determine the low-level landscape of details. Ultimately, the error has to do with the fact that the DOM (Certificate Object Model) is being contradistinct indirectly, by a Controller, during the compile and linking stage. The alteration of the DOM throws the internal tree-walker out of whack and we end upward referencing an undefined node.

In our particular instance, the problem relates to a breakdown in the separation of concerns between module types. In AngularJS, the Controller is not supposed to know anything near the DOM. And, to that extend, information technology probably shouldn't load whatsoever services that mutate the DOM. But, that's exactly what we were doing - we were loading a service that was injecting a 3rd-political party Script chemical element as office of its initialization.

The Controller was [indirectly] mutating the DOM, which is a large no-no in AngularJS.

On its own, this may non have been a trouble - or rather, the problem may never have become symptomatic. But, for users that had certain Google Chrome plugins installed, the page would intermission because the plugins themselves were injecting Script tags into the HTML element of the page. The 3rd-party script tags would and so getting injected before the plugin-injected tags, and that's what was breaking everything.

To get a sense of what I'thou talking about, hither is an isolated use-example:

                  <!doctype html> <html ng-app="Demo" ng-controller="AppController"> <head> 	<meta charset="utf-8" />  	<title> 		TypeError: Cannot Read Property "childNodes" Of Undefined In AngularJS 	</championship> </caput> <body>  	<h1> 		TypeError: Cannot Read Property "childNodes" Of Undefined In AngularJS 	</h1>  	<div>  		<!-- 			In order for this to demo to interruption, nosotros need to have a few things in play:  			1. We need the AppController to be on the HTML element.  			2. We demand the AppController to trigger the loading of a 3rd-party script 			that gets injected into document.  			3. We need the body to have a directive (it doesn't matter which one) that 			is nested inside some other element (ie, it cannot be a straight child of the 			body tag).  			4. We demand the user to have some sort of Chrome plugin like "CookieOK" or 			"Google Analytics Opt-out Add-on" that injects a Script into the page. 		--> 		<div ng-mode="{}"> 			Woot, in that location information technology is. 		</div>  	</div>   	<!-- Load scripts. --> 	<script type="text/javascript" src="./angular-1.iv.three.js"></script> 	<script blazon="text/javascript">  		// Create an application module for our demo. 		var app = angular.module( "Demo", [] );   		// --------------------------------------------------------------------------- // 		// --------------------------------------------------------------------------- //   		// I command the root of the awarding. 		athwart.module( "Demo" ).controller( 			"AppController", 			role( $scope, thirdPartyScript ) {  				// Trigger the loading of a script, which will be injected into the DOM. 				thirdPartyScript.load();  			} 		);   		// --------------------------------------------------------------------------- // 		// --------------------------------------------------------------------------- //   		// I provide the ability to load and and so interact with a 3rd-political party script. 		athwart.module( "Demo" ).manufactory( 			"thirdPartyScript", 			office() {  				// Render the public API. 				return({ 					load: load 				});   				// --- 				// PUBLIC METHODS. 				// ---   				// I load the third-party script tag. 				role load() {  					// Inject script before outset script in page. 					// -- 					// Note: Lawmaking like this is often copy-pasted out of some read-me 					// on the tertiary-party vendor documentation.  					var script = document.createElement( "script" ); 					script.src = "//cdn.some-3rd-party-vendor.com/js/script.js";  					var firstScript = certificate.getElementsByTagName( "script" )[ 0 ];  					firstScript 						.parentNode 							.insertBefore( script, firstScript ) 					;  				}  			} 		);  	</script>  </body> </html>                                  

If I have the "CookiesOK" or the "Google Analytics Opt-out Addition" Google Chrome plugins installed and I endeavour to run the above page, I get the following output:

TypeError: Cannot read property childNodes of undefined in AngularJS application.

NOTE: This will not error if the controller is in a dissimilar place; or, if we don't take a nested directive in the trunk tag. There is something about this combination of elements that causes the internal tree-walker to get confused. But, like I said above, I can't pinpoint the actual problem in the AngularJS source lawmaking.

To fix this, we need to pull the DOM-mutation out of the Controller lifecycle. And, the easiest way to do that is simply to wrap the DOM-mutation inside of $timeout() phone call:

                  <!doctype html> <html ng-app="Demo" ng-controller="AppController"> <head> 	<meta charset="utf-8" />  	<title> 		TypeError: Cannot Read Property "childNodes" Of Undefined In AngularJS 	</championship> </caput> <body>  	<h1> 		TypeError: Cannot Read Property "childNodes" Of Undefined In AngularJS 	</h1>  	<div>  		<div ng-style="{}"> 			Woot, there it is. 		</div>  	</div>   	<!-- Load scripts. --> 	<script type="text/javascript" src="./angular-1.4.3.js"></script> 	<script type="text/javascript">  		// Create an application module for our demo. 		var app = athwart.module( "Demo", [] );   		// --------------------------------------------------------------------------- // 		// --------------------------------------------------------------------------- //   		// I control the root of the application. 		angular.module( "Demo" ).controller( 			"AppController", 			function( $scope, thirdPartyScript ) {  				// Trigger the loading of a script, which will exist injected into the DOM. 				thirdPartyScript.load();  			} 		);   		// --------------------------------------------------------------------------- // 		// --------------------------------------------------------------------------- //   		// I provide the ability to load and and then collaborate with a 3rd-party script. 		angular.module( "Demo" ).manufacturing plant( 			"thirdPartyScript", 			function( $timeout ) {  				// Return the public API. 				return({ 					load: load 				});   				// --- 				// PUBLIC METHODS. 				// ---   				// I load the third-party script tag. 				role load() {  					// Utilise the script inject in the next tick of the result loop. This 					// will give AngularJS time to safely finish its compile and linking. 					$timeout( loadSync, 0, false );  				}   				// --- 				// PRIVATE METHODS. 				// ---   				// I load the 3rd-party script tag. 				function loadSync() {  					// Inject script before first script in page. 					// -- 					// Annotation: Code similar this is often copy-pasted out of some read-me 					// on the 3rd-party vendor documentation.  					var script = document.createElement( "script" ); 					script.src = "//cdn.some-3rd-political party-vendor.com/js/script.js";  					var firstScript = document.getElementsByTagName( "script" )[ 0 ];  					firstScript 						.parentNode 							.insertBefore( script, firstScript ) 					;  				}  			} 		);  	</script>  </body> </html>                                  

With this slight modification, the page runs fine, no "childNodes" fault.

There'south probably a improve way to organize this code. Merely, I haven't personally dealt with loading many 3rd-party script tags; so, I don't have a proficient instinct for this yet. Mostly, I just wanted to get this out there in case others were running into the same, seemingly unreproducible JavaScript error.

Black Lives Matter

Ad for InVision App, Inc prototying platform.


moretonpalwas.blogspot.com

Source: https://www.bennadel.com/blog/2892-typeerror-cannot-read-property-childnodes-of-undefined-in-angularjs.htm