How viewChild can get elements that added with js in angular2?

MortezaDalil picture MortezaDalil · May 24, 2016 · Viewed 13.4k times · Source

If a HTML element has been added to the DOM(for example after click on button), How can we access this element? viewChild can't see that.

Update 1: More description

I've used jquery datatable (jquery.dataTables definitelyTyped version). Base on this link, I can add new column and define html inside that. I wrote this code :

           columnDefs: [
                    {
                        render: function (data, type, row) {
                            return `
                                  <a href="admin/edituser/` + row.UserId+ `" class="btn btn-outline btn-circle btn-sm green"><i class="fa fa-edit" > </i> Edit </a>`+
                                 `<a [routerLink]="['Delete']" class="btn btn-outline btn-circle btn-sm red"><i class="fa fa-trash-o" > </i> Delete </a>
                                            `;
                        },
                        targets: 5
                    },
                ],

enter image description here

With Chrome Developer tools I see this : enter image description here

First anchor tag works but with href, not routerLink.

Second anchor tag doesn't work because angular directives didn't complie to href.

I tried to use DynamicComponentLoader to compile it, and changed code like this: enter image description here

But I can't access #target element with viewChild.(I want to change it with another component by loadNextToLocation). I think this isn't optimize way to get good result.

I want to use routerLink with commands buttons inside jquery datatable.

Could you please help me in the right direction to solve this?

Thank you in advance.

solution :

If you want to add HTML to DOM with javascript or some callback function first determine a tag from template that can be a parent for those element(s) in future.

  1. Add template variable to this parent element.(#target for example)

                    <th> Email</th>
                    <th> Date </th>
                    <th> Phone </th>
                    <th> Commands</th>
    
    
                </tr>
            </thead>
            <tbody #target></tbody>
            <tfoot>
                <tr>
    
                    <th>  </th>
                    <th>  </th>
                    <th>  </th>
                    <th>  </th>
    
                </tr>
            </tfoot>
        </table>
    
  2. Then add a simple class and a simple attribute to your html code(This code is generated after page compiled with angular,Also you can write some code with typescript or javascript to generate html).

       columnDefs: [
                    {
                        render: function (data, type, row) {
                            return `
                                 `<a date-id="` + row.UserId + `" class="editbtn btn btn-outline btn-circle btn-sm red"> Edit</a>
                                 `;
                        },
                        targets: 5
                    },
                ],
    
  3. Now import and inject these sources to your component:

        import {ViewChild, Renderer} from '@angular/core';
    

    and :

        constructor(private renderer: Renderer, private router: Router)
    
  4. Define a viewChild variable in your component class:

        @ViewChild('target') target;
    
  5. Write a function like this :

    public AfterEverything() {
           var el: HTMLElement = this.target.nativeElement;//Make an Element Object
           console.log(el);
           var commands: NodeListOf<Element> = el.getElementsByClassName('editbtn');//Find Element Object to get all elements with a specefic class
           console.log(commands);
           for (var i = 0; i < commands.length; i++) {
               //Loop for add event or style
               console.log(commands[i]);
               //Sample event(Click for routing)
               this.renderer.listen(commands[i], 'click', (event: Event) => {
                   var thisid = event.srcElement.getAttribute("date-id");//Get value from element
                   console.log(thisid);
                   this.router.parent.navigate(['EditUser', { id: thisid }]);//Use value to make routeLink
                   console.log(event.srcElement.innerHTML);
               });
               this.renderer.setElementStyle(commands[i], 'backgroundColor', 'yellow');//for change color(just sample)
           }
    

    }

  6. You must find best place to call this function,for example, ajax data callback is one choice and call AfterEverything() inside that. You can call this function inside ngAfterViewInit().

Note that sometimes a delay of several seconds is necessary to ensure that all new elements are created.

Answer

G&#252;nter Z&#246;chbauer picture Günter Zöchbauer · May 24, 2016

If you add HTML dynamically (like [innerHTML]="someHtml"), then you can use direct DOM access (frowned upon in Angular2) by injecting ElementRef and using ElementRef.nativeElement.querySelector...

If the HTML is generated using *ngFor you can add template variables and query for these elements like

<div *ngFor="let x of y" #someVar></div>

and then in the component class

@ViewChildren('someVar') elements;

ngAfterViewInit() {
  console.log(this.elements);
}