Ion-virtual-scroll does not render element when combined with ng-template

Hello all,
Lately I have been trying to implement a custom generic virtual scroll component combined with an infinite scroll.
I tried to make it generic, in a way that, in my virtual scroll component’s inputs, there’s a variable named templateName, that is the template that will be rendered in the virtual-scroll.
But I’m having this error that I can’t seem to understand correctly… :

index-7a8b7a1c.js:1938 Error: virtual element was not created
    at getElement (ionic-angular.js:3440)
    at ionic-angular.js:3376
    at ZoneDelegate.invoke (zone-evergreen.js:364)
    at Object.onInvoke (core.js:28574)
    at ZoneDelegate.invoke (zone-evergreen.js:363)
    at Zone.run (zone-evergreen.js:123)
    at NgZone.run (core.js:28458)
    at IonVirtualScroll.nodeRender (ionic-angular.js:3372)
    at doRender (ion-virtual-scroll.entry.js:79)
    at VirtualScroll.writeVS (ion-virtual-scroll.entry.js:421) undefined

Here’s some fragments of my code :
virtual-scroll.component.ts

import {
  Component,
  EventEmitter,
  Input,
  OnInit,
  TemplateRef
} from '@angular/core';

@Component({
  selector: 'app-virtual-scroll',
  templateUrl: './virtual-scroll-generic.component.html',
  styleUrls: ['./virtual-scroll-generic.component.scss']
})
export class VirtualScrollGenericComponent implements OnInit {
  @Input() items: any[];

  @Input() itemHeight: string;

  @Input() scrollThreshold: string;

  @Input() templateName: TemplateRef<any>;

  @Input() onScrollStart: EventEmitter<void> = new EventEmitter<void>();

  ngOnInit(): void {
    console.log(this);
  }

  infiniteScroll(): void {
    this.onScrollStart.emit();
  }
}

virtual-scroll.component.html

<ion-virtual-scroll [items]="items" [approxItemHeight]="itemHeight">
  <ng-container *virtualItem="let item">
    <ng-container
      [ngTemplateOutlet]="templateName"
      [ngTemplateOutletContext]="{ item: item }"
    ></ng-container>
  </ng-container>
</ion-virtual-scroll>

<ion-infinite-scroll
  [threshold]="scrollThreshold"
  (ionInfinite)="infiniteScroll()"
>
  <ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Loading..">
  </ion-infinite-scroll-content>
</ion-infinite-scroll>

Here’s the component where virtual-scroll component is being called :slight_smile:

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title> Virtual Scroll </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Virtual Scroll</ion-title>
    </ion-toolbar>
  </ion-header>

  <ng-template #template let-item="item">
    <ion-item detail="true">
      <ion-label>
        <h2>{{ item.name }}</h2>
      </ion-label>
    </ion-item>
  </ng-template>

  <app-virtual-scroll
    [items]="items"
    itemHeight="80px"
    scrollThreshold="100px"
    (onScrollEvent)="loadData()"
    [templateName]="template"
  ></app-virtual-scroll>
</ion-content>

I also made a repository, that you can clone over here : GitHub - canserkanuren/virtual-scroll-generic

If anyone of you understands why, could you possibly give me some hints about the ongoing problem ?
Thanks a lot.

Add the following to the compilerOptions stanza of your tsconfig.json:

    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true,

…and add this at the same level as compilerOptions:

  "angularCompilerOptions": {
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }

That should cause, among other things, the problem you are currently encountering to become a fatal build-time error. Although it won’t get caught in that dragnet, I would also suggest getting rid of all the anys in your code. You can use TypeScript generics for a better alternative.

What is happening:

Angular evaluates templates before you think it does. It gets angry when you lie to it. You told it that items was an array. It isn’t. It will be, eventually. But not fast enough. Always assign some sane value to every public property in your controller (OK, technically, for the purposes of this discussion, every property that’s accessed by a template, but I still think in terms of “every public property”).

The strict compiler options I recommended at the beginning of this post should turn failure to do that into a fatal error. You need to assign something like [] to items at the point of declaration.

Hello,

I did all what you told me to do, but I still get the same error in my code…

In this commit, I added all the missing configurations in my tsconfig.json.

And in this one, I tried to add a default template to my templateName variable (thinking that the problem may be coming from there) but I still get the same error.

<ng-container> doesn’t actually generate DOM elements. What happens if you wrap things so that (as discussed here) the direct child of <ion-virtual-scroll> is a <div>?

The error you are seeing seems to be coming from here, which I read as saying “I’m looking for an actual Element under me, but not finding any”.

Ok so, after changing the ng-container to a simple div or section, the behavior is the next one :

  • Application loads
  • Items appear for like, 0.1 seconds, and then disappear
  • Refreshing the web page still gets me the same behavior.

I tried to look into the DOM : the elements are there, and I can see my items in the DOM but they’re not displayed on the page.

Just a heads up, we’re in the process of deprecating ion-virtual-scroll in favor of @angular/cdk’s virtual scroller. Consider migrating to that instead.

1 Like

I guess that I’ll try Angular’s virtual scroll then.
Thank you for your help guys ! :slight_smile: