0

i have an issue while click binding on dynamic html.I tried setTimeout function but click event not binding on button.i have also tried template referance on button and get value with @ViewChildren but @ViewChildren showing null value.

Typscript

export class AddSectionComponent implements OnInit {
      sectionList: any = [];
      constructor(private elRef: ElementRef,private _httpService: CommonService ,private sanitized: DomSanitizer) { }
    
      ngOnInit(): void {
        this.getSectionList();
      }
      ngAfterViewInit() {
          let element = this.elRef.nativeElement.querySelector('button');
          if (element) {
            element.addEventListener('click', this.bindMethod.bind(this));
          }
      }
      bindMethod() {
        console.log('clicked');
      }
      sanitizeHtml(value: string): SafeHtml {
        return this.sanitized.bypassSecurityTrustHtml(value)
      }
        getSectionList() {
        //API request
        this._httpService.get('/Section/GetSectionList').subscribe(res => {
          if (res) {
              this.sectionList = res.json();
            //sectionList is returning below HTML   
            //<div class="wrapper">
            //  <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
            //</div>
          }
        })
      }
    }

Template

<ng-container *ngFor="let item of sectionList">
    <div [innerHTML]="sanitizeHtml(item?.sectionBody)">

    </div>
    //innerHTML after rendering showing this 
    //<div class="wrapper">
    //  <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
    //</div>
</ng-container>
6
  • This is strange requirement, how did you get to this ? the button doesn't exist, you are just assuming that it does, that's why binding it seems tricky, if you know 100% that it does, then why isn't' it part of the component itself ? even if that was conditional. However, it's possible, you just need to keep listening and rebinding the event to the dom each time, that's not very good approach though. Commented Aug 18, 2022 at 15:39
  • i strongly agreed with you that's not good approach but the main reason i want to load some static Html from my admin side so in this way i can reduce my HTTP calls. Commented Aug 18, 2022 at 15:46
  • if the button isn't static then based on what do you want to bind its click event ? if it is static then keep everything else part of item.sectionBody, but remove the button, and wrap it in a component <div innerHtml ...> </div> <button> here </button>, or then you would need to figure out a smarter way to bind those events dynamically, I can help you more if you create a stackblitz, even if you want to stick to your current approach, it is possible, I just don't think it is going to be very readable/clean code. Commented Aug 18, 2022 at 15:49
  • i am creating a stackblitz and i will let you know. Commented Aug 18, 2022 at 16:14
  • stackoverflow.com/questions/71479471/… Commented Aug 18, 2022 at 16:58

1 Answer 1

1

Short Answer, you are binding functions inside your templates, which means you have a new html content every time change detection runs, and change detection runs everytime a function is called, which means your button keeps on being updated infinitely, that's why it never works, Read more here please.

Now on how to do this, I would listen to ngDoCheck, and check if my button has a listener, if not, I will append the listener. I will also make sure to use on Push change detection, because if not, this will ngDoCheck will be called a lot, and maybe the button will be replaced more often, not quite sure about it. Here is how the code would look like.

html

<!-- no more binding to a function directly --> 
<div #test [innerHTML]='sanitizedHtml'></div>

component

import { HttpClient } from '@angular/common/http';
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  changeDetection:  ChangeDetectionStrategy.OnPush
})
export class AppComponent implements DoCheck {
  name = 'Angular';
  people: any;
  //now we are getting the div itself, notice the #test in the html part
  @ViewChild('test')
  html!: ElementRef<HTMLDivElement>;
  //a property to hold the html content
  sanitizedHtml!: SafeHtml;
  constructor(private _http: HttpClient, private sanitized: DomSanitizer,private change: ChangeDetectorRef ) {}

  ngDoCheck(): void {
    //run with every change detection, check if the div content now has a button and attach the click event
    if (this.html != undefined) {
        let btn = this.html.nativeElement.querySelector('button');
        if (btn && btn.onclick == undefined) {
          btn.onclick = this.bindMethod.bind(this);
        }
      }
  }

  ngOnInit() {
    this.peoples();
  }

  peoples() {
    this._http.get('https://swapi.dev/api/people/1').subscribe((item: any) => {
    const people = `<div class="wrapper">
                      <p>${item['name']}</p>
                      <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
                    </div>`;
    //assign the html content and notify change detection
    this.sanitizedHtml = this.sanitized.bypassSecurityTrustHtml(people);
    this.change.markForCheck();
    });
  }
  bindMethod() {
    console.log('clicked');
  }
}

I don't like the approach because of the need to listen to ngDoCheck, this can run a lot, especially if you don't use onpush change detection. I hope this helped.

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

6 Comments

why we are creating @ViewChild('test') because we are not using it anywhere explain it please.
Viewchild is a property decorator, it passes the value to the html Dom, please google it and read more, basically now the property html is a reference to the div, and we are using that reference to select the button
i am used to with @ViewChild but my question is that we are not getting any value from @ViewChild just declared it.
Yes we are using it for the property 'html', and later you can see that we are accessing that property and selecting the button.
This is exactly what I used, you just changed the name of the property and removed the generic cast of HtmlDivElement. There is no difference.
|

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.