methuselah
methuselah

Reputation: 13206

innerHTML - pass in value on (click) event

How do I pass in a parameter to a click event that has been set in innerHTML?

Component

html = "Lorem ipsum dolor sit amet, <mark (click)='hello('my name is what')'>consectetur adipiscing elit</mark>"

constructor(private changeDetectorRef: ChangeDetectorRef) {}

ngAfterContentChecked() {
  this.changeDetectorRef.detectChanges();
   if (this.showcaseContentText.nativeElement.querySelector('mark')) {
     this.showcaseContentText.nativeElement
      .querySelector('mark')
      .addEventListener('click', this.hello.bind(this));
   }
}

hello(test: string) {
  console.log(test);
}

Template

<div class="text-md-left text-muted mb-lg-6" [innerHTML]="html" style="font-size: 15px"></div>

Upvotes: 1

Views: 533

Answers (1)

Barremian
Barremian

Reputation: 31125

I'm able to capture the click events from the mark tag using RxJS fromEvent instead of addEventListener and AfterViewInit instead of AfterContentChecked hook. I've also sanitized the HTML using Angular DomSanitizer.

Try the following

app.component.ts

import { fromEvent, Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";

...
export class AppComponent implements AfterViewInit, OnDestroy {
  @ViewChild("showcaseContentText") showcaseContentText: ElementRef<any>;
  html =
    "Lorem ipsum dolor sit amet, <mark (click)='hello('my name is what')'>consectetur adipiscing elit</mark>";
  closed$ = new Subject<any>();

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.cdr.detectChanges();
    fromEvent(
      this.showcaseContentText.nativeElement.querySelector("mark"),
      "click"
    )
      .pipe(takeUntil(this.closed$))
      .subscribe(e => console.log(e));
  }

  ngOnDestroy() {
    this.closed$.next();
  }
}

app.component.html

<div #showcaseContentText class="text-md-left text-muted mb-lg-6" [innerHTML]="html | safe" style="font-size: 15px">
</div>

safe.pipe.ts

import { Pipe, PipeTransform } from "@angular/core";
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";

@Pipe({
  name: "safe",
  pure: true
})
export class SafePipe implements PipeTransform {
  constructor(protected sanitizer: DomSanitizer) {}

  public transform(value: any): SafeHtml {
    return this.sanitizer.bypassSecurityTrustHtml(value);
  }
}

Working example: Stackblitz

Update: multiple <mark> tags in single innerHTML

In that case you could use querySelectorAll() instead of the querySelector() function. Also since there would be multiple elements, you could use Array#map along with fromEvent and use RxJS map operator to get the respective ids.

Note that we're create multiple subscription streams. So more the number of mark tags, more the number of streams. You need to close it when the component is closed (in the eg. takeUntil is used). There are better ways to handle multiple subscriptions (eg. using combineLatest), but they have their own pros and cons. I'll leave it to you to sort them out.

Controller

export class AppComponent implements AfterViewInit, OnDestroy {
  @ViewChild("showcaseContentText") showcaseContentText: ElementRef<any>;
  html =
    "Lorem ipsum dolor sit amet, <mark id='mark1' (click)='hello('my name is what')'>consectetur adipiscing elit</mark>. Lorem ipsum <mark id='mark2' (click)='hello('my name is two')'>dolor sit amet</mark>, consectetur adipiscing elit";
  closed$ = new Subject<any>();

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.cdr.detectChanges();
    const obs$ = Array.from(
      this.showcaseContentText.nativeElement.querySelectorAll("mark")
    ).map((el: HTMLElement) => fromEvent(el, "click").pipe(map(_ => el["id"])));

    obs$.forEach(obs =>
      obs.pipe(takeUntil(this.closed$)).subscribe({
        next: id => {
          console.log(id);
        }
      })
    );
  }

  ngOnDestroy() {
    this.closed$.next();
  }
}

Working example: Stackblitz

Upvotes: 3

Related Questions