Angular Event Emitting to change material theme - angular-material

Please I want to change the theme of my app by selecting one of the colors showing the image.
I have the following components:
Header Component (path: 'src/app/shared/components/header')
Default Component (path: 'src/app/layouts/default')
The image below is the Default Component containing the Header Component.
header.component.html
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-icon-button (click)="toggleSideBar()">
<mat-icon>menu</mat-icon>
</button>
<span>APP LOGO</span>
<div fxFlex fxLayout="row" fxLayoutAlign="flex-end">
<ul fxLayout="row" fxLayoutGap="20px">
<li>
<button mat-icon-button [matMenuTriggerFor]="theme">
<mat-icon>format_color_fill</mat-icon>
</button>
<mat-menu #theme="matMenu">
<div class="btn-wrapper">
<button mat-mini-fab class="btn btn-default" (click)="selectedTheme='default'"></button>
<button mat-mini-fab class="btn btn-purple" (click)="selectedTheme='purple'"></button>
<button mat-mini-fab class="btn btn-pink" (click)="selectedTheme='pink'"></button>
<button mat-mini-fab class="btn btn-deep-orange" (click)="selectedTheme='deep-orange'"></button>
</div>
</mat-menu>
</li>
<li>
<button mat-icon-button>
<mat-icon>settings</mat-icon>
</button>
</li>
<li>
<button mat-button [matMenuTriggerFor]="menu">
<mat-icon>person_outline</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>
<mat-icon>exit_to_app</mat-icon>
Sign out
</button>
</mat-menu>
</li>
</ul>
</div>
</mat-toolbar-row>
</mat-toolbar>
header.component.ts
import {Component, EventEmitter, OnInit, Output} from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
#Output() toggleSideBarForMe: EventEmitter<any> = new EventEmitter();
#Output() selectedTheme: string;
constructor() { }
ngOnInit(): void {
}
toggleSideBar(){
this.toggleSideBarForMe.emit();
setTimeout(() => {
window.dispatchEvent(
new Event('resize')
);
}, 300);
}
}
default.component.html
<div [ngClass]="selectedTheme">
<app-header (toggleSideBarForMe)="toggle()"></app-header>
<mat-drawer-container>
<mat-drawer mode="side" [opened]="sideBarOpen">
<app-sidebar></app-sidebar>
</mat-drawer>
<mat-drawer-content>
<router-outlet></router-outlet>
</mat-drawer-content>
</mat-drawer-container>
<app-footer></app-footer>
</div>
default.component.ts
import {Component, OnInit} from '#angular/core';
#Component({
selector: 'app-default',
templateUrl: './default.component.html',
styleUrls: ['./default.component.scss']
})
export class DefaultComponent implements OnInit {
sideBarOpen = true;
constructor() {
}
ngOnInit(): void {
}
toggle(){
this.sideBarOpen = !this.sideBarOpen;
}
}
Here is my custom theme file: custom-theme.scss
#import '~#angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
#include mat-core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$my-app-primary: mat-palette($mat-indigo);
$my-app-accent: mat-palette($mat-pink);
// The warn palette is optional (defaults to red).
$my-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and #include the theme mixins for each component
// that you are using.
#include angular-material-theme($my-app-theme);
.alternate-teal-theme {
$teal-app-primary: mat-palette($mat-teal);
$teal-app-accent: mat-palette($mat-yellow);
// The warn palette is optional (defaults to red).
$teal-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$teal-app-theme: mat-light-theme($teal-app-primary, $teal-app-accent, $teal-app-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and #include the theme mixins for each component
// that you are using.
#include angular-material-theme($teal-app-theme);
}
.purple {
$purple-primary: mat-palette($mat-purple);
$purple-accent: mat-palette($mat-light-green);
$purple-theme: mat-light-theme($purple-primary, $purple-accent);
#include angular-material-theme($purple-theme);
}
.pink {
$pink-primary: mat-palette($mat-pink);
$pink-accent: mat-palette($mat-yellow);
$pink-theme: mat-light-theme($pink-primary, $pink-accent);
#include angular-material-theme($pink-theme);
}
.deep-orange {
$deep-orange-primary: mat-palette($mat-deep-orange);
$deep-orange-accent: mat-palette($mat-teal, A100);
$deep-orange-theme: mat-light-theme($deep-orange-primary, $deep-orange-accent);
#include angular-material-theme($deep-orange-theme);
}
Please how do I make the selected theme to take effect on the app?

The content of custom-theme.scss file should be passed or imported into your style.scss. So that the theme affects the complete application you must declare the class css of the same in a div that encloses the html content of your boostrap component. And the error that I notice in your code is that in the file header.component.ts the output of the selectedTheme is not emitting any event, you should declare it like this #Output() selectedTheme: EventEmitter<string> = new EventEmitter<string>();, Here I leave you a working example of everything I just explained: stackblitz

Related

Select one class in a Svelte component?

<script>
export let text
</script>
<button>
{text}
</button>
<style>
:global(.bg-primary) {
background-color: red ;
}
:global(.bg-secondary {
background-color: blue;
}
</style>
When I import my component inside another component I would like to setup the background color with a prop
<script>
export let text
export let type = 'secondary'
</script>
<button class="bg-{type}">
{text}
</button>
or
<button class:bg-primary={type === 'primary'} class:bg-secondary={type === 'secondary'}>
{text}
</button>
The second approach limits the number of classes and works better with svelte unused class checking.

Material navbar isn't opening in responsive mode on handsets

I've created a site using the Angular Material schematic for a navbar which opens a sidebar when in responsive mode. I noticed that when running on the local dev server and using Chrome to emulate a handset it showed the sidenav as expected. Now that I've deployed to UAT and can browse from my phone, the sidenav doesn't show as I expect it to.
Oddly when using Chrome to emulate a phone, when switching it changes but when I reload the page I'm stuck with the sidenav not showing.
Here is my navbar.component.html
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #drawer class="sidenav" fixedInViewport
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="(isHandset$ | async) === false">
<mat-toolbar>Menu</mat-toolbar>
<mat-nav-list>
<a mat-list-item *ngFor="let link of navLinks"
routerLink="{{link.link}}" (click)="drawer.toggle()">{{link.label}}</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
<button
type="button"
aria-label="Toggle sidenav"
mat-icon-button
(click)="drawer.toggle()"
*ngIf="isHandset$ | async">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button>
<span class="md-title">South Island Practice Management</span>
<nav>
<ul>
<li *ngFor="let link of navLinks">
<a routerLink="{{link.link}}" class="navlink">{{link.label}}</a>
</li>
</ul>
</nav>
</mat-toolbar>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
And here is the navbar.component.ts
import { Component } from '#angular/core';
import { BreakpointObserver, Breakpoints } from '#angular/cdk/layout';
import { Observable } from 'rxjs';
import { map, share } from 'rxjs/operators';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent {
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches),
share()
);
constructor(private breakpointObserver: BreakpointObserver) {}
navLinks = [
{
label: 'Home',
link: '/home'
},
{
label: 'About Us',
link: '/aboutus'
},
{
label: 'Our Services',
link: '/services'
},
{
label: 'Contact Us',
link: '/contactus'
},
]
}
The site is currently live at https://sipm-b2ce5.firebaseapp.com
I'm at a bit of a loss having come over from Bootstrap where this sort of behaviour "Just Works™"

Navigating between components using side-nav in angular 7

I have sidenav setup in the home page of my angular application. I have also setup links to other components in the sidenav. Now I want the components to load in the same space when their links are clicked with the sidenav and toolbar not getting affected.
The HTML file of the home page with the sidenav and toolbar
<mat-toolbar>
<button (click)='sidenav.toggle()'><mat-icon style="color: white">menu</mat-icon></button>
<b style="font-size: 22px; color: white">Hello {{user}}</b>
<nav>
<ul>
<li><button (click)='logout()'><div style='font-size: 19px; color: white'> LOGOUT</div></button></li>
</ul>
</nav>
</mat-toolbar>
<mat-sidenav-container>
<mat-sidenav #sidenav mode='side' [(opened)]='opened'>
<mat-nav-list style="margin-top: 50px">
<a mat-list-item routerLink="/dashboard" routerLinkActive="active"><button (click)='sidenav.close()'><mat-icon>dashboard</mat-icon><span> </span>DASHBOARD</button></a>
<a mat-list-item routerLink="/visual" routerLinkActive="active"><button (click)='sidenav.close()'><mat-icon>timeline</mat-icon><span> </span>VISUALISATION</button></a>
<a mat-list-item routerLink="/config" routerLinkActive="active"><button (click)='sidenav.close()'><mat-icon>settings</mat-icon><span> </span>PROFILE</button></a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
</mat-sidenav-content>
</mat-sidenav-container>
I want to ensure that when I open this page for the first time, dashboard will be displayed, but when i click in visualisation or profile, the dashboard component should be replaced by the other clicked component in the same place whithout the need to reload the toolbar and sidenav components.
To ensure that the sidenav acts as a navigation bar first we will need to specify the tag inside the tag.
Then the second step is to specify the router links in the sidenav links. Ensure that the router links that they point to are specified as child routes of the main component. To specify them go to the app-routing.module.ts module and specify the routes which in the above case would be :
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { DashboardViewComponent } from './dashboard-view/dashboard-view.component';
import { VisualisationComponent } from './visualisation/visualisation.component';
import { ConfgaccountComponent } from './confgaccount/confgaccount.component';
const routes: Routes = [
{
path: 'dashboard', component: DashboardComponent, children: [
{ path: 'dash', component: DashboardViewComponent },
{ path: 'visual', component: VisualisationComponent },
{ path: 'config', component: ConfgaccountComponent },
]
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Now the next step would be to specify the tags inside the tags to ensure that the whenever the links in the sidenav are clicked they are routed to the proper child component and it is displayed in the correct place.
Also ensure that the routerLinks inside the tags are updated to the proper routes specified for the child components, as present in the app-routing.module.ts file.
The modified HTML code will be :
<mat-toolbar>
<button (click)='sidenav.toggle()'><mat-icon style="color: white">menu</mat-icon></button>
<b style="font-size: 22px; color: white">Hello {{user}}</b>
<nav>
<ul>
<li><button (click)='logout()'><div style='font-size: 19px; color: white'> LOGOUT</div></button></li>
</ul>
</nav>
</mat-toolbar>
<mat-sidenav-container>
<mat-sidenav #sidenav mode='side' [(opened)]='opened'>
<mat-nav-list style="margin-top: 50px">
<a mat-list-item routerLink="/dashboard/dash" routerLinkActive="active"><button (click)='sidenav.close()'><mat-icon>dashboard</mat-icon><span> </span>DASHBOARD</button></a>
<a mat-list-item routerLink="/dashboard/visual" routerLinkActive="active"><button (click)='sidenav.close()'><mat-icon>timeline</mat-icon><span> </span>VISUALISATION</button></a>
<a mat-list-item routerLink="/dashboard/config" routerLinkActive="active"><button (click)='sidenav.close()'><mat-icon>settings</mat-icon><span> </span>PROFILE</button></a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<main>
<router-outlet></router-outlet>
</main>
</mat-sidenav-content>
</mat-sidenav-container>
The next step is to create a navigation service to which the router and sidenav subscribe to to ensure the smooth routing to proper components whenever the links are clicked. The service needs to be injected into the constructor of the main dashboard component to ensure it's successful working.
The nav.service.ts file will have contents as specified :
import { Injectable, EventEmitter } from '#angular/core';
import { Event, NavigationEnd, Router } from '#angular/router';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class NavService {
public appDrawer: any;
public currentUrl = new BehaviorSubject<string>(undefined);
constructor(private router: Router) {
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.currentUrl.next(event.urlAfterRedirects);
}
});
}
}
Finally test the successful working of the child components in the main dashboard component. The sidenav will successfully help in proper navigation among child components now.

Angular6: click function only works some of the time

I'm using element.scrollIntoView() in my angular app.
I'm getting the desired element to come into view when my click function works.
my TS file:
elem1;
elem2;
ngOnInit() {
this.elem1 = document.getElementById('elem1');
this.elem2 = document.getElementById('elem2')
}
handleElemScroll(elem) {
// elem.scrollTop = 1000;
elem.scrollIntoView({behavior: 'smooth', block: 'end'});
}
And in my template, I am using mat-nav-list, and on click, passing the id of the element and using elem1.scrollIntoView() or elem2.scrollIntoView() to bring the desired element into view (for now bring it into view, but ideally the element should be at the top).
<div class="new-side-nav">
<mat-nav-list>
<a mat-list-item>
<span (click)="handleElemScroll(elem1)">Go to elem1</span>
</a>
<a mat-list-item>
<span (click)="handleElemScroll(elem2)">Go to elem2</span>
</a>
</mat-nav-list>
</div>
When the click function works, I am able to see both elements come into view on click (scrollIntoView works). But the click behavior is unpredictable. Sometimes the click works and the handleElemScroll() function runs, others time it does nothing.
Any clue as to why this occurs?
Ok, as i told you before look at this code (Angular 6 in my case, but it must work from 2):
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'eas-requests',
templateUrl: './requests.component.html',
styleUrls: ['./requests.component.css']
})
export class RequestsComponent implements OnInit {
// No need to declare
el1:HTMLElement;
el2:HTMLElement;
constructor() { }
ngOnInit() {
//No need to declare
this.el1 = document.getElementById('el1');
this.el2 = document.getElementById('el2');
}
// Just declare this method and receive html element.
clickEvent(e:HTMLElement){
e.scrollIntoView({behavior: 'smooth', block: 'end'});
}
}
And html:
<div id="el1" (click)="clickEvent($event.target)">Element 1</div>
<div id="el2" (click)="clickEvent($event.target)">Element 2</div>
In this action you pass click event. So in function you receive HTMLElement what you click. In my case all work fine as expected on any element where i use click event.

Programmatically open an N level self nested mat-menu component with matMenuTrigger openMenu

I am trying to make a self nested component that uses Angular Material mat-menu. I have a flyoutcomponent that is a wrapper for flyout-menu-item component, that will have a button as a matMenuTrigger for the nested component that will appear as many levels as the FeatureInput.FeatureChoices dictates. FeatureInput is an object that has FeatureChoices that may or may not contain other featurechoices etc N levels deep. Below code does not compile but it should demonstrate what I am doing. Basically I have flyout menu component as a input to a form and I am trying to load a stored answer on a form rather than select new, which I can do easily using the nested component. The desired behavior is that if the user clicks top matMenuTrigger button to open the top menu that it would expand all child menus to the menu item that matches with the FeatureInput.FeatureValue and sets the menu item _highlighted to true. I am using the menuOpen input parameter and ngChanges successfully to find the match(with I a setTimeout which cannot be right). Basically when I console.log this.trigger it is undefined. Ideally in the ngOnChange to the openMenu I would go through all menus and call openMenu on all the triggers but I cannot get access to the matMenuTrigger with ViewChild as the docs say. I get undefined. *-( All help welcome please and thanks.
Here is flyout template component.
<div>
<buttonmat-button [matMenuTriggerFor]="menu.childMenu"
(onMenuOpen)="onMenuOpen()"
(onMenuClose)="onMenuClose()">
<span [innerHTML]="featureInput.Text"></span>
</button>
<app-flyout-menu-item #menu
[featureChoicesObject]="featureInput.FeatureChoices"></app-flyout-menu-item>
</div>
And here is its .ts
import { Component, OnInit, Input, ViewChild } from '#angular/core';
import { MatMenuTrigger } from '#angular/material';
#Component({
selector: 'app-flyout',
templateUrl: './flyout.component.html',
styleUrls: ['./flyout.component.scss']
})
export class FlyoutComponent implements OnInit {
#Input() featureInput: FeatureInput
constructor() { }
ngOnInit() {
}
onMenuOpen() {
this.menuOpen = true;
}
onMenuClose() {
this.menuOpen = false;
}
}
And here is flyout-menu-item template
<mat-menu #childMenu="matMenu" [overlapTrigger]="false">
<span *ngFor="let featureChoice of featureChoices">
<span>
<button mat-menu-item [matMenuTriggerFor]="menu.childMenu">
<span [innerHTML]="featureChoice.Text"></span>
</button>
<app-flyout-menu-item #menu
[menuOpen]="menuOpen"
[featureInput]="featureInput"
[featureChoicesObject]="featureChoice.FeatureChoices"
(onOptionSelected)="someService.SomeMethod($event)"></app-flyout-menu-item>
</span>
<span *ngIf="!featureChoice.FeatureChoices">
<button mat-menu-item (click)="selectOption(featureChoice.ID)" [innerHTML]="featureChoice.Text" value="{{featureChoice.ID}}"></button>
</span>
</span>
</mat-menu>
And here is its .ts
import { Component, OnInit, Input, Output, ViewChild, EventEmitter, OnChanges, SimpleChanges } from '#angular/core';
import { MatMenuTrigger } from '#angular/material';
import { FeatureChoice } from 'app/model/feature-choice';
import { FeatureInput } from 'app/model/feature-input';
#Component({
selector: 'app-flyout-menu-item',
templateUrl: './flyout-menu-item.component.html',
styleUrls: ['./flyout-menu-item.component.scss']
})
export class FlyoutMenuItemComponent implements OnInit{
#ViewChild('menu') public menu;
#ViewChild('childMenu') public childMenu;
#ViewChild(MatMenuTrigger) public trigger: MatMenuTrigger;
#Input() featureInput: FeatureInput;
#Input() featureChoicesObject: FeatureChoice;
#Output() onOptionSelected: EventEmitter<FeatureInput> = new EventEmitter<FeatureInput>();
constructor(public solutionDataService: SolutionDataService) { }
ngOnInit() {
console.log(this.trigger);
}
ngOnChanges(simpleChanges: SimpleChanges) {
if (simpleChanges.menuOpen && simpleChanges.menuOpen.currentValue) {
setTimeout(() => {
// console.log(this.menu);
const itemsArray = this.childMenu.items.toArray();
for (let x = 0; x < itemsArray.length; x++) {
const menuItem = itemsArray[x];
if (this.featureInput.FeatureValue !== '' && menuItem._elementRef.nativeElement.value === this.featureInput.FeatureValue) {
menuItem._highlighted = true;
}
}
}, 1);
}
}
}
this.menuOpen = true;
Perhaps add menuOpen: boolean = false as an attribute at the top of your FlyoutComponent. I don't know where the value of menuOpen is saved.
the menuOpen property relates to the matMenuTrigger.
here's an example:
<button [ngClass]="{'active-icon': trigger.menuOpen}" type="button" mat-
icon-button #trigger="matMenuTrigger" [matMenuTriggerFor]="help">
<mat-icon></mat-icon>
</button>
<mat-menu #help="matMenu">
<div> textId </div>
</mat-menu>

Resources