Material navbar isn't opening in responsive mode on handsets - angular-material

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™"

Related

Angular Event Emitting to change material theme

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

Angular Material navbar - can't see labels

Here is my code for navbar:
<nav mat-tab-nav-bar>
<a mat-tab-link
*ngFor="let link of navLinks"
[routerLink]="link.link"
routerLinkActive #rla="routerLinkActive"
[active]="rla.isActive">
</a>
</nav>
And component:
export class SettingsComponent implements OnInit {
navLinks: any[];
activeLinkIndex = -1;
constructor(private router: Router) {
this.navLinks = [
{
label: 'First',
link: 'syspref',
index: 0
}, {
label: 'Second',
link: 'userpref',
index: 1
}
];
}
ngOnInit(): void {
this.router.events.subscribe((res) => {
this.activeLinkIndex = this.navLinks.indexOf(this.navLinks.find(tab => tab.link === '.' + this.router.url));
});
}
}
It works fine except that I dont see labels. Any idea why?
Thanks
Found it. I was missing {{link.label}}:
<nav mat-tab-nav-bar>
<a mat-tab-link
*ngFor="let link of navLinks"
[routerLink]="link.link"
routerLinkActive #rla="routerLinkActive"
[active]="rla.isActive">{{link.label}}
</a>
</nav>

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.

Angular flex-layout, grid size not working as expected

Sorry if this is a really simple problem, just trying to get my head around angular flex grid.
I have this component
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
viewAreas: any[] =
[{
areaName: 'A',
layoutSize: "70",
},
{
areaName: 'B',
layoutSize: "30"
}
];
}
along with this template
<div fxLayout="row">
<div *ngFor="let area of viewAreas" style="border: 2px solid red">
<div [fxFlex.gt-sm]="area.layoutSize"
[fxFlex.gt-xs]="area.layoutSize"
[fxFlex]="area.layoutSize">
{{area.areaName }} {{area.layoutSize }}
</div>
</div>
</div>
I would expect this to give me a row with 2 sections, 1 70% the width of the page and another 30% but I'm not getting that
You can see this here
https://stackblitz.com/edit/angular-umhsx2
If someone could tell me what i'm doing wrong it would be appreciated.
fxLayout="row"
Works only with the direct child be
fxFlex.gt-*
So try the following code.
<div fxLayout="row">
<div *ngFor="let area of viewAreas" style="border: 2px solid red" [fxFlex.gt-sm]="area.layoutSize"
[fxFlex.gt-xs]="area.layoutSize"
[fxFlex]="area.layoutSize">
{{area.areaName }} {{area.layoutSize }}
</div>
</div>

Navigation using $stateProvider and ion-side-menu not working in ionic framework

I used the ionic CLI to create an application.
ionic start myApp sidemenu
ionic platform add ios
ionic build ios
ionic emulate ios
I then proceeded to modify app.js to look like the following.
angular.module('starter', ['ionic', 'starter.controllers'])
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
if (window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
});
})
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.register', {
url: '/register',
views: {
'menuContent': {
templateUrl: 'templates/register.html',
controller: 'RegisterCtrl'
}
}
})
.state('app.snap', {
url: '/snap',
views: {
'menuContent': {
templateUrl: 'templates/snap.html',
controller: 'SnapCtrl'
}
}
})
.state('app.search', {
url: '/search',
views: {
'menuContent': {
templateUrl: 'templates/search.html',
controller: 'SearchCtrl'
}
}
})
.state('app.browse', {
url: '/browse',
views: {
'menuContent': {
templateUrl: 'templates/browse.html',
controller: 'BrowseCtrl'
}
}
});
$urlRouterProvider.otherwise('/app/snap');
});
I've modified controller.js to look like the following.
angular.module('starter.controllers', [])
.controller('AppCtrl', function($scope, $ionicModal, $timeout) {
$scope.loginData = {};
$ionicModal
.fromTemplateUrl('templates/login.html', { scope: $scope })
.then(function(modal) { $scope.modal = modal; });
$scope.closeLogin = function() {
$scope.modal.hide();
};
$scope.login = function() {
$scope.modal.show();
};
$scope.doLogin = function() {
console.log('Doing login', $scope.loginData);
$timeout(function() {
$scope.closeLogin();
}, 1000);
};
})
.controller('RegisterCtrl', function($scope) {
//TODO
})
.controller('SnapCtrl', function($scope, Camera) {
//TODO
})
.controller('SearchCtrl', function($scope) {
//TODO
})
.controller('BrowseCtrl', function($scope) {
//TODO
});
The services.js was modified as follows (mostly taken from http://learn.ionicframework.com/formulas/cordova-camera/).
angular.module('starter.services', [])
.factory('Camera', ['$q', function($q) {
return {
getPicture: function(options) {
var q = $q.defer();
navigator.camera.getPicture(function(result) {
q.resolve(result);
}, function(err) {
q.reject(err);
}, options);
return q.promoise;
}
}
}]);
My menu.html was modified as follows.
<ion-side-menus enable-menu-with-back-views="false">
<ion-side-menu-content>
<ion-nav-bar class="bar-stable">
<ion-nav-back-button></ion-nav-back-button>
<ion-nav-buttons side="left">
<button class="button button-icon button-clear ion-navicon" menu-toggle="left"></button>
</ion-nav-buttons>
</ion-nav-bar>
<ion-nav-view name="menuContent"></ion-nav-view>
</ion-side-menu-content>
<ion-side-menu side="left">
<ion-header-bar class="bar-stable">
<h1 class="title">MyMenu</h1>
</ion-header-bar>
<ion-content>
<ion-list>
<ion-item menu-close ng-click="login()">
<i class="icon ion-person"></i> Login
</ion-item>
<ion-item menu-close href="#/app/register">
<i class="icon ion-person-add"></i> Register
</ion-item>
<ion-item menu-close href="#/app/snap">
<i class="icon ion-camera"></i> Snap
</ion-item>
<ion-item menu-close href="#/app/search">
<i class="icon ion-search"></i> Search
</ion-item>
<ion-item menu-close href="#/app/browse">
<i class="icon ion-android-document"></i> Browse
</ion-item>
</ion-list>
</ion-content>
</ion-side-menu>
</ion-side-menus>
When I run this in the iOS emulator, clicking on the login links menu item work (a modal shows up) always. However, clicking on the any other menu item only works for the first menu item clicked. For example, if I click register, then the register page shows up, but then clicking any other menu item doesn't work (the register page continues to show up). This behavior also shows up on the android emulator (ionic emulate android) and also the browser (ionic serve).
Any ideas on what I'm doing wrong here?

Resources