question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

All iframes in widgets refresh when any widget gains focus (using ng-dynamic-component)

See original GitHub issue

Context

I have defined various widget type components, and I’m using ng-dynamic-component so that each widget is dynamically associated with the component at runtime. One of these components is a youtube iframe widget component, i.e. all widgets that embed youtube videos will use this component. For now, the HTML of this component is simply

<iframe width={{iframeWidth}} height={{iframeHeight}} [src]="getUrl()" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>

Issue

The issue I’m having is that whenever any widget is dragged, resized, or even clicked (so simply when a widget gains focus), all of the iframe widgets refresh their iframe. You can see an example here. Note that this does not happen when I click anywhere else, such as on the background grid. I imagine all widget content is being refreshed, not just the iframes, but since iframes take longer to load it’s only noticeable for them.

I have a feeling it’s something related to ng-dynamic-component and the Angular lifecycle, but I’m new to Angular so it might be something simple I’m not setting up correctly. I’ll share the relevant pieces of code in case that might help.

Relevant Code

I use a similar dashboard array as the Home example in dashboard-component.ts:

this.dashboard = [
  {cols: 2, rows: 1, y: 0, x: 0, widgetType: 'iframe', widgetData: {src: "https://www.youtube.com/embed/H-WEhug-up8", iframeWidth: 240, iframeHeight: 160}},
  {cols: 2, rows: 2, y: 0, x: 2, widgetType: 'gettingStarted'},
  {cols: 1, rows: 1, y: 0, x: 4, widgetType: 'numericQuery'},
  {cols: 1, rows: 1, y: 2, x: 5, widgetType: 'favorites'},
  {cols: 1, rows: 1, y: 1, x: 0, widgetType: 'numericQuery'},
  {cols: 1, rows: 1, y: 1, x: 0, widgetType: 'iframe', widgetData: {src: "https://www.youtube.com/embed/IDaqFiLvcB0", iframeWidth: 640, iframeHeight: 360}},
  {cols: 2, rows: 2, y: 3, x: 5, widgetType: 'gettingStarted'},
  {cols: 2, rows: 2, y: 2, x: 0, widgetType: 'numericQuery'},
  {cols: 2, rows: 1, y: 2, x: 2, widgetType: 'favorites'},
  {cols: 1, rows: 1, y: 2, x: 4, widgetType: 'numericQuery'},
  {cols: 1, rows: 1, y: 2, x: 6, widgetType: 'iframe', widgetData: {src: "https://www.youtube.com/embed/PYOSKYWg-5E", iframeWidth: 340, iframeHeight: 260}}
];

I made a WidgetItemComponent which acts as the ng-dynamic-component facilitator for choosing the correct dynamic component:

@Component({
  selector: 'app-widget-item',
  template: `<ndc-dynamic [ndcDynamicComponent]="component" [ndcDynamicInputs]="inputs"></ndc-dynamic>`,
  styleUrls: ['./widget-item.component.css']
})
export class WidgetItemComponent implements OnInit {

  component : any;
  inputs = { widgetData: {} };

  constructor() {	}

  ngOnInit() 	{
    this.component = this.getComponent();
    if (this.dashboardItem != null) {
      this.inputs.widgetData = this.dashboardItem.widgetData;
    }
  }

  @Input() dashboardItem: GridsterItem;

  getComponent() {
    if (this.dashboardItem != null) {
      if (this.dashboardItem.widgetType == 'iframe') {
        return IframeWidgetComponent;
      }
      else if (this.dashboardItem.widgetType == 'numericQuery') {
        return NumberQueryWidgetComponent;
      }
      else if (this.dashboardItem.widgetType == 'favorites') {
        return FavoritesWidgetComponent;
      }
      else if (this.dashboardItem.widgetType == 'gettingStarted') {
        return GettingStartedWidgetComponent;
      }
    }
  }
}

And finally in iframe-widget-component.ts I have:

@Component({
  selector: 'app-iframe-widget',
  templateUrl: './iframe-widget.component.html',
  styleUrls: ['./iframe-widget.component.css']
})
export class IframeWidgetComponent implements OnInit {

  src: string;
  iframeWidth: number;
  iframeHeight: number;

  @Input() widgetData: any;

  constructor(public sanitizer: DomSanitizer) { }

  ngOnInit() {
    if (this.widgetData != null) {
      this.src = this.widgetData.src;
      this.iframeWidth = this.widgetData.iframeWidth;
      this.iframeHeight = this.widgetData.iframeHeight;
    }
  }
  
  getUrl() {
    return this.sanitizer.bypassSecurityTrustResourceUrl(this.src);
  }
}

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

4reactions
NoMercy235commented, Jul 5, 2018

Hello guys! A little late to the party, but here’s what I think it’s happening. Gridster components are all made with the changeDetectionStrategy set to OnPush if I remember correctly. Which means that they trigger a change detection when their input changes or when an external event affects them (such as a click or a resize). Which means that on every click/resize a change detection will be triggered in the component.

Now, you are trying to get the url by binding it with the getUrl() method. The problem here is that Angular doesn’t know what the result of a function changes, therefore, when a change detection is triggered, the function is executed again to get the result. To avoid this kind of behavior, you will have to execute the function at an earlier point (such as in the constructor or ngOnInit hook) and save the result in a property on the class.

// inside the class
public urlResource: any;

constructor () {...}

ngOnInit(): void {
    this.url = this.sanitizer.bypassSecurityTrustResourceUrl("https://i.ytimg.com/vi/J9QOB6hSI-c/maxresdefault.jpg");
}

Now, even if the change detection is triggered, Angular knows that the url hasn’t changed (since it’s an object with a proper reference) and it won’t try to rerender the component to which the url is binded again.

I hope this helps. ^.^

1reaction
CollinGraf314commented, Jun 26, 2018

I think you’ve stumbled upon a very interesting bug. I’ve taken a look at it and I think that this might actually be related to angular gridster2 as I can’t reproduce any of this behavior without it. I was able to strip out most of your code and still reproduce the issue here: https://stackblitz.com/edit/angular-dktb63.

I removed all the dynamic component related code as well as all styling, all of your gridster options, and all the logic in the iframe component except the getUrl() function. You can still see the bug by checking the console after/during a screen resize.

I also tested in both Chrome and Edge just to check if it was browser specific, and it seems it isn’t. Interestingly, simply having a gridster element in the dom seems to trigger the bug, refreshing elements that are not even inside the grid. This merits more looking into.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Fixing the Iframe Display with Dynamic Widgets - Crocoblock
I want to embed audio or videos using JetEngine meta fields and widgets. However, they are not displayed. Answer.
Read more >
The ultimate guide to iframes - LogRocket Blog
Not a fan of iframes? This post provides an overview of the tag's best features, shows you how to use them, and how...
Read more >
How to Use Stacked Architecture to Build a Flutter Todo App
In Flutter, every widget has a build method that returns its tree of widgets. The build method takes a BuildContext with which it...
Read more >
Shadow DOM v1 - Self-Contained Web Components
Use shadow DOM to compartmentalize an element's HTML, CSS, and JS, thus producing a "web component". Example - a custom element attaches shadow ......
Read more >
How to build custom form controls - Learn web development
Note: We'll focus on building the control, not on how to make the code ... all interactions for every use case for every...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found