angularng-bootstrap

ngBootstrap ScrollSpy with sticky top and no height


How do I get ngbScrollSpy to work with a link list that uses sticky top, and no height on the ngbScrollSpy element?

Here is a StackBlitz showing the setup.

https://stackblitz.com/edit/angular-jqkeev9r-o9aky4bl?file=src%2Fapp%2Fscrollspy-items.ts,src%2Fapp%2Fscrollspy-items.html

I think I need to set some kind of container or target to the body element.

Code:

<div class="row">
    <div class="col-4">
        <div id="list-example" class="list-group sticky-top">
            <button type="button" class="list-group-item list-group-item-action" [ngbScrollSpyItem]="[spy, 'items-1']">
                1 - button
            </button>
            <button type="button" class="list-group-item list-group-item-action" [ngbScrollSpyItem]="spy" fragment="items-2">
                2 - button
            </button>
            <a type="button" class="list-group-item list-group-item-action" [ngbScrollSpyItem]="spy" fragment="items-3">
                3 - link no href
            </a>
            <a
                class="list-group-item list-group-item-action"
                href
                [class.active]="spy.active === 'items-4'"
                (click)="spy.scrollTo('items-4'); $event.preventDefault()"
            >
                4 - link no directive
            </a>
            <a routerLink="." class="list-group-item list-group-item-action" [ngbScrollSpyItem]="spy" fragment="items">
                5 - routerLink example
            </a>
        </div>
    </div>
    <div class="col-8">
        <div ngbScrollSpy #spy="ngbScrollSpy" rootMargin="16px" class="bg-light p-3 rounded-2 mb-3">
            <h4 ngbScrollSpyFragment="items-1">First heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
            <h4 ngbScrollSpyFragment="items-2">Second heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
            <h4 ngbScrollSpyFragment="items-3">Third heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
            <h4 ngbScrollSpyFragment="items-4">Fourth heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
        </div>
    </div>
</div>

Solution

  • Customizing requires using NgbScrollSpyService instead of directive (or putting directive on body/desired container...), and if root is not provided, it will use document element by default (see options to tweak it to your needs). For example, use ids, and pass them as fragments:

    <h4 id="items-1">First heading</h4>
    
    fragments = ['items-1', 'items-2', 'items-3', 'items-4'];
    
    this.spy.start({
        fragments: this.fragments
    });
    

    and then to highlight active link bound CSS class to spy.active:

    <button [class.active]="'items-1' === spy.active" type="button" class="list-group-item list-group-item-action">
            1 - button
    </button>
    

    service:

    import { Component, inject } from '@angular/core';
    import {
      NgbScrollSpyModule,
      NgbScrollSpyService,
    } from '@ng-bootstrap/ng-bootstrap';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'ngbd-scrollspy-items',
      imports: [NgbScrollSpyModule, FormsModule],
      templateUrl: './scrollspy-items.html',
      providers: [NgbScrollSpyService],
    })
    export class NgbdScrollSpyItems {
      
      fragments = ['items-1', 'items-2', 'items-3', 'items-4', 'items-5'];
    
      spy = inject(NgbScrollSpyService);
    
      ngAfterViewInit() {
    
        this.spy.start({
          fragments: this.fragments,
        });
    
      }
    }
    

    template:

    <div class="row">
    <div class="col-4">
        <div id="list-example" class="list-group sticky-top">
            <button type="button" class="list-group-item list-group-item-action"  [class.active]="'items-1' === spy.active">
                1 - button
            </button>
            <button type="button" class="list-group-item list-group-item-action"  [class.active]="'items-2' === spy.active">
                2 - button
            </button>
            <a type="button" class="list-group-item list-group-item-action"  [class.active]="'items-3' === spy.active">
                3 - link no href
            </a>
                    <a
                    class="list-group-item list-group-item-action"
                    href
                    [class.active]="spy.active === 'items-4'"
                    (click)="spy.scrollTo('items-4'); $event.preventDefault()"
            >
                    4 - link no directive
            </a>
            <a routerLink="." class="list-group-item list-group-item-action"  [class.active]="'items-5' === spy.active">
                5 - routerLink example
            </a>
        </div>
    </div>
    <div class="col-8">
        <div  class="bg-light p-3 rounded-2 mb-3">
            <h4 id="items-1">First heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
            <h4 id="items-2">Second heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
            <h4 id="items-3">Third heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
            <h4 id="items-4">Fourth heading</h4>
            <p>
                Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth
                master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
                keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat
                salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.
            </p>
        </div>
    </div>
    </div>
    

    demo