0

I need to use introjs to tour new features on an angular 6 app.

Inside the tour some step target items in ngFor in a child component of the component where I start introjs. The library is able to target child component html elements but seems to not be able to target element inside ngFor. The usage I want is simple I have a li that represent a card item I loop on as :

<div *ngFor="let source of sourcesDisplay; let idx = index" class="col-lg-5ths col-md-4 col-sm-6 col-xs-12">
        <!-- item -->
    <div class="item blue-light">
      <div class="header">
        <div class="categories">
          <div class="truncat">{{source.categories.length > 0? source.categories : 'NO CATEGORY'}}</div>
        </div>
        <a routerLink="{{ organization?.name | routes: 'sources' : source.id }}" class="title">
          <div class="truncat">{{source.name}}</div>
        </a>
        <div class="corner-ribbon blue-light" *ngIf="isARecentSource(source.createdAt)">New</div>
        <div class="corner-fav" [class.is-active]="isSourceFavorite(source.id)"
             (click)="toggleFavorite(source.id)"><em class="icon-heart-o"></em></div>
      </div>
      <a class="content" routerLink="{{ organization?.name | routes: 'sources' : source.id }}">
        <div class="icon">
          <em [ngClass]="source.icon? 'icon-'+source.icon : 'icon-cocktail3'"></em>
        </div>
      </a>
      <div class="actions">
        <ul class="buttons three">
          <li class="icon-font" ><a (click)="openDashboardModal(source)"
                                   [class.disable]="source.powerbi.length === 0" class="btn-effect"><em
            class="icon-chart-bar"></em></a></li>
          <li class="icon-font"><a
            (click)="openDatalakeModal(source)" [class.disable]="source.datalakePath.length === 0"
            class="btn-effect"><em class="icon-arrow-to-bottom"></em></a>
          </li>
          <li class="icon-font"><a routerLink="{{ organization?.name | routes: 'sources' : source.id }}"
                                   class="btn-effect"><em class="icon-info-circle"></em></a></li>
        </ul>
      </div>
    </div>
  </div><!-- /item -->

And I want to target part of this card like a button for example as :

<li class="icon-font" id="step4{{idx}}">
   <a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
     <em class="icon-chart-bar"></em>
   </a>
</li>

and then in my component :

const intro = IntroJs.introJs();
intro.setOptions({
  steps: [
    {
      element: document.querySelector('#step40'),
      intro: 'Welcome to the Data Portal ! Explore and download data accross any division. Let\'s discover the new home page.'
    }]})
intro.start();

Is there something I am doing wrong ? is introjs not able to do it at all ? is there another lib that can do it?

Thank you in advance

Bárbara Peres
  • 585
  • 1
  • 9
  • 26
Dylan Tuna
  • 85
  • 2
  • 6

1 Answers1

2

The sections generated by the *ngFor loop may not be rendered yet in the AfterViewInit event. To detect the presence of these elements, you should use @ViewChildren and monitor the QueryList.changes event.

In the template, define a template reference variable #step instead of id:

<li class="icon-font" #step>
   <a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
     <em class="icon-chart-bar"></em>
   </a>
</li>

In the code, retrieve the elements with @ViewChildren, and subscribe to the QueryList.changes event. You may have to convert the QueryList to an array to access an element with a specific index.

@ViewChildren("step") steps: QueryList<ElementRef>;

ngAfterViewInit() {
  this.startIntroJs(); // Maybe already present
  this.steps.changes.subscribe((list: QueryList<ElementRef>) => {
    this.startIntroJs(); // Created later
  });
}

private startIntroJs(): void {
  if (this.steps.length > 40) {
    const intro = IntroJs.introJs();
    intro.setOptions({
      steps: [
        {
          element: this.steps.toArray()[40].nativeElement,
          intro: 'Welcome to the Data Portal...'
        }
      ]
    });
    intro.start();
  }
}
ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • I tried your solution. It doesn't work, the viewChildren doesn't detect the changes on the ngFor list. My data are loaded asyncly on afterViewInit the list isn't loaded yes and so not rendered yet. I did some research and it seems that viewchildren on ngFor don't detect changes. Do you know any workaround? I don't want to hide my list and do static stuff just for the tour ... – Dylan Tuna Oct 18 '18 at 09:58
  • `ViewChildren` is the good way to detect changes caused by `ngFor` and `ngIf` (see [this stackblitz](https://stackblitz.com/edit/angular-7eyby7)). If it doesn't work for you, please provide a stackblitz showing the problem. – ConnorsFan Oct 18 '18 at 11:54