5

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

George Chondrompilas
  • 3,167
  • 27
  • 35
uksz
  • 18,239
  • 30
  • 94
  • 161
  • 1
    I don't know what is the exact problem .. But still you can use `BroswerDomAdapter` seems good for `DOM` manipulation and good thing is it is powered with DOM Api :-). – micronyks Mar 17 '16 at 09:32
  • I don't know what excactly causes this, but you could insert a zero-timeout, then it works as expected: http://plnkr.co/edit/MKLf6EWGa9xvjKUJDW2q?p=preview – olsn Mar 17 '16 at 09:43
  • @micronyks, is there any documentation available on BrowserDomAdapter? there are 0 google results LOL – uksz Mar 17 '16 at 09:55
  • Yes docs are not available. But if you are familiar with javascript DOM api then you can use it with BroswerDomAdapter. – micronyks Mar 17 '16 at 10:20

2 Answers2

34

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
    }
)
Community
  • 1
  • 1
Eric Martinez
  • 31,277
  • 9
  • 92
  • 91
  • Thats pretty nice explanation - thanks! However, I decided to use ElementRef function as follows :this.ElementRef.nativeElement.firstChild.firstElementChild etc, to access the divs that I was looking for – uksz Mar 17 '16 at 11:46
  • @uksz I strongly recommend you to use Directives, using directly nativeElement is discouraged because of webworkers, it's not wrong, but it can cause you troubles. Use `nativeElement` as your last resource if nothing else works, just see the warning in its [documentation](https://angular.io/docs/ts/latest/api/core/ElementRef-class.html) – Eric Martinez Mar 17 '16 at 11:49
  • Very good explanation about the `selectRootElement` method, thanks! – Elias Garcia Mar 22 '18 at 12:32
  • @EricMartinez hello and thank you for that repsonse. Although I have to ask : `ElementRef` returns a reference to an element, but not the element itslef. How should on proceed when, let's say, they want the background color of a div ? –  Apr 25 '18 at 09:25
7

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!

Ali Adravi
  • 21,707
  • 9
  • 87
  • 85