1

I've got a web page that uses Angular and routes data from a TS file. Everything works except (click).

If I put the SVG with the (click) function directly in the HTML, it works, but if I call the SVG from a TS file, it doesn't. If I remove (click)=linkOpen(), the link continues to open in a new window; if I remove target=iframe, the link opens in the current tab.

project.component.html:

    <div class="page" *ngIf="project$ | async as project">
        <div [innerHTML]="svgHTML(project)"></div>
    </div>

project.component.ts:

import { Component, OnInit, Injectable } from '@angular/core';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { DomSanitizer } from '@angular/platform-browser';

import { CopeAdditionsService } from '../cope-additions.service';
import { Project } from '../project';

@Component({
  selector: 'app-project',
  templateUrl: './project.component.html',
  styleUrls: ['./project.component.css']
})

@Injectable()
export class ProjectComponent implements OnInit {
  display: boolean;
  project$: Observable<Project>;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private service: CopeAdditionsService,
    public sanitizer: DomSanitizer
  ) {
    this.display = false;
  }

  ngOnInit() {
    this.project$ = this.route.paramMap.pipe(
      switchMap((params: ParamMap) =>
        this.service.getProject(params.get('id')))
    );
  }

  gotoProjects(project: Project) {
    let projectId = project ? project.id : null;
    this.router.navigate(['/cope-additions', { id: projectId }]);
  }

  linkOpen() {
    if (window.innerWidth >= 600) {
      this.display = true;
      document.getElementById('imgLink').setAttribute('target', 'iframe');
    } else {
      this.display = false;
      document.getElementById('imgLink').setAttribute('target', '_self');
    }
  }

  svgHTML(project) {
    return this. sanitizer.bypassSecurityTrustHtml(project.SVG);
  }
}

project-details.ts:

    SVG:`
        <svg class=\'svg\' fill=\'transparent\' id=\'svg5\' xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 476 612\'>
            <a href=\'/cope-additions/5/lot/15\' id=\'imgLink\' target=\'iframe\' (click)=\'linkOpen()\'><rect class=\'lot-for-sale\' x=\'64\' y=\'40\' width=\'125.5\' height=\'95\' alt=\'B-1\'><title>lot for sale</title></rect></a>
        </svg>`

Instead of opening in an iframe, the link opens in a new tab, except on mobile, where it doesn't open at all. Before moving the SVG to a TS file (it was directly in the HTML file), the link also didn't open on mobile, although linkOpen() worked/works as expected when using the Chrome inspector to preview as a device.

Anna
  • 11
  • 3

3 Answers3

2

My understanding of the problem is that you need to add an eventListener for click to #imgLink. But you can't do that until it exists, you need the element to be in the DOM to add the event. Afaik, the (click) approach wont work for the reasons Sam stated.

https://stackblitz.com/edit/angular-bhbwkd

This shows conceptually how you can do this. The issue being it involves the inner setTimeout and that is less than beautiful. So the problem is basically just timing.

A way to achieve this would be with a directive, setting the click event in ngAfterViewInit (https://angular.io/guide/lifecycle-hooks). It would probably need an output also (https://angular.io/guide/component-interaction#parent-listens-for-child-event). Another alternative is a full blown component whose template is the svg from project-details.ts. You could then capture the click event 'normally'.

Some other suggestions:

  • Moving the link outside the svg to a parent element (not sure if this will work for your use case)
  • Moving project-detais to a .svg file and using an img tag.

Why it mightn't have worked on mobile: who knows mobile is weird (this is coming form someone whose full time job is developing for mobile with angular). But seriously here are some links that might help https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent, https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Using_Touch_Events. In general if you get a touch event you should also get a click event. It's possible that SVGs are magic. I'd suggest trying to capture all the touch/click events to see which are working and their order then trying to figure out why. If you are ever going to do anything anyway complicated that needs good mobile support http://hammerjs.github.io/ is a good solution.

Endnote: If adding click events manually I'd sugggets using Renderer2 rather than manipulation directly https://medium.com/@kmathy/angular-manipulate-properly-the-dom-with-renderer-16a756508cba explains better than I can.

1

I think that that HTML is binded before that dynamic HTML is added. Try adding a listener in your component controller (project.component.ts)

Here is the link for that:

Dynamically add event listener

Sam Alexander
  • 504
  • 1
  • 6
  • 24
  • I'm still not getting anything to work. I defined `@ViewChild('imgLink') imgLink;` and added `this.renderer.listen(this.imgLink.nativeElement, 'click', (this.linkOpen))` to ngOnInit which gave me the error "Cannot read property 'nativeElement' of undefined at ProjectComponent.push../src/app/cope-additions/project/project.component.ts.ProjectComponent.ngOnInit". I also tried adding the listener to ngAfterViewInit but get the same error. – Anna Jan 14 '19 at 16:34
  • [nativeElement will always return undefined](https://github.com/vaadin/vaadin-grid/issues/411#issuecomment-233113374) because it is inside an *ngIf. – Anna Jan 15 '19 at 17:11
0

Putting the click event on the div that calls the innerHTML and leaving the target on the SVG a elements did the trick, none of the listeners needed.

To fix the mobile issue, I duplicated the SVG, used typescript to call it on mobile only, and added "xlink:" to the href because iOS requires that. Xlink is depricated, but nothing else seems to work.

Anna
  • 11
  • 3