1

In older versions of Angular, where we used to have module-based applications, it would load a module once and even if you close components that are exported by that module - it wouldn't load that module again, even if you opened the component once again. But now that we're slowly migrating to standalone components - I'm facing an interesting behavior regarding the lazy loading "feature". In the GIF below, by clicking a dropdown arrow - I'm rendering three instances of the same component, that has its' images and styles. As you can see, for a microsecond there, the layout is not styled initially and a scrollbar appears and disappears after the component's styles are fully loaded.

Lazy loading

If you take a look at the network tab, we can see that it loads images of that component once and css file of that component each time we try to render it:

Network tab

So as you can see, my goal here is to prevent that behavior from happening over and over again, ideally I would want to load it all initially, when the page is loaded. Here's the code for the component that's lazy loaded:

<div class="hive-info">
  <img [src]="'beehive' | svgPath" class="hive-icon" />
  <div class="hive-name">{{ hiveName }}</div>
  <div class="hive-weight">{{ hiveWeight }}</div>
  <action-icon iconPath="chart-icon" iconClass="img standard-img" iconTitle="Chart"  />
</div>
@Component({
  selector: 'hive-info',
  imports: [SvgPathPipe, ActionIconComponent],
  templateUrl: './hive-info.component.html',
  styleUrl: './hive-info.component.scss'
})
export class HiveInfoComponent {
  @Input() hiveName: string = 'Beehive 1';
  @Input() hiveWeight: string = '20 kg'
}

And here's how that component is used in the other component:

@if (isDropdownToggled) {
  @for (hive of hives; track hive.name) {
    <hive-info [hiveName]="hive.name" [hiveWeight]="hive.weight" />
  }
}
@Component({
  selector: 'hive-group',
  imports: [ActionIconComponent, SvgPathPipe, FormsModule, HiveInfoComponent],
  templateUrl: './hive-group.component.html',
  styleUrl: './hive-group.component.scss'
})
export class HiveGroupComponent {
  @Input() groupName: string = '';

  isEditing: boolean = false;
  isDropdownToggled: boolean = false;
  editableGroupName: string = '';

  onEditClick(): void {
    this.isEditing = true;
    this.editableGroupName = this.groupName;
  }

  onConfirmClick(): void {
    // TODO: send request to back-end to update group name (emit event with new group name to the parent component)
    this.groupName = this.editableGroupName;
    this.isEditing = false;
  }

  onCancelClick(): void {
    this.isEditing = false;
  }

  toggleDropdown(): void {
    this.isDropdownToggled = !this.isDropdownToggled;
  }

  hives = [
    {name: "Hive 1", weight: "20 kg"},
    {name: "Hive 2", weight: "15 kg"},
    {name: "Hive 3", weight: "25 kg"}
  ];
}

1 Answer 1

2

Based on the comments, you can try a hybrid approach, where the hidden attribute does not destroy any elements and we use:

<div [hidden]="!isDropdownToggled">
  @defer(when isDropdownToggled; prefetch on idle) {
    @for (hive of hives; track hive.name) {
      <hive-info [hiveName]="hive.name" [hiveWeight]="hive.weight" />
    }
  } @placeholder {
    <p>Loading...</p>
  }
</div>

The when toggle will display the content only when the isDropdownToggled is true and the prefetch on idle will preload your content so that it does not slow down your UI.


You can try the hidden attribute which does not destroy the DOM elements.

<div [hidden]="!isDropdownToggled">
  @for (hive of hives; track hive.name) {
    <hive-info [hiveName]="hive.name" [hiveWeight]="hive.weight" />
  }
</div>

If you face any performance issues due to large number of DOM elements, you can use @defer which has the @placeholder which you can use to show a spinner until the elements are loaded successfully.

Deferred loading with @defer

@if (isDropdownToggled) {
  @defer {
    @for (hive of hives; track hive.name) {
      <hive-info [hiveName]="hive.name" [hiveWeight]="hive.weight" />
    }
  } @placeholder {
    <p>Loading...</p>
  }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the solution, [hidden] worked for me, but that's not what I'm looking for. Because with [hidden] we will still have a large DOM without any reason, just because it's hidden - it doesn't fix the issue. What I'm looking for is to load that component's styles and images once and reuse it with @if and keep the DOM clean.
If there's no way to do that - I'd rather switch back to module based approach and will not use standalone for another few years.
@maksonios I updated with an alternative approach, we can use defer to load the component content only once, it is rare for the end user to expand all the dropdowns, so defer renders the dom only when the user needs it.
Yes, defer sovles the issue, the DOM is cleaning up and it's looking correct. But once again, I simply do not understand, why I have to wait for a few milliseconds every time to load 13 lines of CSS. It's not something that should happen in the moddern front-end frameworks and from what I researched so far - Angular team did not consider this and seems like there's no easy fix to this, so I guess our team will just go back to the usual module-based approach. Thanks for the help though.
Running the app without the hot module replacement fixed the issue for me. It seems that it's a bug that wasn't reproducible in Angular 18. Running the app with ng serve --no-hmr fixed it. Found the existing issue here: github.com/angular/angular/issues/59058
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.