File

content-templates/page-section/src/lib/services/page-section.service.ts

Description

Service that tracks all page sections on a page

Index

Properties
Methods

Methods

addSection
addSection(section: PageSectionInstance)

Adds a section to be tracked

Parameters :
Name Type Optional Description
section PageSectionInstance No

New section

Returns : void
removeSection
removeSection(section: PageSectionInstance)

Removes a section and stops tracking changes to it

Parameters :
Name Type Optional Description
section PageSectionInstance No

Existing section

Returns : void

Properties

Readonly linkableSections
Type : unknown
Default value : computed(() => this.filterLinkableSections(this._sections()))

All sections that have an anchor id

Readonly sections
Type : unknown
Default value : this._sections.asReadonly()

All sections

Readonly sortedSections
Type : unknown
Default value : computed(() => this.sortSectionsByDomPosition(this.linkableSections()))

All sections with an anchor id sorted in dom order

import { computed, ElementRef, Injectable, Signal, signal } from '@angular/core';

/** Page section instance */
export interface PageSectionInstance {
  /** Section label */
  tagline: Signal<string>;
  /** Section <hx> level */
  level: Signal<number>;
  /** Section anchor */
  anchor: Signal<string | undefined>;
  /** Section element */
  elementRef: Signal<ElementRef<Element>>;
}

/**
 * Helper for getting a page section's html element
 *
 * @param section A page section
 * @returns The section element
 */
export function getSectionElement(section: PageSectionInstance): Element {
  return section.elementRef().nativeElement;
}

/** Service that tracks all page sections on a page */
@Injectable()
export class PageSectionService {
  /** All sections (mutable signal) */
  private readonly _sections = signal<PageSectionInstance[]>([]);
  /** All sections */
  readonly sections = this._sections.asReadonly();
  /** All sections that have an anchor id */
  readonly linkableSections = computed(() => this.filterLinkableSections(this._sections()));
  /** All sections with an anchor id sorted in dom order */
  readonly sortedSections = computed(() => this.sortSectionsByDomPosition(this.linkableSections()));

  /**
   * Adds a section to be tracked
   *
   * @param section New section
   */
  addSection(section: PageSectionInstance): void {
    this._sections.update((sections) => [...sections, section]);
  }

  /**
   * Removes a section and stops tracking changes to it
   *
   * @param section Existing section
   */
  removeSection(section: PageSectionInstance): void {
    this._sections.update((sections) => sections.filter((s) => s !== section));
  }

  /**
   * Filters sections that have an anchor id
   *
   * @param sections All sections
   * @returns Sections with an anchor id
   */
  private filterLinkableSections(sections: PageSectionInstance[]): PageSectionInstance[] {
    return sections.filter((section) => section.anchor());
  }

  /**
   * Sorts sections by the order in the dom.
   *
   * @param sections Unsorted sections
   * @returns Sorted sections
   */
  private sortSectionsByDomPosition(sections: PageSectionInstance[]): PageSectionInstance[] {
    sections = [...sections];
    sections.sort((section1, section2) => {
      const el1 = getSectionElement(section1);
      const el2 = getSectionElement(section2);
      const position = el1.compareDocumentPosition(el2);
      return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
    });
    return sections;
  }
}

results matching ""

    No results matching ""