In Page Navigation

I used in page navigation frequently while working at a former company. The owners often didn’t want new pages for different sections. They didn’t even want parameters appended to the url. The result was to create unique methods of showing/ hiding content. This project is one such concept.

In the TypeScript file below, we retrieve a group of elements. @ViewChildren is akin to document.get… Though it no doubt has many other uses. We retrieve the third line of the HTML, denoted by the “#sections” reference. Necessary to show & hide them. Next, btnGroup references the group of buttons.

Here, we simply pass the section count and add HTML for the content. bar references the progress bar. currentSection for the only visible section. percentages holds the percentage divs.

Math is used to round the percentages. In turn a Math object is needed. Created in the constructor. Notice the first line of the ngAfterViewInit block. ChangeDetection is used because the values are changed during runtime compilation. The length of the bar is set here too. Also in the ngAfterViewInit hook grid columns are set, indicative of how many sections there are. Set by the sectionLength variable. Yes, set any number of sections, and an equal amount of buttons and sections render.

The navigate fn sets the currentSection and the progress bar width. show & hideProgressBar functions adjust the bar opacity and height.

import {
      ChangeDetectorRef,
      Component,
      ElementRef,
      QueryList,
      ViewChild,
      ViewChildren
    } from '@angular/core';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss'],
    })
    export class AppComponent {
      markup: string;
      typescript: string;
      style: string;
    
      @ViewChildren('percentages') percentages: QueryList<ElementRef>;
      @ViewChildren('sections') sections: QueryList<ElementRef>;
      @ViewChild('btnGroup', { static: false }) btnGroup: ElementRef;
      @ViewChild('bar', { static: false }) bar: ElementRef;
    
      // SET TO ANY AMOUNT!
      sectionLength = 3;
    
      barLinkWidth = 0;
      currentSection = 0;
      Math: any;
    
      constructor(private _cd: ChangeDetectorRef) {
        this.Math = Math;
      }
    
      ngAfterViewInit() {
        this._cd.detectChanges();
    
        this.barLinkWidth = 100 / this.sections.length;
        this.bar.nativeElement.setAttribute(
          'style',
          'width:' + this.barLinkWidth + '%'
        );
        this.btnGroup.nativeElement.style.gridTemplateColumns =
          'repeat(' + this.sectionLength + ', auto)';
      }
    
      navigate(sectionIndex: number) {
        this.currentSection = sectionIndex;
        this.bar.nativeElement.style.width =
          this.barLinkWidth * (sectionIndex + 1) + '%';
      }
    
      showProgressBar() {
        this.bar.nativeElement.style.height = '22px';
        this.percentages.forEach((val) => {
          val.nativeElement.style.opacity = 1;
        });
      }
    
      hideProgressBar() {
        this.bar.nativeElement.style.height = '3px';
        this.percentages.forEach((val) => {
          val.nativeElement.style.opacity = 0;
        });
      }
    }

Add the Markup

The markup below loops over an empty array. We use the constructor keyword to initialize the object and pass it the amount of sections. Hiding all but the current section. ngIf unfortunately cannot be used on the same line as Angular restricts one template binding per element. Add the section headline. The progress bar equation is a way of accommodating any number. I’d imagine 2, 3, or 4 is ideal. All of which work beautifully with this line. Divide 100 by the amount of sections. Round that number. Then multiple it by the section indice.

<div id="module-name" class="element-shadow element-margin-top">
    <section
      *ngFor="let item of [].constructor(sectionLength)"
    >
      <div #sections>
        {{ 'Section ' + (currentSection + 1) }}
        <div *ngIf="currentSection === 0" class="section-content">
          <div class="group">
            <label for="name">First Name:</label>
            <input type="text" placeholder="Enter a name" id="name" />
          </div>
          <div class="group">
            <label for="occupation">Occupation:</label>
            <input type="text" placeholder="Your occupation" id="occupation" />
          </div>
          <div class="group">
            <label for="title">Title:</label>
            <input type="text" placeholder="Your title" id="title" />
          </div>
        </div>
    
        <div *ngIf="currentSection === 1" class="section-content">
          <div class="group">
            <label for="city">City:</label>
            <input type="text" placeholder="Your city" id="city" />
          </div>
          <div class="group">
            <label for="state">State:</label>
            <input type="text" placeholder="Your state" id="state" />
          </div>
          <div class="group">
            <label for="zip">Zip Code:</label>
            <input type="text" placeholder="Your zip code" id="zip" />
          </div>
        </div>
    
        <div *ngIf="currentSection === 2" class="section-content">Results...</div>
      </div>
    </section>
    <div id="indicator" #bar></div>
    <div id="percent">
      <div
        #percentages
        *ngFor="let percent of [].constructor(sectionLength); let i = index"
      >
        {{ Math.round((100 / sectionLength) * (i + 1)) + '%' }}
      </div>
    </div>
    <div
      class="btn-group"
      #btnGroup
      (mouseover)="showProgressBar()"
      (mouseout)="hideProgressBar()"
    >
      <button
        *ngFor="let btn of [].constructor(sectionLength); let i = index"
        (click)="navigate(i)"
      >
        {{ 'Section ' + (i + 1) }}
      </button>
    </div>
    </div>

Wrap it Up

Using a separate file for the content would definitely work. We could even remove the ‘Section 1’ and include that in the section content. Form groups would work here too. Images, endpoints, etc. Whatever you decide to do, go for it. Angular is an immensely powerful framework with the ability to do nearly anything imaginable in the UI. Add the CSS below.

#module-name {
      height: 300px;
      border-radius: 6px;
      position: relative;
      font-family: sans-serif;
      overflow: hidden;
    
      #indicator {
        background-color: #313b3f;
        height: 3px;
        position: absolute;
        bottom: 0;
        transition: all 0.5s;
      }
    
      section {
        height: 100%;
        background-color: #FFFFFF;
        border: 1px solid #313b3f;
        border-radius: 6px;
    
        > div {
          font-size: 1.5em;
          width: 100%;
          height: 100%;
          text-transform: uppercase;
          color: #313b3f;
          padding: 20px;
          box-sizing: border-box;
    
          .section-content {
            font: normal 14px sans-serif;
            text-transform: none;
            display: grid;
            grid-template-columns: repeat(3, auto);
            max-width: 500px;
            justify-content: center;
            grid-column-gap: 10px;
            margin: 40px auto 0 auto;
    
            label {
              display: block;
              margin-bottom: 5px;
            }
    
            input {
              padding: 3px;
              color: #313b3f;
              box-sizing: border-box;
              outline: none;
            }
    
            input::placeholder {
              font: normal 12px sans-serif;
            }
          }
        }
      }
    }
    
    .btn-group {
      position: absolute;
      bottom: 35px;
      left: 0;
      right: 0;
      text-align: center;
      display: grid;
      grid-column-gap: 5px;
      width: calc(100% - 30px);
      margin: 0 auto;
    
      button {
        padding: 10px 5px;
        background-color: #03658c;
        color: #FFFFFF;
        border: none;
        border-radius: 6px;
        outline: none;
        cursor: pointer;
      }
    }
    
    #percent {
      display: flex;
      position: absolute;
      width: 100%;
      bottom: 3px;
      font: normal 13px sans-serif;
    
      > div {
        width: 33.3%;
        text-align: center;
        color: #d9a74a;
        opacity: 0;
        transition: opacity 0.5s;
      }
    }
    
    @media screen and (max-width: 600px) {
      #module-name section > div .section-content {
        grid-template-columns: none;
        font-size: 12px;
        margin-top: 5px;
        grid-template-rows: repeat(3, 55px);
      }
    }

I guess the most important take away here is this is a malleable feature that can be used when your boss or the client has an unusual request. This worked particularly well for me when I was working at a startup and I was more often than not pressed for time. Yeah, I could’ve found a library or some pre-written solution, but being able to code a custom, viable feature is rewarding. Fork and customize. Make this in page navigation component your own!

You May Also Like

More From Author

+ There are no comments

Add yours