1

I try to set up event (click) to button element, but I still have an error

ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'nativeElement' of undefined
TypeError: Cannot read property 'nativeElement' of undefined

When I tried to call <a (click)=EditButton(data)>Edytuj</a> there was no action, so I tried to use ChildView, Renderer, nativeElement in few version but without success.

Buttons are nested into DataTable.net Options (as column) inside Component file:

...
  columns: [
  { data: "Imie" },
  { data: "Nazwisko" },
  { data: "Brygada" },
  { data: "Stawka" },
  { data: "Aktywny" },
  {
    data: null, defaultContent :`
    <a #EditButton >Edytuj</a>
    <a #DeleteButton >Usuń</a>
    `
  },
],
buttons: [
...

Bellow is Component code:

import { Component, OnInit, ViewChild, TemplateRef, Renderer2,  AfterViewInit } from '@angular/core';
import { FadeInTop } from "../shared/animations/fade-in-top.decorator";
import { Http, Response } from '@angular/http';

import { BsModalService, ModalDirective } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';

import { Observable } from "rxjs/Rx";
//import 'rxjs/add/operator/map';
//import 'rxjs/add/operator/catch';
import { bootstrapValidationRoutes } from '../+forms/+bootstrap-validation/bootstrap-validation.routing';


@FadeInTop()
@Component({
  selector: 'sa-czas-pracy',
  templateUrl: './czas-pracy.component.html',
  styleUrls: ['./czas-pracy.component.css']
})
export class CzasPracyComponent implements OnInit {
  
  public REST_ROOT = 'http://localhost:3000/pracownicy/pracownicy';
  
  //private static lgModal;

  options = {
    dom: "Bfrtip",
    ajax: (data, callback, settings) => {
      this.http.get(this.REST_ROOT)
        .map(this.extractData)
        .catch(this.handleError)
        .subscribe((data) => {
          console.log('data from rest endpoint', data);
          callback({
            aaData: data.slice(0, 100)
          })
        })
    },
    columns: [
      { data: "Imie" },
      { data: "Nazwisko" },
      { data: "Brygada" },
      { data: "Stawka" },
      { data: "Aktywny" },
      {
        data: null, defaultContent :`
        <button #EditButton >Edytuj</button>
        <button #DeleteButton >Usuń</button>
        `
      },
    ],
    buttons: [
      { text: 'Dodaj',
        action: () => {this.showAddModal()}
      }     
    ],
  };
  @ViewChild('AddEditModal') AddEditModal :ModalDirective;
  @ViewChild('EditButton') myButton;
  
  modalRef: BsModalRef;
  title: string;
  
  constructor(
          private http:Http, 
          private modalService: BsModalService,
          private renderer: Renderer2
        ) {}
 ngOnInit() {      
//   let simple1 = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
//     console.log('Clicking the button', evt);  
//    });    
    }
  ngAfterViewInit() {
    let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
      console.log('Clicking the button', evt);
    });
  }

  editPracownik(){
    console.log('edit');
  }
  ...

I searched and tried a lot of ways, but nothing work.

Any work around would be helpful.


Yes I checked and you have right. ngAfterContentChecked() called before and after DataTable.option. So I think it's ok, but myButton still is undefined.

Console when loading component:

czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:34 data from rest endpoint (3) [{…}, {…}, {…}]
3czas-pracy.component.ts:48 Create button
czas-pracy.component.ts:48 Create button
3czas-pracy.component.ts:48 Create button
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined

I tried refer to references by methodw witch is linked to button defined in html template - and myButton is still undefined

    <a class="btn btn-danger btn-xs" (click)="findMyButton()">FindMyButton</a>    

...

findMyButton(){    
  console.log('find my button clicked')
  console.log((this.myButton) ? 'is myButton' : "myButton undefined"); 
  if(this.myButton && !this.hasAttachedListener){
    let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
      console.log('Clicking the button', evt);
    });
    this.hasAttachedListener=true;
}

result:

czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:90 find my button clicked
czas-pracy.component.ts:91 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined
czas-pracy.component.ts:79 AfterContentChecked
czas-pracy.component.ts:80 myButton undefined

Changed code

export class CzasPracyComponent implements OnInit, AfterContentChecked {
  
  public REST_ROOT = 'http://localhost:3000/pracownicy/pracownicy';
  
  //private static lgModal;

  options = {
    dom: "Bfrtip",
    ajax: (data, callback, settings) => {
      this.http.get(this.REST_ROOT)
        .map(this.extractData)
        .catch(this.handleError)
        .subscribe((data) => {
          console.log('data from rest endpoint', data);
          callback({
            aaData: data.slice(0, 100)
          })
        })
    },
    columns: [
      { data: "Imie" },
      { data: "Nazwisko" },
      { data: "Brygada" },
      { data: "Stawka" },
      { data: "Aktywny" },
      {
        data: null, render : function (data, type, row) {
          console.log('Create button');
          return `
                <button #EditButton >Edytuj</button>
                <button #DeleteButton >Usuń</button>
                `
              }
              },
    ],
    buttons: [
      { text: 'Dodaj',
        action: () => {this.showAddModal()}
      }     
    ],
  };
  @ViewChild('AddEditModal') AddEditModal :ModalDirective;
  @ViewChild('EditButton') myButton;
  @ViewChild('button') myButton1;
  
  modalRef: BsModalRef;
  title: string;
  hasAttachedListener:boolean=false;

  constructor(
          private http:Http, 
          private modalService: BsModalService,
          private renderer: Renderer2
        ) {}
 ngOnInit() {
 }

 ngAfterContentChecked() {
    console.log('AfterContentChecked');
    console.log((this.myButton) ? 'is myButton' : "myButton undefined"); 
    if(this.myButton && !this.hasAttachedListener){
      let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
          console.log('Clicking the button', evt);
      });
      this.hasAttachedListener=true;
    }
  }

  findMyButton(){
    console.log('find my button clicked')
    console.log((this.myButton) ? 'is myButton' : "myButton undefined"); 
    if(this.myButton && !this.hasAttachedListener){
      let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
          console.log('Clicking the button', evt);
      });
      this.hasAttachedListener=true;
    }
  }

Have you got any idea how to implement button code that be visible by angular?

3
  • Can you show me how your html template code looks like where the data table is inserted into. I can't really figure out, when and where your data table is generated, what does start the ajax call with the options you provided, is it an external script? Commented Nov 28, 2017 at 9:30
  • I'm using SmartAdmin Template from MyOragne and there is implemented DataTables.net, here is code plnkr.co/edit/6rZfxiOmdy9d9b3tUMiu. There is a solution for AngularJS, but I can't put it into my project datatables.net/examples/ajax/null_data_source.html Commented Nov 28, 2017 at 10:06
  • I've updated my answer which may contain a solution that you can use. Commented Nov 28, 2017 at 10:17

2 Answers 2

2

I'm not familiar with DataTable.net but here's what I believe is happening. Through DataTable.net you're loading information from the backend to intitialize your view. This is done by calling your REST_ROOT link, but you have to consider that this takes time, it does not look like the result is existing the same time the component is initialized.

Now here's the problem. You're trying to access a button that is not existing yet.

ngAfterViewInit() {
  let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
    console.log('Clicking the button', evt);
  });
}

The ngAfterViewInit() life cycle hook is called once after the view was initialized. This is done right after the component was created and it does not rely on dynamically created content in later stages. So at this point, your http call is probably still not finished and therefore the button is not existing in the view. So the life cycle hook you are using is just trying to find your button too early. Here is what the documentation says:

Respond after Angular initializes the component's views and child views. Called once after the first ngAfterContentChecked(). A component-only hook.

You probably need to implement it in another life cycle hook, like ngAfterViewChecked(). Hooks like that are called more often, so make sure you always check if your button is existing and especially only attach the click event once, so keep track of wether you have attached it or not, otherwise you will have a lot of listener on your button.

hasAttachedListener:boolean=false;

ngAfterViewChecked() {
    if(this.myButton && !this.hasAttachedListener){
        let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
            console.log('Clicking the button', evt);
        });
        this.hasAttachedListener=true;
    }
}

One final thing though, I'm not sure if @ViewChild works if the html was just created as for example innerHTML and not through renderer or some thing. I think in this case angular does not know that it should watch the #EditButton because it was not aware of it at creation time. So even if this fix above may help (which I'm not sure) it could still be that the button reference will always be undefined.

Edit

I try to make it easier to understand. With ViewChild() and the # operator angular gives the user the opportunity to get a reference on a real DOM element, which is similar to what you would usually do with the $ selector. But this only works if the HTML was generated through angular itself. If you have template code for a component for example, angular will take that HTML code and write it to the DOM itself, and this way knows about everything that is inside. If I open the developer tools of the browser and edit some HTML code on the fly, then angular will not know anything about it. So there is a difference between HTML code that was generated by angular and HTML code that was generated by a person or an external script that has nothing to do with angular.

So what is the problem here? The problem is that your data tables HTML is generated without the help of angular, it's generated by an external script that does not know about angular at all. It's the same as if you would open the developer tools in the browser and copy some code in. Angular does not know that it just happend. Now if you have a button inside of this code like #MyButton, your angular component can only know that this reference exists if angular has created it by itself. But in your case with the external script, angular has just no idea that this button exists, so your reference with @ViewChild() is always undefined.

Solution approach through top level ViewChild

Here's what you could try. Although you can't use @ViewChild to reference a DOM element that was dynamically created, you can reference the top level DOM element in your template in which the datatable code is created afterwards.

<div #table>
    <!-- this is where your table will be placed -->
</div>

@ViewChild('table') tableRef;

Now with this reference you can wait until your code is ready (you've attached the log file where you can see exactly when the data table is finished with loading) and the HTML is created. Your reference now has full access over all its children divs, including your created table. You can use this reference to iterate over its children.

this.tableRef.nativeElement.childNodes

Or what is probably the better solution, you could find your button through a query selection that you can call on this top level node. Let's assume you've updated your button and it now would have an ID instead of an angular reference marker.

<button id="EditButton">Edytuj</button>

Then when the table is created we call querySelection on the top level node and find that object with the ID.

var editButton = this.tableRef.nativeElement.querySelector("#EditButton");
this.renderer.listen(editButton, 'click', (evt) => {
    console.log('Clicking the button', evt);
});

This should do the trick.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you, I've edited questions and add more details P.S. I'm not sure if i correct understood that what you wrote i last > One final thing though, I'm not sure if @ViewChild works if the html ... So you mean this is no good place to implement button if i good understood
In your smartadmin template you can check what the last DOM element above the generated code is, I think this might be the sa-datatable tag. Give it an angular reference, this should work here because this is your component template, so angular will create it itself.
Your explains was very clear and useful. It was very thoroughly and point me right way. Thank you very much.
2

I finally resolve the problem. Of course great thanks for Benedikt Schmidt. Below the solution:

...
  dataPracownicy : any;

  options = {
    dom: "Bfrtip",
    ajax: (data, callback, settings) => {
      this.http.get(this.REST_ROOT)
        .map(this.extractData)
        .catch(this.handleError)
        .subscribe((data) => {
            this.dataPracownicy = data;
          console.log('data from rest endpoint', data);
          callback({
            aaData: data.slice(0, 100)
          })
        })
    },
    columns: [
      { data: "Imie" },
      { data: "Nazwisko" },
      { data: "Brygada" },
      { data: "Stawka" },
      { data: "Aktywny" },
      {
        data: null, render : function (data, type, row) {
          console.log('Create button');
          return `
                <button id=EditButton >Edytuj</button>
                <button #DeleteButton >Usuń</button>
                `
              }
              },
    ],
    buttons: [
      { text: 'Dodaj',
        action: () => {this.showAddModal()}
      }     
    ],
  };
  @ViewChild('AddEditModal') AddEditModal :ModalDirective;
  @ViewChild('TablePracownicy') tableRef;


  modalRef: BsModalRef;
  title: string;
  hasAttachedListener:boolean=false;

  constructor(
          private http:Http, 
          private modalService: BsModalService,
          private renderer: Renderer 
        ) {}
 ngOnInit() {}

ngAfterViewChecked() {
    if (!this.hasAttachedListener){
      var buttons = document.querySelectorAll("#EditButton");
      console.log(buttons.length  );
      if(buttons.length>0){
        console.log(buttons);
        for(let i=0;i<buttons.length;i++){
        this.renderer.listen(buttons[i], 'click', (evt) => {
          console.log('Clicking the button'+[i], evt);
          });
        }
        this.hasAttachedListener=true;
   }
  }
}
...

I hope that my solution will be useful greetings

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.