angularangular-datatables

Angular Datatables routerLink in render function


I am using the Angular-Datatables package and using the render function to show a button, except this button needs a click event to redirect to another page, at the moment it's with an onlick but obviously this does a full redirect which defeats the purpose of the Angular being a SPA.

This is my implementation of the datatable

<table datatable [dtOptions]="dtOptions"></table>

this.dtOptions = {
  lengthChange:false,
  ajax: (dataTablesParameters: any, callback) => {
    this.userService.getUsers().subscribe(resp => {
        callback({
          data: resp
        });
      });
  },
  columns: [{
    title: 'Username',
    data: 'name'
  }, {
    title: 'Email',
    data: 'email'
  }, {
    title: 'Phone Number',
    data: 'phoneNumber'
  }, {
    title:'',
    data:null,
    render: function (data, type, row, meta) {
      const temp = JSON.stringify(data);
      return `<button class="btn btn-primary" onclick="location.href='/userdetails?user=`+JSON.parse(JSON.stringify(data)).id+`'">Profile</button>`;
   }
  }],
  responsive: true
};

This question uses the rowCallback function which assigns an click event to the whole row, but I cannot do this because of the responsive:true flag, as without this, in a small window, my table goes off screen, so the responsive fits the row to the width of the screen with a click event to expand the remaining fields which conflicts with the rowCallback if I used it (well specifically the rowCallback takes priority).

What can I do to get around this? I have tried <button class="btn btn-primary" routerLink='/testroute'">Profile</button> but that doesn't do anything, it can be seen via inspect element but route does not change, nor is there errors in the console


Solution

  • I got a solution that works, it may not be the "right" way but it works for me.

    The docs used ngAfterViewInit() and added a click listener but the example wasn't very clear, it adds a click listener to the whole document not the button itself and the docs example the click only works on the button because of a property it looks for which they don't show how is defined. So what I ended up doing was checking the target of the click and checking it's innerHTML to match the button I defined, so it looks like this;

    this.renderer.listen('document', 'click', (event) => {
        if(event.target.innerHTML === 'Profile'){
            //navigate function
        }
    });
    

    but doing this, I have now "lost" the ID of the user for the table rows, so I assign the user's ID as the ID of the button in the render function, like so;

    render: function (data: any, type: any, full: any) {
        return `<button class="btn btn-primary" id="${JSON.parse(JSON.stringify(data)).id}">Profile</button>`;
    }
    

    so the navigate function inside my renderer.listen is now;

    this.router.navigate(['/userdetails'], {queryParams:{user: event.target.id}});
    

    but now because this assigns a click listener on the whole document you get this being triggered everywhere on every page, so it has to be removed in the ngOnDestroy like this; credit yurzui and AqeelAshiq

    listenerFn = () => {};
    
    ngAfterViewInit(): void {
        this.listenerFn = this.renderer.listen(...)
    }
    
    ngOnDestroy() { //stop listener from working on every click in every page in site
        this.listenerFn();
    }
    

    so to put it all together my component now looks like this;

    dtOptions: DataTables.Settings = {};
    columns = [{ prop: 'Name' }, { name: 'Email' }, { name: 'Phone Number' }];
    listenerFn = () => {};
    constructor(private userService: UserService,
      private router:Router,
      private renderer: Renderer2) { }
    ngOnInit(): void {
      this.dtOptions = {
        lengthChange:false,
        ajax: (dataTablesParameters: any, callback) => {
          this.userService.getUsers().subscribe(resp => {
              callback({
                data: resp
              });
            });
        },
        columns: [{
          title: 'Username',
          data: 'name'
        }, {
          title: 'Email',
          data: 'email'
        }, {
          title: 'Phone Number',
          data: 'phoneNumber'
        }, {
          title:'',
          data:null,
          render: function (data: any, type: any, full: any) {
            return `<button class="btn btn-primary" id="${JSON.parse(JSON.stringify(data)).id}">Profile</button>`;
          }
        }],
        responsive: true
      };
    }
    ngAfterViewInit(): void {
      this.listenerFn = this.renderer.listen('document', 'click', (event) => {
        if(event.target.innerHTML === 'Profile'){
          this.router.navigate(['/userdetails'], {queryParams:{user: event.target.id}});
        }
      });
    }
    ngOnDestroy() { //stop listener from working on every click in every page in site
      this.listenerFn();
    }
    

    Now for why I mentioned it might not be the "right" way, as mentioned the click listener is everywhere, not just this table, as mentioned I have mitigated it to be removed when the page navigated away from, but its still there for everywhere on this page, hence the need for the check of it being my actual button. Ideally my solution wouldn't need this as it would be just on the buttons, but that is a problem for another day.