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.
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:
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"}
];
}
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>
}
}
6