Dylan Tuna
Dylan Tuna

Reputation: 95

How to target an item inside ngFor with introjs

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

Upvotes: 1

Views: 828

Answers (1)

Martin Parenteau
Martin Parenteau

Reputation: 73761

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();
  }
}

Upvotes: 3

Related Questions