Search code examples
cssangularangular-materialangular11

Highlight the sidebar item according the progress


I have a sidebar to display the job progress. Now I have three phases.

phases

At the beginning the project has not started yet, so I want all sidebar items are greyed out. I want the current phase is based on the previous phases completion. On the right side there are three buttons. Here is the logic.

  1. Working on phase 1. When I work on the phase 1, the Phase 1 sidebar item is active and the color is green. Phase 2 and Phase 3 items are still inactive.
  2. Working on phase 2. When I click Phase 1 completed button then we go to phase 2, the Phase 2 is colored as green and Phase 1 is still active but just no color.
  3. Working on phase 3. When I click Phase 2 completed button then we go to phase 3, the Phase 3 is colored as green and Phase 1 and Phase 2 are active but just no color.
  4. When we go to the next phase, the previous phase items have their borders visible.

My ts code:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sidenav',
  templateUrl: './sidenav.component.html',
  styleUrls: ['./sidenav.component.css']
})
export class SidenavComponent implements OnInit {

  sidenavWidth = 10;
  ngStyle: string;
  active1: boolean;
  active2: boolean;
  active3: boolean;
  constructor() {

  }

  ngOnInit() {
    this.active1 = false;
    this.active2 = false;
    this.active3 = false;
  }
  submit1() {
    this.active1 = true;
    this.active2 = false;
    this.active3 = false;
    console.log('1');
  }
  submit2() {
    this.active1 = true;
    this.active2 = true;
    this.active3 = false;
    console.log('2');
  }
  submit3() {
    this.active1 = true;
    this.active2 = true;
    this.active3 = true;
    console.log('3');
  }
}

My html:

    <mat-sidenav-container fullscreen>
  <mat-sidenav #sidenav  mode="side" class="example-sidenav" [ngStyle]="{ 'width.em': sidenavWidth }" opened="true">
    <div class="logomain">Project progress</div>
    <mat-nav-list>
      <mat-list-item routerLink="1" [routerLinkActive]="[active1]" >
        <div fxFlex="10"></div>
        <div *ngIf="sidenavWidth > 6" class="sidenav-item">
          <h5 class="lead">Phase 1</h5>
        </div>
      </mat-list-item>

      <mat-list-item routerLink="2" [routerLinkActive]="[active2]">
        <div fxFlex="10"></div>
        <div *ngIf="sidenavWidth > 6" class="sidenav-item">
          <h5 class="lead">Phase 2</h5>
        </div>
      </mat-list-item>

      <mat-list-item routerLink="3" [routerLinkActive]="[active3]">
        <div fxFlex="10"></div>
        <div *ngIf="sidenavWidth > 6" class="sidenav-item">
          <h5 class="lead">Phase 3</h5>
        </div>
      </mat-list-item>
    </mat-nav-list>

  </mat-sidenav>

  <!-- <div class="example-sidenav-content">
    <router-outlet></router-outlet>
    
  </div> -->
  <br><br><br>
 <section>
  <br>
  <div class="example-button-row">
    <button mat-raised-button color="primary" (click) ="submit1()">Phase 1 completed</button>
   </div>
</section>
<mat-divider></mat-divider>
<br>
<section>
    <br>
    <div class="example-button-row">
      <button mat-raised-button color="primary" (click) = "submit2()">Phase 2 completed</button>
    </div>
</section>
<br>
.<section>
    <br>
    <div class="example-button-row">
      <button mat-raised-button color="primary" (click) = "submit3()">Phase 3 completed</button>
    </div>
</section>
<br>
</mat-sidenav-container>

Here is the stackblize example.

My questions. When I click the buttons the side bar item are not greyed out by the condition. Also I am not sure how to apply the green color to the working phase.

Finally I have many items instead of 3 items in the example. I don't want manually to set boolean values in the click events.

Update:

The correct editable stackblitz link https://stackblitz.com/edit/angular-material-with-angular-sidenav-etrylt


Solution

  • It looks like you just need a simple structure to represent your Phases with a property to indicate if it is completed or not.

    interface Phase {
        id: number;
        name: string;
        isComplete: boolean;
    }
    

    Then, when you click a button, toggle the isComplete property.

    You can define an array of Phase objects and use *ngFor to repeat them in your template. This will prevent your template from growing when you add more phases.

    sidebar.component.ts:

      public phases = [
        { id: 1, name: "Phase 1", isComplete: false },
        { id: 2, name: "Phase 2", isComplete: false },
        { id: 3, name: "Phase 3", isComplete: false }
      ];
    

    sidebar.component.html:

    <mat-nav-list>
      <mat-list-item *ngFor="let phase of phases">
          <h5>{{ phase.name }}</h5>
      </mat-list-item>
    </mat-nav-list>
    
    <section *ngFor="let phase of phases">
        <button
          (click)="togglePhaseComplete(phase)"
          [disabled]="phase.isComplete"
        >
          {{ phase.name }} completed!
        </button>
      </div>
    </section>
    

    Then, you can use this isComplete property in your template to disable buttons for phases that have already been completed:

    [disabled]="phase.isComplete"
    

    You can also make a little helper function to determine if a phase is Active or not in order to display your green background color:

    public isActive(phase: Phase) {
        // the first incomplete phase found is active
        const activePhase = this.phases.find(p => !p.isComplete);
        return phase.id === activePhase?.id;
    }
    

    Then just add [class.green]="isActive(phase)" to your mat-list-item (assuming you defined a class named "green" in your css).

    Here's a working StackBlitz

    Now, disabling the nav items for phases that haven't yet been active is a little more work because the mat-list-item doesn't have a disabled property like the button does, so something like this will NOT work:

    <mat-list-item *ngFor="let phase of phases" 
                   [disabled]="!isActive(phase) && !phase.isComplete"
    >
    

    But, as mentioned in this answer, you can create a css class to disable mouse events and display the item as disabled, so the code would look like:

    <mat-list-item *ngFor="let phase of phases" 
                   [class.disabled]="!isActive(phase) && !phase.isComplete"
    >