elements[0] on populated HTMLCollection returning undefined. This is a showstopper

3,083 views
Skip to first unread message

Henrik Bechmann

unread,
Apr 2, 2015, 7:51:19 PM4/2/15
to ang...@googlegroups.com

HTMLCollection access seems to be broken in angular environment for some reason, resulting in my being unable to traverse a nested set of elements.

With the the following code:

        var elements = tElement[0].getElementsByTagName('mw-template');
        console
.log(elements);
        console
.log(elements instanceof HTMLCollection);
        console
.log(elements.item(0));
        console
.log(elements[0]);


I get the following results:

[item: function, namedItem: function]
0: mw-template.ng-scope
1: mw-template.ng-scope
2: mw-template.ng-scope
3: mw-template.ng-scope
4: mw-template.ng-scopelength:
5__proto__: HTMLCollection


directives
.js:137 true
directives
.js:138 null
directives
.js:139 undefined

In other words it appears to be a valid HTMLCollection, but I am unable to access it. I've searched through google, and found a couple of similar complaints, but no answers.

I've tried countless other approaches around trying to cast the items as an array (though I actually want the live versions), and using both jqLite and jQuery to cast the items at various steps into jQuery collections. Also taking direct Nothing seems to work. 

Note that the collection id deeply nested:

this code:

        console.log(tElement[0].children[0].children);
        console
.log(tElement[0].children[0].children[0]);


gives this result

[item: function, namedItem: function]
0: mw-block-toolbar.ng-scope
1: mw-template.ng-scope
2: mw-template.ng-scope
3: mw-template.ng-scope
4: mw-template.ng-scope
5: mw-template.ng-scopelength:
6__proto__: HTMLCollection



undefined




Anyone have any ideas?

This is a showstopper.


Henrik Bechmann

unread,
Apr 3, 2015, 12:57:59 AM4/3/15
to ang...@googlegroups.com
So after grinding at it for quite a while, I found a solution. Bottom line, template mangles dom interaction in directive compile function (eg. only returns partial strings for innerHTML and such - it's messed up). The solution was to forego template and transclude (doing those manually in compile function), pull out the innerHTML from the compile directive function's tElement parm, set the innerHTML string to blank (''), and then create a jQuery collection from the innerHTML. That then behaves normally in terms of DOM manipulation.

After the manipulation, $compile the result in the link function, and add it back to the live DOM.

It's messed up.

Here's the code that works:

.directive('mwDelegateBlockList',['$compile','mwParse',function($compile,mwParse){
    return {
        restrict:'E',
//        template:template,
//        transclude:true,
        compile:compile
    }
    // function template(element, attributes) {
    // }
    function compile(tElement,tAttrs) {
        var html = tElement.html();
        tElement.html('');
        var format = tAttrs['mwFormat'] || 'list';
        if (format == 'grid') {
            html = `<md-grid-list>${html}</md-grid-list>`;
        } else if (format == 'table') {
            html = `<table>${html}</table>`;
        } else { // 'list'
            html = `<md-list>${html}</md-list>`;
        }
        var $content = angular.element(html);
        function link(scope, element, attributes, constructor) {
            console.log('scope',scope);
            var children = $content.children(); // selector children('mw-template') does not work
            var length = children.length;
            for (var i = 0; i < length; i++) {
                var child = angular.element(children[i]);
                if (child.prop('tagName').toLowerCase() == 'mw-template') {
                    var mwIf = child.attr('mw-if');
                    if (mwIf) {
                        // check if template is required for current format
                        var retain = mwParse.expression(mwIf)(scope);
                        // if not remove it
                        if (!retain) child.remove();
                    }
                }
            }
            console.log('$content',$content);
            var content = $compile($content)(scope);
            element.append(content);
        }
        return link;
    }
}])

- Henrik 

Sander Elias

unread,
Apr 3, 2015, 5:47:04 AM4/3/15
to ang...@googlegroups.com
Hi Henrik,

May I make an remark that's not directly related to your problem? 
You are pushing and pulling  state and data to/from the DOM. This is the way we worked before we had Angular. In angular, we manage our data in the models, witch we keep in controllers/services. Then angular makes sure that data is rendered to the DOM. It helps to see this as an one-way street. We change data, and the template + directives make sure it's displayed the right way. User interaction calls functions in our controllers, which may manipulate data, and the cycle repeats. 

It looks like you are making it harder for yourself then is needed by trying to get the DOM into that loop. Manipulating the DOM yourself is possible but it trips up the cycle. 
From what i'm reading from your directive, you can replace the directive with an ngSwitch statement in your template. 

Regards
Sander

Reply all
Reply to author
Forward
0 new messages