Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

AngularJS is throwing "TypeError: Cannot read property 'childNodes' of undefined" when using DOM elements that trigger directives in ng-view #5069

Closed
manuel-josupeit-walter-aleri opened this issue Nov 21, 2013 · 17 comments

Comments

@manuel-josupeit-walter-aleri

Dear angular guys,

after upgrading AngularJS from version 1.0.8 to 1.2.1 we've encountered an exception being thrown in compositeLinkFn on line 5539:

TypeError: Cannot read property 'childNodes' of undefined
    at compositeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:5539:36)
    at compositeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:5539:13)
    at publicLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:5444:30)
    at boundTranscludeFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:5555:21)
    at controllersBoundTransclude (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:6145:18)
    at update (http://code.angularjs.org/1.2.1/angular-route.js:838:13)
    at Scope.$broadcast (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:11851:28)
    at http://code.angularjs.org/1.2.1/angular-route.js:549:26
    at wrappedCallback (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:10597:81)
    at wrappedCallback (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js:10597:81) 

This error seems to occur only when the routing module is in use and tags are nested within the ng-view container. At least one of these tags must also match any angularjs directive (such as a-tags or form-tags).

Reproducable: always
Browsers: Chrome 31, Firefox 25 and IE 10
Operating system: Windows 8.1

Steps to reproduce:

  • Create a new html file
  • Add the following content to the newly created file:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html ng-app="test">
<head>
  <title></title>
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script>
  <script type="text/javascript" src="http://code.angularjs.org/1.2.1/angular-route.js"></script>
  <script type="text/javascript">
    angular.module('test', ['ngRoute'])
               .config(['$routeProvider', function($routeProvider) {
                    $routeProvider.when('/', {
                        template: 'Hello world!'
                    });
               }]);
  </script>
</head>
<body>
  <div ng-view>
    <div>
        <a href="#"></a>
    </div>
  </div>
</body>
</html>
  • Open the file in any browser and watch the console

As soon as the <a>-Tag is removed, the exception disappears. This issue might be related to #2532.

@petebacondarwin
Copy link
Contributor

This seems to be have been introduced by this commit: b7a5449

@petebacondarwin
Copy link
Contributor

The exception is being thrown on this line: https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L935

@petebacondarwin
Copy link
Contributor

It seems that node is undefined because stableNodeList and linkFns have got out of sync.

@petebacondarwin
Copy link
Contributor

It only occurs if the content of the ng-view element contains Angular directives and that these directives are not on the top level nodes of the child contents of the ng-view element.

@ghost ghost assigned IgorMinar Nov 21, 2013
@IgorMinar
Copy link
Contributor

I suspect that ngView used to be terminal directive but now it's not and it is somehow affecting the next compilation.

I updated the test case and removed it further down to the least amount of nodes in ngView that repro the issue:

<!doctype html>
<html ng-app="test">
<head>
  <title></title>
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script>
  <script type="text/javascript" src="http://code.angularjs.org/1.2.1/angular-route.js"></script>
  <script type="text/javascript">
    angular.module('test', ['ngRoute'])
               .config(['$routeProvider', function($routeProvider) {
                    $routeProvider.when('/', {
                        template: 'Hello world!'
                    });
               }]);
  </script>
</head>
<body>
  <div ng-view>myText<p><a></a></p></div>
</body>
</html>

@ghost ghost assigned tbosch Nov 21, 2013
@tbosch
Copy link
Contributor

tbosch commented Nov 21, 2013

Here is the same error without routes and ngView with a custom directive

<!doctype html>
<html ng-app="test">
<head>
    <script type="text/javascript" src="../build/angular.js"></script>
    <script type="text/javascript">
        angular.module('test', []).directive('test', function() {
            return {
                restrict: 'ECA',
                transclude: 'element',
                link: function(scope, element, attr, ctrl, transclude) {
                    transclude(scope, function(clone) {
                        element.after(clone);
                        clone.html('<div></div>');
                    });
                }
            };
        });

    </script>
</head>
<body>
<div test>myText<p><a></a></p></div>

</body>
</html>

The crucial thing here is the do clone.html(...). If this is removed the error does not happen. Also, if clone.html() is done outside of the transclude function it works fine.

@tbosch
Copy link
Contributor

tbosch commented Nov 21, 2013

So we have the following situation:

  • transclude: 'element" compiles the content of the element.
  • When ngView calls the transclude(scope, coneAttachFn) function, the following things happen:
    1. the element is cloned and then cloneAttachFn is called with the clone.
    2. after cloning the element is linked.

The problem exists as ngView changes the element in the cloneAttachFn, i.e. after it is compiled but before it is linked.

The same can be reproduced with the following code snippet:

var injector = angular.injector(['ng']);
injector.invoke(function($compile, $rootScope) {
  var link = $compile('<div>myText<p><a></a></p></div>');
  link($rootScope, function(clone) {
    clone.html('<div></div>');
  });
});

@tbosch
Copy link
Contributor

tbosch commented Nov 21, 2013

The reason why the example only fails if the html structure is so specific (text-node, outer-node with inner node that has a directive) and not for a html template like <div><a></a> is that the directive a only uses jquery elements, which handle non existing elements well.

If we use a directive that accesses the underlying element directly, we get a corresponding error more easily:

angular.module('test', []).directive('test', function() {
  return {
    link: function(scope, element) {
      element[0].someProp = true;
    }
  };
});

var injector = angular.injector(['ng', 'test']);
injector.invoke(function($compile, $rootScope) {
  var link = $compile('<div><span test></span></div>');
  link($rootScope, function(clone) {
    clone.html('');
  });
});

So to summarize, the problem is that we change the elements in the cloneAttachFn of the link function, and the solution is to not do this there. This means we should change ngView and other directives (e.g. ngIf also adds a comment node during cloneAttachFn) so that they modify the node after the call to $transclude.

tbosch added a commit to tbosch/angular.js that referenced this issue Nov 22, 2013
tbosch added a commit to tbosch/angular.js that referenced this issue Nov 22, 2013
tbosch added a commit that referenced this issue Nov 22, 2013
@tbosch tbosch closed this as completed in e6521e7 Nov 22, 2013
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this issue Jan 27, 2014
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this issue Jan 27, 2014
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this issue Jan 27, 2014
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this issue Jan 27, 2014
@tomtom87
Copy link

Currently getting this error thrown when trying to use the ng-view directive on a div with no contents, inside of a controller with a ng-route template - angular version 1.4.7 from npm

angular.js:12477 TypeError: Cannot read property 'replace' of undefined
    at Cb (angular.js:11406)
    at hf.a.$get (angular.js:12236)
    at Object.e [as invoke] (angular.js:4478)
    at angular.js:4295
    at d (angular.js:4437)
    at Object.e [as invoke] (angular.js:4469)
    at angular.js:4295
    at d (angular.js:4437)
    at Object.e [as invoke] (angular.js:4469)
    at angular.js:7080

@petebacondarwin
Copy link
Contributor

@tomtom87 - can you provide a running example of this (perhaps in a plunker) and create a new issue so that we can investigate?

@sector021
Copy link

Help me out from this issue
angular.js:10033 Error: Could not resolve '#' from state 'logIn'
at Object.transitionTo (angular-ui-router.js:3140)
at Object.go (angular-ui-router.js:3068)
at angular-ui-router.js:4181

@ebrahim-tadbir
Copy link

ebrahim-tadbir commented Jun 3, 2016

when using ng-view this happened to me.

just try to use

    $scope.$on('$viewContentLoaded', function() {
    //needed action    
    });

in the controller related to your state.

@shirenjiuhao
Copy link

<title></title> <style> .box{background:#eee;transition:2s all;position: absolute}
    .box.ng-enter{
        opacity: 0;
    }
    .box.ng-enter-active{
        opacity: 1;
    }
    .box.ng-leave{
        opacity: 1;
    }
    .box.ng-leave-active{
        opacity: 0;
    }
</style>
<script src="js/angular.js"></script>
<script src="//cdn.bootcss.com/ionic/0.9.22-alpha/js/angular/angular-route.min.js"></script>
<script src="//cdn.bootcss.com/ionic/0.9.21-alpha/js/angular/angular-animate.js"></script>
<script> var app = angular.module("app",["ngRoute","ngAnimate"]); </script>

@Nokel81
Copy link

Nokel81 commented May 5, 2017

This also seems to happen when adding DOM elements by JS ahead of other DOM elements that were defined in HTML.

document.getElementById("canvasParent").insertBefore(canvas, document.getElementById("canvasParent").firstChild);

@gkalpak
Copy link
Member

gkalpak commented May 6, 2017

@Nokel81, it would be awesome if you could create a demo.

@Nokel81
Copy link

Nokel81 commented May 8, 2017

https://plnkr.co/edit/Rwoduo3qe6s1WIeb1hO2

Here is a plunker that shows the error, the lines of code to add the canvas is at the bottle of the controller's function

@gkalpak
Copy link
Member

gkalpak commented May 9, 2017

@Nokel81, I see. This is something different, because you are manipulating the DOM from inside a controller (which is not supported). Note that controllers are instantiated after compilation and before linking. Since linking depends on the state of the DOM during compilation it is a realy bad idea to change the DOM structure inside controllers.

Typically, such manipulation should happen in the post-linking phase (when both the element and all its children have been compiled and linked). If you want to do it from a controller, you should use the $postLink hook (that runs during the post-linking phase). Demo

BTW, ngController is no longer a recommended pattern. Modern practices recommend component-based architectures, which (among other things) offer better isolation/modularization.

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

No branches or pull requests

12 participants