3

I need some help concerning an externally changed DOM within an Angular2 RC1 web app. The scenario is simple: I do have a component with an according template that contains an empty div with an ID like this

<div id="my-svg-canvas"></div>

The typescripted component has an ngOnInit method that triggers a function from a plain JS file that is included via script-tag in the index.html. That function renders an SVG into the above listed "div" tag. The SVG contains some a-elements with on-click directives that reference several methods in my component.

That worked fine back then in Angular1 after calling $scope.$apply() after the SVG was completely written. Now, in Angular2 the respective methods in my component are not triggered.

I know, that ZoneJS is now responsible for detecting changes in the DOM. I was hoping that this is done automatically for the whole DOM rendered inside an Angular-managed component. Next try was to inject NgZone in my component and explicitly run the rendering function therein, like this:

ngOnInit() {
  this.ngZone.run(() => {renderSVG(this.myData, "my-svg-canvas");})
}

No success either :-/ Moreover, I registered a MutationObserver (see this thread: Angular2 Directive: How to detect DOM changes), since the problem occurred a little similar to me. It hasn't fired either, though.

I have no clue where to look for, no error message to cling to :-???

Note: It was necessary to use "on-click" instead of "(click)" since the SVG rendering framework refused to set invalid attributes. Obviously attributes starting with a bracket are fine for HTML, but not for XML. However, this should not be the problem, since both directives are perfectly exchangable.

PS:

The rendering method called makes use of the SVG.js framework and looks like this:

function renderSVG(dataParam, elementIdParam) {
  draw = SVG(elementIdParam).spof();
  initAndPaint(draw, dataParam);
  return draw; 
 }

And the tag generated by the SVG.js lib

<a id="SvgjsA1033" on-click="showVal('W1')" class="union">

When I paste the generated SVG code into my template, everything works fine.

Community
  • 1
  • 1
Jan B.
  • 6,030
  • 5
  • 32
  • 53

3 Answers3

3

I don't think Angular is going to parse your dynamically added HTML (see Angular2 - catch/subscribe to (click) event in dynamically added HTML).

So, after you call renderSVG(), you'll probably need to traverse the DOM and find the newly added a elements and then manually add (and later remove) an event handler for each one.

See Dynamically add event listener in Angular 2.

For a more involved approach, see Equivalent of $compile in Angular 2.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • The solution provided in the first links is really giving me the creeps :-) I gave the DynamicLoaderComponent a chance and it actually works, although it feels like a time warp back in the middle ages compared to the one-liner in Angular1. Fair enough, we're dealing with the RC, a better solution should be presented though. – Jan B. May 27 '16 at 20:15
  • Thanks Mark! I've added the implementation to your post and flagged it as "accepted" – Jan B. May 27 '16 at 20:24
  • @user3802631, I completely agree. We need something simpler than the DynamicLoaderComponent. It looks like your edit was rejected. I suggest you add it as a separate answer. – Mark Rajcok May 28 '16 at 02:11
2

I would move your code into the ngAfterViewInit hook method of your component:

ngAfterViewInit() {
  this.ngZone.run(() => {renderSVG(this.myData, "my-svg-canvas");})
}

Moreover you should reference your DOM element using the ViewChild decorator:

<div #mySvgCanvas"></div>

and

@ViewChild('mySvgCanvas')
mySvgCanvasElt: ElementRef;

ngAfterViewInit() {
  var elt = this.mySvgCanvasElt.nativeElement;

I don't think that using zones explicitly is required here...

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
1

Mark Rajcok's link (see "For a more involved approach") to a solution using DynamicLoaderComponent in the accepted answer works. As a closure for this thread this is my implementation:

constructor(private loader: DynamicComponentLoader, private injector:Injector) {}

ngAfterViewInit() {
  let tournamentCanvas = document.createElement("canvas");
  renderKOTournamentSVG(this.tournament, tournamentCanvas);

  @Component({
    selector: 'canvas-component',
    template: tournamentCanvas.outerHTML)}
  class CanvasComponent {}

  this.loader.loadAsRoot(CanvasComponent, "canvas-component", this.injector);
 }
Jan B.
  • 6,030
  • 5
  • 32
  • 53