0

TL;DR:

I am rendering a BioDigital HumanAPI anatomical model in my Angular 5 app within an iFrame. I instantiate API object using:

this.human = new HumanAPI(iFrameSrc);

There's an API function human.on(...) that can be used to register click events from within iFrame (like picking objects from the model, etc.). I need this function to be able to listen to the events at all times. I do the object instantiation and put this function within ngOnInit() and it works, but when I change the source of iFrame to render a different model, this function stops working. Where should I put this listening function so that its logic is available at all times?

Longer version:

I am developing an Angular app using BioDigital HumanAPI. The basic idea here is that HumanAPI provides several anatomical models which can be rendered in a web-app using an iFrame (an example here). The src of this iFrame is a link, something like:

https://human.biodigital.com/widget?m=congestive_heart_failure

Since I want the user of my Angular app to be able to view several of such models, I have a list of these URLs, and based on user's selection, I update the src of iFrame, using a function updateFrameSrc which has following code:

iframeSrc: SafeUrl;
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(newUrl);

Finally (the question is coming, please stay with me), in order to manipulate and register different click events and user interactions with the model within the iFrame itself, we make a HumanAPI object like this:

this.human = new HumanAPI(iFrameID);

This lets us use API event listener functions like human.on('scene.picked') to register and save click events (like shown in the example I referenced above). All of this is working fine.

The problem is that since I initialize the human object in the ngOnInit() function and also put the human.on('scene.picked') function there, I cannot register the click events after the iFrame source is changed. As I understand it, ngOnInit() is only called once when the component is first initialized, so may be the listening logic of human.on is not available after updating the iFrame source? I have tried placing the logic in different life-cycle hooks but its doesn't work.

My current work-around is to re-call the ngOnInit() function after updating the iFrame source, and it works that way, but I believe this is against the standard life-cycle management practices.

My questions are:

  • It is acceptable to re-call the ngOnInit() function from within the component logic?
  • If not, where should I place a JavaScript API function that listens to click events from an iFrame at all times, even after the source of that iFrame has been changed?
Uzair A.
  • 1,586
  • 2
  • 14
  • 30
  • why can't you just move the two lines you need from ngOnInit() into a separate function and call that function in both ngOnInit and in your updateFrameSrc function? – Abraham Al-Dabbagh Jan 30 '18 at 06:36
  • @AbrahamAl-Dabbagh Okay, this was the answer after all. I had tried it this way but I was forgetting to initialize the `human` object again after updating `iFrame` source. Kindly add this as an answer so that I may accept it! – Uzair A. Jan 30 '18 at 07:36

2 Answers2

1

If you are looking for near real time you will want this to occur in the NgOnChanges life-cycle hook. Be advised this is expensive.

If slightly less "near real time" is acceptable, I would advise wiring up a rapid delay subject Observable.Interval(500) (also, but slightly less expensive) at the time of Component initialization NgOnInit.

Please DO NOT circumvent the hooks by re-calling ngOnInit.

If you have additional questions let me know.

iHazCode
  • 622
  • 4
  • 15
  • I tried doing it using `ngOnChanges()` but as you said, it is _very_ expensive for my app. The thing is, putting listener logic in `ngOnInit()` works fine as long as I don't change the `iFrame` source and without using delays or something that, kind of, _force listens_ to events (like on every mouseDown event). Recalling `ngOnInit()` works too, so I was thinking that may be some less expensive workaround exists as well. – Uzair A. Jan 30 '18 at 05:21
  • I just did something similar by bootstrapping a periodic task (Observable.Interval) inside of the NgOnInit. It will work, and for most use cases the DOM will render fast enough that the operator/user will not perceive any delay over putting it in the ngOnChanges. Please do not recall ngOnInit. It causes problems that may not be immediately apparent. – iHazCode Jan 30 '18 at 05:24
1

As suggested in an earlier comment, you can just move the code in ngOnInit() to a separate function and call that function from both ngOnInit() as well as your update function.

Don't forget to re-initialize the human object of HumanAPI in that function as well, when updating the iFrame source.

Re-calling ngOnInit() should be avoided as it circumvents the acceptable functionality of lifecycle hooks, as mentioned by @iHazCode.

Uzair A.
  • 1,586
  • 2
  • 14
  • 30