Search code examples
angularheadercomponentstogglesidenav

toggle sidenav from another component in angular


I want to toggle a sidenav from another component in Angular. I have 3 child Components: menu,content,header. Button is on header component.I want to click on button then menu should toggle.

header.ts

export class HeaderComponent implements OnInit {
  constructor(public service: SharedService) {}

  ngOnInit() {}

  toggleMenu() {
    this.service.flag = !this.service.flag;
    this.service.flagChange.emit(this.service.flag);
  }
}

header.html

<div>
  <a href="">
    <i class="fas fa-bars text-white barsIcon" ` (click)="toggleMenu()"></i>
  </a>
</div>

menu.ts

export class MenuComponent implements OnInit {
  constructor(public service: SharedService) {
    this.service.flagChange.subscribe((res) => this.service.flag);
  }

  ngOnInit() {}
}

menu.html

<div>
  <ul>
    <li>
      <a routerLink="/dashboard">Dashboard</a>
    </li>
    <li>
      <a routerLink="/user">User</a>
    </li>

    <li>
      <a routerLink="">Currency</a>
    </li>
    <li>
      <a routerLink="/report">Report</a>
      <ul>
        <li>
          <a routerLink="">Report1</a>
        </li>
        <li>
          <a routerLink="">Report2</a>
        </li>
        <li>
          <a routerLink="">Report3</a>
        </li>
      </ul>
    </li>
  </ul>
</div>

service.ts

@Injectable({
  providedIn: "root",
})
export class SharedService {
  public flag: boolean = false;
  public flagChange = new EventEmitter<boolean>();

  constructor() {}
}

Solution

  • You aren't that fare, but you should use BehaviorSubject and not EventEmitter for that purpose
    Emitter a meant for @Output()

    here some little changes.
    I also refactor a little bit your code ;)

    header.ts

    export class HeaderComponent {
      constructor(public sharedService: SharedService) {}
    }
    
    

    header.html

    <div>
      <a href="">
        <i class="fas fa-bars text-white barsIcon" (click)="sharedService.toggleMenu()"></i>
      </a>
    </div>
    

    menu.ts

    export class MenuComponent implements OnInit, OnDestroy {
      protected _unsubscribe$: Subject<void> = new Subject();
    
      flag?: boolean
    
      constructor(private _sharedService: SharedService) {}
    
      ngOnInit() {
        this._sharedService.onFlagChange$
          .pipe(takeUntil(this._unsubscribe$))
          .subscribe((value) => {
            this.flag = value
          })
      }
    
      ngOnDestroy(): void {
        this._unsubscribe$.next();
        this._unsubscribe$.complete();
      }
    }
    

    menu.html

    <div [class.hidden]="!flag"> <!-- or *ngIf, as you wish ;) -->
      <ul>
        <li>
          <a routerLink="/dashboard">Dashboard</a>
        </li>
        <li>
          <a routerLink="/user">User</a>
        </li>
    
        <li>
          <a routerLink="">Currency</a>
        </li>
        <li>
          <a routerLink="/report">Report</a>
          <ul>
            <li>
              <a routerLink="">Report1</a>
            </li>
            <li>
              <a routerLink="">Report2</a>
            </li>
            <li>
              <a routerLink="">Report3</a>
            </li>
          </ul>
        </li>
      </ul>
    </div>
    

    service.ts

    @Injectable({
      providedIn: "root",
    })
    export class SharedService {
      flag = false;
      onFlagChange$: BehaviorSubject<boolean> = new BehaviorSubject(this.flag);
    
      constructor() {}
    
      toggleMenu() {
        this.flag = !this.flag;
        this.onFlagChange$.next(this.flag); // Here, .next instead of .emit
      }
    }
    

    Error

    1. Using BehaviorSubject instead of EventEmitter

    Optimization

    1. Always unsubscribe from observable!

    Refactoring

    1. Do not declare :boolean if you're already adding a value to it, this is implicit.
    2. Move toggle logic inside the service
    3. Do not declare ngOnInit if not used
    4. Make services private if not used in the html file