Search code examples
angulartypescriptangular-materialtheming

Angular doesn't update view after implementing custom themes


I'm new at Angluar and Front End development but I'm working on a rather big project and just recently I had to implemented Custom Theming into it, the problem is after I've done it, it stopped updating the HTML after you change *ngIf values.

I have a suspicion that the way I implemented the custom themes might have something to do so I'll start sharing that part of the code: styles.scss

/* You can add global styles to this file, and also import other style files */
@import 'custom-theme.scss';

html,
body {
    height: 100%;
}

body {
    margin: 0;
    font-family: Roboto, "Helvetica Neue", sans-serif;
}

custom-theme.scss

@use '@angular/material' as mat;
@include mat.core();

$light-primary: mat.define-palette(mat.$blue-palette);
$light-accent: mat.define-palette(mat.$indigo-palette);
$light-warn: mat.define-palette(mat.$red-palette);

$light-theme: mat.define-light-theme((color:(primary: $light-primary,
                accent: $light-accent,
                warn: $light-warn )));

@include mat.all-component-themes($light-theme);

.dark-theme {
    $dark-primary: mat.define-palette(mat.$deep-orange-palette);
    $dark-accent: mat.define-palette(mat.$amber-palette);
    $dark-warn: mat.define-palette(mat.$red-palette);

    $dark-theme: mat.define-dark-theme((color:(primary: $dark-primary,
                    accent: $dark-accent,
                    warn: $dark-warn )));

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

.light-background {
    background-color: rgba(0, 0, 0, 0.02);
}

.dark-background {
    background-color: #303030;
}

And this is how I change the themes: header.component.ts:

constructor(
    public cookieService: CookieService,
    public dialog: MatDialog,
    public router: Router,
    public translateService: TranslateService,
    private responsive: BreakpointObserver,
    private render: Renderer2,
    @Inject(DOCUMENT) private document: Document,
    media: MediaMatcher,
  ) {
    this.mobileQuery = media.matchMedia('(max-width: 600px)');
    this.langs = this.translateService.getLangs();
    this.selectedLang = this.translateService.currentLang;
    this.userId = cookieService.get('UserId');
  }

  ngOnInit() {
    this.responsive.observe(['(min-width: 1022px)'])
      .subscribe(result => {
        this.isMobile = result.matches;
      });
  }

  changedTheme(): void {
    this.render.removeClass(this.document.body, 'light-theme');
    this.render.removeClass(this.document.body, 'dark-theme');
    this.render.removeClass(this.document.body, 'light-background');
    this.render.removeClass(this.document.body, 'dark-background');
    this.render.addClass(this.document.body, this.selectedTheme.toLowerCase() + '-theme');
    this.render.addClass(this.document.body, this.selectedTheme.toLowerCase() + '-background');
  }

The function changedTheme is used (actually copied and pasted) in another two components, app.component.ts and settings.component.ts, since I need to manage themes on both components as well.

I was thinking if it's possible to "emit" an event to change the themes but I'm not sure if it's bad practice or not and that's not the main objective of this question. However, I need some help with that. I don't have any other code that specifically changes or manually updates the view.

PS: Had to put the .light-background and dark-background classes since the theming doesn't change the background for some reason

Update

Okay, I used git and went back before I coded the theming and the problem persisted. Rebooted the computer, redownloaded node_modules and tested in multiple browsers, even without the theming the problem persists. Now I really don't know what to do. The only difference is that it removed @angular/flex-layout but, even with me forcing it to keep it, the problem persists. Where should I look to debug this kind of problem?

Update 2

Almost an entire day of work after debugging commit after commit, I've done it. The problem isn't with the theming, it's with my header.html component. I had to do some trickery with it to make it responsive for mobile and desktop, they have different interfaces, and cause of that something broke. Dunno why or what, but it's easier to find another way of doing the responsive part than solving this problem that I've created. Here's the broken code:

<div style="display: contents;">
  <mat-toolbar color="primary" class="header-top">
    <div style="display: contents;" *ngIf="!isMobile">
      <button mat-icon-button (click)="snav.toggle()" class="icon-button">
        <img class="icon" src="../assets/imgs/1315254.png" alt="profile" />
      </button>

      <span class="toolbar-text" (click)="goToPage('home')">{{ cookieService.get('Username') }}</span>

      <button mat-icon-button (click)="goToPage('access')" *ngIf="!userId">
        <mat-icon>manage_accounts</mat-icon>
      </button>
    </div>

    <div style="display: contents;" *ngIf="isMobile">
      <span class="toolbar-text" (click)="goToPage('home')">FindME!</span>
      <span class="spacer"></span>

      <mat-form-field style="margin-top: 20px;" *ngIf="isDevMode()">
        <mat-select [(value)]="selectedLang" (valueChange)="changedLanguage()" id="lang-select">
          <mat-option *ngFor="let lang of langs" [value]="lang">
            {{ lang.split('-')[0].toUpperCase() }}
          </mat-option>
        </mat-select>
      </mat-form-field>

      <button mat-icon-button (click)="goToPage('access')" *ngIf="!userId">
        <mat-icon>manage_accounts</mat-icon>
      </button>
      <button mat-icon-button [matMenuTriggerFor]="notifications" *ngIf="userId">
        <mat-icon>notifications</mat-icon>
      </button>
      <button mat-icon-button [matMenuTriggerFor]="chat" *ngIf="userId">
        <mat-icon>chat</mat-icon>
      </button>
      <button mat-icon-button class="icon-button" [matMenuTriggerFor]="menu" *ngIf="userId">
        <img class="icon" src="../assets/imgs/1315254.png" alt="profile" />
      </button>

      <!-- Menu -->
      <mat-menu #menu="matMenu">
        <button mat-menu-item (click)="goToPage('profile')">
          <mat-icon>account_circle</mat-icon>
          <span>{{ 'HEADER.PROFILE' | translate }}</span>
        </button>
        <button mat-menu-item (click)="goToPage('settings')">
          <mat-icon>settings</mat-icon>
          <span>{{ 'HEADER.SETTINGS' | translate }}</span>
        </button>
        <button mat-menu-item (click)="logout()">
          <mat-icon>logout</mat-icon>
          <span>{{ 'HEADER.LOGOUT' | translate }}</span>
        </button>
      </mat-menu>

      <!-- Notifications -->
      <mat-menu #notifications="matMenu">
        <h1 *ngIf="userNotifications == null">{{ "HEADER.NO-NEW-NOTIFICATIONS" | translate }}</h1>
        <div *ngIf="userNotifications != null">
          <button mat-menu-item (click)="goToPage('profile')">
            <mat-icon>notifications</mat-icon>
          </button>
        </div>
      </mat-menu>

      <!-- Chat -->
      <mat-menu #chat="matMenu">
        <h1 *ngIf="chats == null">{{ "HEADER.NO-NEW-CONVERSATION" | translate }}</h1>
        <div *ngIf="chats != null">
          <button mat-menu-item (click)="goToPage('profile')">
            <mat-icon>chat</mat-icon>
          </button>
        </div>
      </mat-menu>

      <button mat-icon-button aria-label="icon-button with share icon" (click)="goToPage('about')">
        <mat-icon>help</mat-icon>
      </button>
    </div>
  </mat-toolbar>

  <mat-sidenav-container class="example-sidenav-container">
    <mat-sidenav #snav [mode]="'over'" fixedTopGap="102">
      <mat-nav-list>
        <mat-selection-list role="list">
          <mat-divider></mat-divider>
          <mat-list-item role="button" *ngIf="userId">
            <a (click)="goToPage('notifications')">
              {{ 'NOTIFICATIONS' | translate }}
            </a>
          </mat-list-item>
          <mat-divider></mat-divider>
          <mat-list-item role="button" *ngIf="userId">
            <a (click)="goToPage('chat')">
              {{ 'CHATS' | translate }}
            </a>
          </mat-list-item>
          <mat-divider></mat-divider>
        </mat-selection-list>
      </mat-nav-list>
    </mat-sidenav>
    <mat-sidenav-content>
      <router-outlet></router-outlet>
    </mat-sidenav-content>
  </mat-sidenav-container>
</div>

Solved it by returning it to before I made it "mobile compatible":

<mat-toolbar color="primary" class="header-top">
  <span class="toolbar-text" (click)="goToPage('home')">FindME!</span>
  <span class="spacer"></span>
  <mat-form-field>
    <mat-select [(value)]="selectedLang" (valueChange)="changedLanguage()" id="lang-select">
      <mat-option *ngFor="let lang of langs" [value]="lang">
        <!-- {{ lang | translate }} -->
        {{ lang.split('-')[0].toUpperCase() }}
      </mat-option>
    </mat-select>
  </mat-form-field>

  <button mat-icon-button (click)="goToPage('access')" *ngIf="!cookieService.get('UserId')">
    <mat-icon>manage_accounts</mat-icon>
  </button>
  <button mat-icon-button [matMenuTriggerFor]="notifications" *ngIf="cookieService.get('UserId')">
    <mat-icon>notifications</mat-icon>
  </button>
  <button mat-icon-button [matMenuTriggerFor]="chat" *ngIf="cookieService.get('UserId')">
    <mat-icon>chat</mat-icon>
  </button>
  <button mat-icon-button class="icon-button" [matMenuTriggerFor]="menu" *ngIf="cookieService.get('UserId')">
    <img class="icon" src="../assets/imgs/1315254.png" alt="profile" />
  </button>

  <!-- Menu -->
  <mat-menu #menu="matMenu">
    <button mat-menu-item (click)="goToPage('profile')">
      <mat-icon>account_circle</mat-icon>
      <span>{{ 'HEADER.PROFILE' | translate }}</span>
    </button>
    <button mat-menu-item (click)="goToPage('settings')">
      <mat-icon>settings</mat-icon>
      <span>{{ 'HEADER.SETTINGS' | translate }}</span>
    </button>
    <button mat-menu-item (click)="logout()">
      <mat-icon>logout</mat-icon>
      <span>{{ 'HEADER.LOGOUT' | translate }}</span>
    </button>
  </mat-menu>
  <!-- Notifications -->
  <mat-menu #notifications="matMenu">
    <h1 *ngIf="userNotifications == null">{{ "HEADER.NO-NEW-NOTIFICATIONS" | translate }}</h1>
    <div *ngIf="userNotifications != null">
      <button mat-menu-item (click)="goToPage('profile')">
        <mat-icon>notifications</mat-icon>
      </button>
    </div>
  </mat-menu>
  <!-- Chat -->
  <mat-menu #chat="matMenu">
    <h1 *ngIf="chats == null">{{ "HEADER.NO-NEW-CONVERSATION" | translate }}</h1>
    <div *ngIf="chats != null">
      <button mat-menu-item (click)="goToPage('profile')">
        <mat-icon>chat</mat-icon>
      </button>
    </div>
  </mat-menu>

  <button mat-icon-button aria-label="icon-button with share icon" (click)="goToPage('about')">
    <mat-icon>help</mat-icon>
  </button>
</mat-toolbar>

So I feel really dumb and sorry for wasting your time. Maybe I'll delete this question cause it didn't help anyone at all but yeah, thanks for the help and attention!!


Solution

  • Its really strange, I tried reproducing the issue but your code works great, maybe replicate the issue in the stackblitz and share back!

    code

    import { Component, Inject, Renderer2 } from '@angular/core';
    import { MatIconModule } from '@angular/material/icon';
    import { MatDividerModule } from '@angular/material/divider';
    import { MatButtonModule } from '@angular/material/button';
    import { MatSlideToggleModule } from '@angular/material/slide-toggle';
    import { DOCUMENT } from '@angular/common';
    import { FormsModule } from '@angular/forms';
    
    /**
     * @title Basic buttons
     */
    @Component({
      selector: 'button-overview-example',
      templateUrl: 'button-overview-example.html',
      styleUrls: ['button-overview-example.css'],
      standalone: true,
      imports: [
        MatButtonModule,
        MatDividerModule,
        MatIconModule,
        MatSlideToggleModule,
        FormsModule,
      ],
    })
    export class ButtonOverviewExample {
      isDark = false;
      selectedTheme: string;
    
      constructor(
        private render: Renderer2,
        @Inject(DOCUMENT) private document: Document
      ) {}
    
      toggleTheme() {
        this.selectedTheme = this.isDark ? 'dark' : 'light';
        this.changedTheme();
      }
    
      changedTheme(): void {
        this.render.removeClass(this.document.body, 'light-theme');
        this.render.removeClass(this.document.body, 'dark-theme');
        this.render.removeClass(this.document.body, 'light-background');
        this.render.removeClass(this.document.body, 'dark-background');
        this.render.addClass(
          this.document.body,
          this.selectedTheme.toLowerCase() + '-theme'
        );
        this.render.addClass(
          this.document.body,
          this.selectedTheme.toLowerCase() + '-background'
        );
      }
    }
    

    html

    Light
    <mat-slide-toggle [(ngModel)]="isDark" (ngModelChange)="toggleTheme()"
      >Dark</mat-slide-toggle
    >
    <br />
    
    <section>
      <div class="example-label">Basic</div>
      <div class="example-button-row">
        <button mat-button>Basic</button>
        <button mat-button color="primary">Primary</button>
        <button mat-button color="accent">Accent</button>
        <button mat-button color="warn">Warn</button>
        <button mat-button disabled>Disabled</button>
        <a mat-button href="https://www.google.com/" target="_blank">Link</a>
      </div>
    </section>
    <mat-divider></mat-divider>
    <section>
      <div class="example-label">Raised</div>
      <div class="example-button-row">
        <button mat-raised-button>Basic</button>
        <button mat-raised-button color="primary">Primary</button>
        <button mat-raised-button color="accent">Accent</button>
        <button mat-raised-button color="warn">Warn</button>
        <button mat-raised-button disabled>Disabled</button>
        <a mat-raised-button href="https://www.google.com/" target="_blank">Link</a>
      </div>
    </section>
    <mat-divider></mat-divider>
    <section>
      <div class="example-label">Stroked</div>
      <div class="example-button-row">
        <button mat-stroked-button>Basic</button>
        <button mat-stroked-button color="primary">Primary</button>
        <button mat-stroked-button color="accent">Accent</button>
        <button mat-stroked-button color="warn">Warn</button>
        <button mat-stroked-button disabled>Disabled</button>
        <a mat-stroked-button href="https://www.google.com/" target="_blank"
          >Link</a
        >
      </div>
    </section>
    <mat-divider></mat-divider>
    <section>
      <div class="example-label">Flat</div>
      <div class="example-button-row">
        <button mat-flat-button>Basic</button>
        <button mat-flat-button color="primary">Primary</button>
        <button mat-flat-button color="accent">Accent</button>
        <button mat-flat-button color="warn">Warn</button>
        <button mat-flat-button disabled>Disabled</button>
        <a mat-flat-button href="https://www.google.com/" target="_blank">Link</a>
      </div>
    </section>
    <mat-divider></mat-divider>
    <section>
      <div class="example-label">Icon</div>
      <div class="example-button-row">
        <div class="example-flex-container">
          <button
            mat-icon-button
            aria-label="Example icon button with a vertical three dot icon"
          >
            <mat-icon>more_vert</mat-icon>
          </button>
          <button
            mat-icon-button
            color="primary"
            aria-label="Example icon button with a home icon"
          >
            <mat-icon>home</mat-icon>
          </button>
          <button
            mat-icon-button
            color="accent"
            aria-label="Example icon button with a menu icon"
          >
            <mat-icon>menu</mat-icon>
          </button>
          <button
            mat-icon-button
            color="warn"
            aria-label="Example icon button with a heart icon"
          >
            <mat-icon>favorite</mat-icon>
          </button>
          <button
            mat-icon-button
            disabled
            aria-label="Example icon button with a open in new tab icon"
          >
            <mat-icon>open_in_new</mat-icon>
          </button>
        </div>
      </div>
    </section>
    <mat-divider></mat-divider>
    <section>
      <div class="example-label">FAB</div>
      <div class="example-button-row">
        <div class="example-flex-container">
          <div class="example-button-container">
            <button
              mat-fab
              color="primary"
              aria-label="Example icon button with a delete icon"
            >
              <mat-icon>delete</mat-icon>
            </button>
          </div>
          <div class="example-button-container">
            <button
              mat-fab
              color="accent"
              aria-label="Example icon button with a bookmark icon"
            >
              <mat-icon>bookmark</mat-icon>
            </button>
          </div>
          <div class="example-button-container">
            <button
              mat-fab
              color="warn"
              aria-label="Example icon button with a home icon"
            >
              <mat-icon>home</mat-icon>
            </button>
          </div>
          <div class="example-button-container">
            <button
              mat-fab
              disabled
              aria-label="Example icon button with a heart icon"
            >
              <mat-icon>favorite</mat-icon>
            </button>
          </div>
        </div>
      </div>
    </section>
    <mat-divider></mat-divider>
    <section>
      <div class="example-label">Mini FAB</div>
      <div class="example-button-row">
        <div class="example-flex-container">
          <div class="example-button-container">
            <button
              mat-mini-fab
              color="primary"
              aria-label="Example icon button with a menu icon"
            >
              <mat-icon>menu</mat-icon>
            </button>
          </div>
          <div class="example-button-container">
            <button
              mat-mini-fab
              color="accent"
              aria-label="Example icon button with a plus one icon"
            >
              <mat-icon>plus_one</mat-icon>
            </button>
          </div>
          <div class="example-button-container">
            <button
              mat-mini-fab
              color="warn"
              aria-label="Example icon button with a filter list icon"
            >
              <mat-icon>filter_list</mat-icon>
            </button>
          </div>
          <div class="example-button-container">
            <button
              mat-mini-fab
              disabled
              aria-label="Example icon button with a home icon"
            >
              <mat-icon>home</mat-icon>
            </button>
          </div>
        </div>
      </div>
    </section>
    

    stackblitz