Angular Material - Dark Mode Changing Background Image

Viewed 567

Currently I have an app component that has a sidenav and my router-outlet in the sidenav content. In this component I manage the app theme by adding or removing the darkMode class in my style.scss.

Is there a way to control what background-image I'm using?

What if I also wanted to control the background image based on what component is opened on the router-outlet? Would that be possible to? Maybe not setting the background-image in the app component but on the opened component instead

app.component.html

<mat-sidenav-container autosize class="h-100">
    <mat-sidenav #sidenav mode="side" opened="true">
        <mat-nav-list>
            <mat-list-item>
                <mat-slide-toggle [formControl]="toggleControl"></mat-slide-toggle>
            </mat-list-item>
            ...
        <mat-sidenav-content>
            <router-outlet></router-outlet>
        </mat-sidenav-content>

app.component.ts

@HostBinding('class') className = '';
toggleControl = new FormControl(false);

constructor(private overlay: OverlayContainer, ...) { }

ngOnInit() {
    this.toggleControl.valueChanges.subscribe((darkMode) => {
      const darkClassName = 'darkMode';
      this.className = darkMode ? darkClassName : '';
      if (darkMode) {
        this.overlay.getContainerElement().classList.add(darkClassName);
      } else {
        this.overlay.getContainerElement().classList.remove(darkClassName);
      }
    });

    this.subscription = this._service.currentLogStatus.subscribe(logStatus => this.loggedIn = logStatus);
}

app.component.scss

mat-sidenav-container {
    background-image: url('../assets/img/home-background.png');
}

style.scss

.darkMode {
  @include mat.all-component-colors($angular-dark-theme);
}

Update

After some indications I've tried.

The darkMode background still presists on the ProfileComponent, even though I have another background referenced.

app.component.html

<mat-sidenav-content>
        <router-outlet (activate)="onRouterOutletActivate($event)"></router-outlet>
</mat-sidenav-content>

app.component.ts

ngOnInit() {
    this.toggleControl.valueChanges.subscribe((toggled) => {
      console.log(this.currentComponent);
      this.className = toggled ? 'darkMode' : '';
      
      if (toggled) {
        if (this.currentComponent == "home") {
          this._overlay.getContainerElement().classList.add('darkMode');
          this._overlay.getContainerElement().classList.remove('darkModeProfile');
        }

        if (this.currentComponent == "profile") {
          this._overlay.getContainerElement().classList.add('darkModeProfile');
          this._overlay.getContainerElement().classList.remove('darkMode');
        }
      } else {
        if (this.currentComponent == "home") {
          this._overlay.getContainerElement().classList.remove('darkMode');
        }

        if (this.currentComponent == "profile") {
          this._overlay.getContainerElement().classList.remove('darkModeProfile');
        }
      }
    });

    this.subscription = this._service.currentLogStatus.subscribe(logStatus => this.loggedIn = logStatus);
  }

  public onRouterOutletActivate(event : any) {
    if (event.constructor.name == "ProfileComponent") {
      this.currentComponent = "profile";
    }

    if (event.constructor.name == "HomeComponent") {
      this.currentComponent = "home";
    }
  }

app.component.scss

mat-sidenav-container {
    background-image: url('../assets/img/home-background.png');
}

:host-context(.darkMode) {
    mat-sidenav-container {
        background-image: url('../assets/img/home-background-dark.png');
    }
}

:host-context(.darkModeProfile) {
    mat-sidenav-container {
        background-image: url('../assets/img/profile-background.png');
    }
}
2 Answers

What if I also wanted to control the background image based on what component is opened on the router-outlet?

You probably need the RouterLinkActive Directive.

Tracks whether the linked route of an element is currently active, and allows you to specify one or more CSS classes to add to the element when the linked route is active.

How to use it?

<a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>

To add the classes only when the URL matches the link exactly, add this option exact: true:

<a routerLink="/user/bob" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact:
true}">Bob</a>

More information and examples can be found in the official docs. https://angular.io/api/router/RouterLinkActive

UPDATE:

To detect which component is opened / active , you can use (activate) directive of the router-outlet.

<router-outlet (activate)='onActivate($event)'></router-outlet>

Based on that logic, you can change the background of the opened component.

Stackblitz with (activate) directive https://stackblitz.com/edit/angular-router-basic-example-dvsmnx?file=app%2Fapp.component.ts

The solution that worked for me was to maintain the .darkMode class and use different backgrounds based on what component is being rendered by router-outlet

Solution

app.component.html

<mat-sidenav-container [ngStyle]="{ 'background': 
    'linear-gradient( rgba(0, 0, 0,' + theme + '), rgba(0, 0, 0,' + theme + ')),'+ 
    'url(../assets/img/' + currentComponent + '-background' + '.png)'}"
    autosize class="h-100">
    <mat-sidenav #sidenav mode="side" opened="true">
        <mat-nav-list>
            <mat-list-item>
                <mat-slide-toggle [formControl]="toggleControl"></mat-slide-toggle>
            </mat-list-item>
            ...
        <mat-sidenav-content>
            <router-outlet (activate)="onRouterOutletActivate($event)"></router-outlet>
        </mat-sidenav-content>
    </mat-sidenav>
</mat-sidenav-container>

app.component.ts

currentComponent: string = "home";
theme: number = 0;

constructor(private _overlay: OverlayContainer) { }

ngOnInit() {
  this.toggleControl.valueChanges.subscribe((toggled) => {
    this.className = toggled ? 'darkMode' : '';
      
    if (toggled) {
      this._overlay.getContainerElement().classList.add('darkMode');
      this.theme = 0.5;
    } else {
      this._overlay.getContainerElement().classList.remove('darkMode');
      this.theme = 0;
    }
  });

  this.subscription = this._service.currentLogStatus.subscribe(logStatus => this.loggedIn = logStatus);
}

public onRouterOutletActivate(event : any) {
  if (event.constructor.name == "ProfileComponent" || event.constructor.name == "RegisterComponent") {
    this.currentComponent = "profile";
  }

  if (event.constructor.name == "HomeComponent") {
    this.currentComponent = "home";
  }
}

style.scss

.darkMode {
  @include mat.all-component-colors($angular-dark-theme);
}

Doing it this way, I can control what image is shown and apply a filter when the .darkMode is enabled.

Related