Renderer multiple selectRootElement Issue

I am trying to use Renderer.selectRootElement to get some elements from my Component, as described here.

Everything works fine, unless I select only one element (plnkr).

As you can see, I have created a component:

export class ExampleComponent implements OnInit{
    @Input() start: any;
    @Input() end: any;

  constructor(public _renderer:Renderer){

  };

    ngOnChanges(){

    }
    ngOnInit(){
        console.log("NG ON CHAN START DATE",this.start);
        console.log("NG ON INIT END DATE",this.end);
        var container =  this._renderer.selectRootElement('.container');
        console.log(container);   
        var inner1 =  this._renderer.selectRootElement('.inner1');
        console.log(inner1);   
        var inner2 =  this._renderer.selectRootElement('.inner2');
        console.log(inner2);   
    }

}

When I try to run this, I have an error of :

EXCEPTION: The selector ".inner1" did not match any elements in [{{exampleData.end}} in MainViewComponent@3:65]

(however, in my app, when only the first container is found, then none others are found).

Any ideas where does this come from?

UPDATE

I found out that the directive is not invoked fully - only div with class container gets added to the HTML.

enter image description here


Solution 1:

DO NOT USE selectRootElement

Its purpose is not to select random elements by selector in your components view.

Simply see its implementation in DomRootRenderer

selectRootElement(selector: string): Element {
    var el = DOM.querySelector(this._rootRenderer.document, selector);
    if (isBlank(el)) {
      throw new BaseException(`The selector "${selector}" did not match any elements`);
    }
    DOM.clearNodes(el);
    return el;
 }

Do you see something interesting there? It's removing the nodes inside the element! Why would it do that? Because its purpose it's to grab the root element! So which one is the root element? Does this sound familiar?

<my-app>
    Loading...
</my-app>

Yes! That's the root element. Okay then, but what's wrong with using selectRootElement if I only want to grab the element? It returns the element without its children and nothing changes in the view! Well, you can still use it of course, but you will be defeating its purpose and misusing it just like people do with DynamicComponentLoader#loadAsRoot and subscribing manually to EventEmitter.

Well, after all its name, selectRootElement, says pretty much what it does, doesn't it?

You have two options to grab elements inside your view, and two correct options.

  • Using a local variable and @ViewChild
<div #myElement>...</div>

@ViewChild('myElement') element: ElementRef;

ngAfterViewInit() {
   // Do something with this.element
}
  • Create a directive to grab the element you want
@Directive({
    selector : '.inner1,inner2' // Specify all children
    // or make one generic
    // selector : '.inner'
})
class Children {}

template : `
    <div class="container">
        <div class="inner1"></div>
        <div class="inner2"></div>
        
        <!-- or one generic
            <div class="inner"></div>
            <div class="inner"></div>
        -->
    </div>
`
class Parent (
    @ViewChildren(Children) children: QueryList<Children>;
    ngAfterViewInit() {
        // Do something with this.children
    }
)

Solution 2:

If you want to preserve content then use the second boolean parameter to true, like this: (using Angular 6)

let activeLi = this.renderer.selectRootElement('ul.ddl>li.active', true);

See the detail from API

     /*
     * Implement this callback to prepare an element to be bootstrapped
     * as a root element, and return the element instance.
     * @param selectorOrNode The DOM element.
     * @param preserveContent Whether the contents of the root element
     * should be preserved, or cleared upon bootstrap (default behavior).
     * Use with `ViewEncapsulation.ShadowDom` to allow simple native
     * content projection via `<slot>` elements.
     * @returns The root element.
     */

abstract selectRootElement(selectorOrNode: string | any, preserveContent?: boolean): any;

Thanks Eric to bring in notice that it removes content by default!