1.2. Component Scrollspy - quan1997ap/angular-app-note GitHub Wiki

USE

  1. Tham số truyền vào scrollspy
    [required] scrollOn: string =  'currentElement' | 'body' => bắt sự kiện scroll trên div chứa directive, hoặc body. Hiện tại support trên 'body'
    [required] scrollSpySectionId: id của thẻ chứa nội dung scroll ( không phụ thuộc vào tham số  scrollOn )
    [required] tagIDList: danh sách id của section. Dựa vào danh sách này để navbar check đã scroll đế item nào. 
      Ex: tagIDList = [ 'id1', 'id2' ] or [[ 'tab1-id1', 'tab1-id1id2' ],[ 'tab2-id1id1', 'tab2-id2' ] ]
          Gom các id thành 1 mảng hoặc tách thành các mảng của tab

    Event click sidebar-menu: scrollSpyService.scrollTo('section1')": scroll xuống thẻ có id = 'section1'
    Note:
    Để giải quyết vấn đề khi scroll xuống đáy, nhưng navbar vẫn chưa check được sự kiện. Do con trỏ đã chạm đáy, nhưng phần tử vẫn chưa chạm top.
    Cần thêm phần footer dài để bù vào khoảng trống bên dưới.

HTML

    <div style="min-height: 100vh;" 
         id="scrollspy-section" [scrollSpySectionId]="'scrollspy-section'" 
         [scrollOn]="'body'" scrollSpy 
         [tagIDList]="['section1', 'section2', 'section3', 'section4']" 
         (sectionChange)="scrollSpyService.onSectionChange($event)"
    >
        <div class="row scrollspy">
            <div class="col-sm-12 col-md-12 col-lg-9 main-content">
                <div class="page-header-text">
                    Scrollspy + Default page
                </div>
                <hr>
                <br>
                <div id="section1">
                  <div class="title-text">1. Section1</div>
                  <div>Section1 Content</div>
                </div>
                <div id="section2">
                  <div class="title-text">2. Section2</div>
                  <div>Section2 Content</div>
                </div>
            </div>
            <div class="col-sm-12 col-md-12 col-lg-3 position-relative">
                <div class="position-fixed right-menu">
                    <div class="header-sidebar">
                        CONTENT
                    </div>
                    <div (click)="scrollSpyService.scrollTo('section1')"  [ngClass]="{'right-menu-item': true, 'active': scrollSpyService.currentSection==='section1'}" >
                      Section1
                    </div>
                    <div (click)="scrollSpyService.scrollTo('section2')" [ngClass]="{'right-menu-item': true, 'active': scrollSpyService.currentSection==='section2'}" >
                      Section2
                    </div>
                </div>
            </div>
        </div>
    </div>

TS - COMPONENTS

    import { ScrollSpyService } from 'path to -> scroll-spy.service';
    import { SetHeaderService } from 'path to -> set-header.service';
    import { Component, OnInit } from '@angular/core';

    export class ComponentName implements OnInit {

      constructor(
        public scrollSpyService: ScrollSpyService,
          ) {

      }

      ngOnInit() {
        // Sử dụng khi có fixed header hoặc có tab. Khi load lại page sẽ đưa về tab mặc định.
        this.scrollSpyService.setCurrentTabIndex(0);
      }

      setHeader(){
        this.setHeaderService.setHeader(
          {
            header: 'Scrollspy + Default page',
            description: 'Scrollspy + Default page'
          }
        );
      }

    }

TS - MODULES

import { ScrollSpyModule } from '../../shared/services/scroll-spy/scroll-spy.module';

imports: [
   ScrollSpyModule
]

CONFIG CODE

scroll-spy.directive.ts

import { Directive, Input, EventEmitter, Output, ElementRef, HostListener } from '@angular/core';

@Directive({
    selector: '[scrollSpy]'
})
export class ScrollSpyDirective {
    /*
      params:
      [required] scrollOn: string =  'currentElement' | 'body' => bắt sự kiện scroll trên div chứa directive, hoặc body. Hiện tại support trên body
      [required] scrollSpySectionId: id của thẻ chứa nội dung scroll ( không phụ thuộc vào tham số  scrollOn )
      [required] tagIDList: danh sách id của section. Dựa vào danh sách này để check đã scroll đế item nào
    */
    @Input() public scrollOn: string = 'body';
    @Input() public scrollSpySectionId: string = '';
    @Input() public tagIDList: string[] = [];
    @Output() public sectionChange = new EventEmitter<string>();
    timeOutScoll: any;

    private currentSection: string;

    constructor(private _el: ElementRef) { }

    @HostListener('scroll', ['$event'])
    onScroll(event: any) {
        if (this.scrollOn === 'currentElement') {

        }
    }

    @HostListener("window:scroll", ['$event'])
    onWindowScroll(event: any) {
        if (this.scrollOn === 'body') {
            const scrollTop = window.pageYOffset; // vị trí của thanh scroll
            const parentOffset = document.getElementById(this.scrollSpySectionId).offsetTop;
            // check elementID match currentSection => change currentSection => emitvalue
            let currentSection: string;
            const elementChecks: any = this.tagIDList.map(tagId => document.getElementById(tagId));
            elementChecks.forEach(element => {
                if (element  && element.offsetTop && element.offsetTop !== undefined && ((element.offsetTop - parentOffset) <= scrollTop)) {
                    currentSection = element.id;
                }
            });
            if (currentSection !== this.currentSection) {
                this.currentSection = currentSection;
                this.sectionChange.emit(this.currentSection);
            }
        }
    }

    ngOnChanges(changes): void {
      if(changes && changes.tagIDList && changes.tagIDList.currentValue && changes.tagIDList.currentValue.length){
        this.tagIDList = this.tagIDList.flat();
      }
        
    }

}

scroll-spy.service.ts

import { Injectable } from '@angular/core';
import { MatTabChangeEvent } from '@angular/material/tabs';

@Injectable({
  providedIn: 'root'
})
export class ScrollSpyService {
  tabSelectedIndex = 0;

  public currentSection = '';
  constructor(
  ) {
  }

  setCurrentTabEvent($event: MatTabChangeEvent) {
    window.scrollTo(0, 0);
    this.tabSelectedIndex = $event.index;
  }

  setCurrentTabIndex(tabIndex) {
    this.tabSelectedIndex = tabIndex;
  }

  onSectionChange(sectionId: string) {
    this.currentSection = sectionId;
  }

  scrollTo(sectionId) {
    this.currentSection = sectionId;
    const element = document.getElementById(sectionId);
    const offsetHeader = document.querySelector('#app-header')['offsetHeight']; // header-height
    const bodyRect = document.body.getBoundingClientRect().top;
    const elementRect = element.getBoundingClientRect().top;
    let elementPosition = elementRect - bodyRect - offsetHeader;
    if (document.querySelector('#fixed-tab')) {
      const offsetFixedTabHeader = document.querySelector('#fixed-tab')['offsetHeight']; // header-height
      elementPosition = elementPosition - offsetFixedTabHeader;
    }
    window.scrollTo({
      top: elementPosition,
      behavior: 'smooth'
    });
  }

  getCurrentSection() {
    return this.currentSection;
  }

  setCurrentSection(section) {
    this.currentSection = section;
  }

}

scroll-spy.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ScrollSpyDirective } from './scroll-spy.directive';
import { ScrollSpyService } from './scroll-spy.service';

@NgModule({
    imports: [
        CommonModule,
    ],
    declarations: [
        ScrollSpyDirective
    ],
    exports: [
        ScrollSpyDirective
    ],
    providers: [
        ScrollSpyService
    ],
})
export class ScrollSpyModule { }

⚠️ **GitHub.com Fallback** ⚠️