I am quite new to Angular 5 and have just started learning it.
Recently, I have been trying to create a menu bar with multiple menus for my app using Angular 5 Material.
The menu will be triggered/opened during mouse enter and closed when the mouse leaves the menu.
My problem is that everytime the mouse mouse hovers to the first menu it loads the menu items of the 2nd menu. Here is a screenshot of the problem:
Here are my codes:
mainmenu.component.html:
<div>
<button mat-button [matMenuTriggerFor]="menu1"
(mouseenter)="openMyMenu()">Trigger1</button>
<mat-menu #menu1="matMenu" overlapTrigger="false">
<span (mouseleave)="closeMyMenu()">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</span>
</mat-menu>
</div>
<div>
<button mat-button [matMenuTriggerFor]="menu2"
(mouseenter)="openMyMenu()">Trigger2</button>
<mat-menu #menu2="matMenu" overlapTrigger="false">
<span (mouseleave)="closeMyMenu()">
<button mat-menu-item>Item 3</button>
<button mat-menu-item>Item 4</button>
</span>
</mat-menu>
</div>
mainmenu.component.ts:
import { Component, OnInit, ViewChild } from '#angular/core';
import {MatMenuTrigger} from '#angular/material'
#Component({
selector: 'app-mainmenu',
templateUrl: './mainmenu.component.html',
styleUrls: ['./mainmenu.component.css']
})
export class MainmenuComponent implements OnInit {
#ViewChild(MatMenuTrigger) matMenuTrigger: MatMenuTrigger;
constructor() { }
ngOnInit() {
}
openMyMenu() {
this.matMenuTrigger.openMenu();
}
closeMyMenu() {
this.matMenuTrigger.closeMenu();
}
}
I also tried this: #ViewChild('menu1') matMenuTrigger: MatMenuTrigger;
but I am getting errors.
Your opinions and advices are very much appreciated!
Thanks,
Artanis Zeratul
References:
https://coursetro.com/posts/code/113/How-to-Build-an-Angular-5-Material-App
How to make Material Design Menu (mat-menu) hide on mouseleave
The correct solution for this problem:
#ViewChildren(MatMenuTrigger) trigger: QueryList<MatMenuTrigger>;
//And call:
me.trigger.toArray()[indexOfMenu].openMenu();
I had the same issue.
Create two separate components, each will then contain its own mat-menu and will not affect the other.
<!-- component1 -->
<div>
<button mat-button [matMenuTriggerFor]="menu1"
(mouseenter)="openMyMenu()">Trigger1</button>
<mat-menu #menu1="matMenu" overlapTrigger="false">
<span (mouseleave)="closeMyMenu()">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</span>
</mat-menu>
</div>
<!-- component2 -->
<div>
<button mat-button [matMenuTriggerFor]="menu2"
(mouseenter)="openMyMenu()">Trigger2</button>
<mat-menu #menu2="matMenu" overlapTrigger="false">
<span (mouseleave)="closeMyMenu()">
<button mat-menu-item>Item 3</button>
<button mat-menu-item>Item 4</button>
</span>
</mat-menu>
</div>
I had the same issue and I solved it by using the read metadata property of #ViewChild decorator
mainmenu.component.html
<button #menuBtn1 [matMenuTriggerFor]="menu1">Trigger1</button>
<button #menuBtn2 [matMenuTriggerFor]="menu2">Trigger2</button>
mainmenu.component.ts
#ViewChild('menuBtn1', { read: MatMenuTrigger, static: false}) menu1: MatMenuTrigger;
#ViewChild('menuBtn2', { read: MatMenuTrigger, static: false}) menu2: MatMenuTrigger;
foo() {
this.menu1.openMenu(); // also closeMenu()
this.menu2.openMenu();
}
The trick is to use the template reference menuBtn1 or menuBtn2 and specify through the read property what you want to get that is the MatMenuTrigger directive
NOTE: I saw that the question refers to angular and angular-material 5. I tested it with angular 8 but it should be the same
This issue is related to the element referencing in angular, so you cannot directly use the multiple mat-menu in a single component.
The trick to do is to create a separate component that implements the mat-menu:
Eg,
mat-menu.component.html:
`<div>
<span>
<a (click)="openSelectMenu()">Menu
<mat-icon>arrow_drop_down</mat-icon>
<div #menuTrigger="matMenuTrigger" [matMenuTriggerFor]="menu1"></div>
</a>
<mat-menu #menu1="matMenu" overlapTrigger="false">
<a mat-menu-item *ngFor="let menu of menuItems">{{menu}}</a></mat-menu>
</span>
</div>`
mat-menu.component.ts
`#ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;`
`menuItems=['1', '2', '3'];`
`openSelectMenu() {
this.trigger.openMenu();
}`
Now you can use this component multiple times in any component.
Eg,
app.component.html
`<app-menu></app-menu>
<app-menu></app-menu>`
It will work.
I have two matmenus in my toolbar each one is a separate component and triggers a separate matmenu.
See images below:
Here is my notifications component(component 1 in the image above)
In my editor view :
In my notifications.component.html file :
<button mat-icon-button [matMenuTriggerFor]="notificationsMenu" (mouseover)="openNotifications()">
<mat-icon class="material-icons ele-text-color-grey">notifications</mat-icon>
</button>
<mat-menu #notificationsMenu="matMenu" [overlapTrigger]="false"></mat-menu>
I don't think it is possible to have two in one component but I hope this helps.
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<button mat-button [matMenuTriggerFor]="admin">ADMIN</button>
<mat-menu #admin="matMenu">
<button mat-menu-item>User Management</button>
</mat-menu>
</li>
<li class="nav-item dropdown">
<button mat-button [matMenuTriggerFor]="profile">PROFILE</button>
<mat-menu #profile="matMenu">
<button mat-menu-item>Change Password</button>
<button mat-menu-item>Logout</button>
</mat-menu>
</li>
Related
I'm using NgbDropdown in my angular application. I have two components CompParent and CompChild. Where compChild holds the HTML for dropdown and it is included into CompParent. I'm trying close all the opened dropdowns in page when a scroll event happens on scrollable-div.
comp-child.component.html:
<div ngbDropdown container="body">
<button class="btn btn-outline-primary btn-sm" ngbDropdownToggle>Actions</button>
<div ngbDropdownMenu>
<button ngbDropdownItem>Edit</button>
<button ngbDropdownItem>Duplicate</button>
<button ngbDropdownItem>Move</button>
<div class="dropdown-divider"></div>
<button ngbDropdownItem>Delete</button>
</div>
</div>
And CompChild is included into CompParent like this:
comp-parent.component.html
<div class="scrollable-div" (scroll)="closeAllDropdowns($event)">
<div class="dropdown-container" *ngFor="let item of items">
<app-comp-child></app-comp-child>
</div>
</div>
What i have tried so far is:
in compParent TS:
export class compParentComponent{
#ViewChild(NgbDropdown) private readonly dropdown: NgbDropdown;
#HostListener('scroll', ['$event'])
closeAllDropdowns(event) {
this.dropdown.close();
}
}
But this.dropdown returns undefined and says close method is not a function associated with it. Also i tried to select all dropdowns using templateRef, that too didn't work.
#ViewChild and #ViewChildren can only query elements from the component itself, not from childs. The possible option is to have a public reference to dropdown inside child, have reference to childs inside parent.
Parent:
export class CompParentComponent{
#ViewChildren(CompChild) compChild!: QueryList<CompChild>;
#HostListener('scroll', ['$event'])
closeAllDropdowns(event) {
this.compChild.forEach(dropdown => dropdown.close());
}
}
Child:
export class CompChildComponent{
#ViewChild(NgbDropdown) public dropdown: NgbDropdown;
}
I am trying to create the modal, which will load on page load in angular 6, its working fine on click method which will pass one argument
I tried with ngOnInit, but its void type not taking the argument
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Select user</h4>
<button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<select >
<option value="volvo">Rohait</option>
<option value="saab">Anuj</option>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="c('Save click')">Save</button>
</div>
</ng-template>
Launch demo modal
that I running using this
open(content) {
this.modalService.open(content);
}
But its not working here, its working on click method
If you are using Angular 8, first, you can declare this.
#ViewChild('content', { static: true }) content: TemplateRef<any>;
However, for those who are using Angular 7 and below, you should use this instead.
#ViewChild('content') content: TemplateRef<any>;
The above will allow you to access the content template reference variable within your Class.
And on your ngOnInit,
ngOnInit() {
this.modalService.open(this.content);
}
And do remember to import ViewChild, as well as TemplateRef into your component.ts,
import { TemplateRef, ViewChild } from '#angular/core';
This should allow your modalService to open the modal.
I have a button that I want to be able to toggle a class on a div to hide and show the div how would I do that in Angular?
HTML
<div id="chatsidebar">
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" id="sidebarCollapse" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
I want to add the class "active" onto the #chatsidebar div
app.component.ts
togglesideBar() {
}
Thanks
I'm answering this part of your question:
I want to add the class "active" onto the #chatsidebar div
To do it, you can use NgClass. NgClass allows you to add or remove any class to or from an element based on the given condition. Your code will looks something like this:
HTML
<div id="chatsidebar" [ngClass]="{'active': isSideBarActive}"> <!-- this ngClass will add or remove `active` class based on the `isSideBarActive` value -->
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" id="sidebarCollapse" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
Component
isSideBarActive: boolean = true; // initial value can be set to either `false` or `true`, depends on our need
togglesideBar() {
this.isSideBarActive = !this.isSideBarActive;
}
HTML
<div id="chatsidebar" *ngIf="status">
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" id="sidebarCollapse" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
app.component.ts:
status:boolean=true;
togglesideBar() {
if(this.status == true) this.status=false;
else this.status = true;
}
Demo:
https://plnkr.co/edit/fNoXWhUhMaUoeMihbGYd?p=preview
you can try below.
<div id="chatsidebar" class="{{activeClass}}"> ... </div>
and on your component define a property and set the class value on toggle function
// On Component
activeClass : string = "";
...
togglesideBar() {
this.activeClass = 'active'
}
it shall work, but may not the ideal solution.
Assuming you have a class named hide:
<div [class.hide]="hide">
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
togglesideBar() { this.hide = !this.hide; }
This will hide the element in question, while leaving it in the DOM. The other solutions using *ngIf will add and remove the element to and from the DOM. There are subtle reasons in specific cases to prefer one over the other, well described in the on-line documentation you have already read. In this case, it doesn't really matter.
The [class.className]=boolean format is just one of several ways to control classes in Angular. For instance, you could also have said:
[ngClass]="{'hide': hide}"
This is slightly more flexible because you can add/remove multiple classes at once.
Since you are using glyphicons, you are probably using Bootstrap, so you most likely already have the hide class defined.
As an aside, you rarely need IDs, and using them is pretty much of an anti-pattern in Angular.
Take a variable in your component something like
isShowChatSidebar:boolean=true;
then modify your method and html
togglesideBar() {
this.isShowChatSidebar=!this.isShowChatSidebar
}
<div id="chatsidebar" [ngClass]="{'active': isShowChatSidebar}">>
<app-chatsidebar></app-chatsidebar>
</div>
I am having some trouble trying to figure out the correct way to bind a value in my slidebar that slides out.
Event.html:
<template>
<require from="./sidebar/event-builder-sidebar"></require>
<require from="./event-item"></require>
<div class="flex-row">
<aside class="event-builder-settings-panel">
<!-- need to bind the single clicked task back to this -->
<event-builder-sidebar containerless data.two-way="?"></event-builder-sidebar>
</aside>
<div class="content-panel">
<div class="eb-actions-row row-flip">
<div class="action-row-buttons">
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-ios-book"></i> Task Library</button>
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-plus"></i> Add Task</button>
</div>
</div>
<section class="outer-content outer-content-spacing">
<div class="inner-content-div inner-content-padding">
<ul class="eb event-list">
<!-- Loop happens here -->
<li class="event-item eb-item-created" repeat.for="t of tasks">
<event-item containerless data.two-way="t"></event-item>
</li>
</ul>
</div>
</section>
</div>
</div>
</template>
You see where event-item is being looped. It generates multiple cards and when a user clicks on those cards they get a slidebar (event-builder-slidebar) that slides in from the left and allows the user to edit information in the card. I am currently at a loss for the correct way to do this. I am guessing I would need the event that triggers the slidebar to slide in to pass the current data object back up to the parent event.html and into the event-builder-slidebar. That is where I am at a loss to figure out how to do.
import { bindable, bindingMode } from 'aurelia-framework';
import { CssHelper } from '../../../shared/css-helper';
export class EventItem {
#bindable({ defaultBindingMode: bindingMode.twoWay }) data;
static inject() {
return [CssHelper];
}
constructor(cssHelper) {
this.cssHelper = cssHelper;
this.toggleEdit = e => { this.edit(e); };
}
attached() {
document.addEventListener('click', this.toggleEdit);
}
edit(e) {
// this needs to pass this.data back to event builder sidebar somehow
}
}
Using the containerless attribute is likely gumming things up for you. If you stop using that, you can simply use event binding to bind the custom element's click event to a function on your parent VM that sets a, e.g. selectedEvent property. You can even set it without a VM function directly in the template like this:
<template>
<require from="./sidebar/event-builder-sidebar"></require>
<require from="./event-item"></require>
<div class="flex-row">
<aside class="event-builder-settings-panel">
<!-- bind the single clicked task back to this -->
<event-builder-sidebar containerless data.bind="selectedEvent"></event-builder-sidebar>
</aside>
<div class="content-panel">
<div class="eb-actions-row row-flip">
<div class="action-row-buttons">
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-ios-book"></i> Task Library</button>
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-plus"></i> Add Task</button>
</div>
</div>
<section class="outer-content outer-content-spacing">
<div class="inner-content-div inner-content-padding">
<ul class="eb event-list">
<!-- Loop happens here -->
<li class="event-item eb-item-created" repeat.for="t of tasks">
<event-item data.bind="t" click.delegate="selectedEvent = t" ></event-item>
</li>
</ul>
</div>
</section>
</div>
</div>
</template>
However, if you really want to use containerless custom elements, then you'll need to fire the click event as a custom event (and you'll end up having a container element anyways it'll just be a div or something). Here is a gist that shows this in action: https://gist.run/?id=eb9ea1612c97af91104a35b0b5b10430
element vm
import {inject, bindable, containerless} from 'aurelia-framework';
#containerless
#inject(Element)
export class Thing {
#bindable value;
constructor(element) {
this.element = element;
}
fireClick() {
let e = new CustomEvent('click', { bubbles: true });
this.element.dispatchEvent(e);
}
}
element template
<template>
<div click.delegate="fireClick()" style="border: solid red 1px; height: 30px; width: 40px; display: inline-block; margin: 10px; text-align: center;">
${value}
</div>
</template>
I am trying to add some buttons to the GeoNetwork 3 MapViewer. Unfortunately I'm new to AngularJS, which GN3 is written in. So what I've done is editing the html-template that the 'gnMainViewer' directive is using.
ViewerDirectve.js:
module.directive('gnMainViewer', [
'gnMap',
function(gnMap) {
return {
restrict: 'A',
replace: true,
scope: true,
templateUrl: '../../catalog/components/viewer/' +
'partials/mainviewer.html',
[...]
}]);
In this template I can find the buttons that are shown in the MapView.
mainviewer.html:
<div class="wrapper" data-ng-controller="gnViewerController">
<div data-gn-alert-manager=""></div>
<div id="map" ngeo-map="map" class="map"></div>
<div gn-gfi="" map="map"></div>
<div gn-localisation-input map="map"></div>
<!--Top right buttons - Tools-->
<div class="tools tools-right" gi-btn-group gnv-close-panel data-ng-controller="toolsController">
<button class="btn btn-default" ng-model="activeTools.addLayers" type="submit"
rel="#addLayers" gi-btn gnv-tools-btn>
<span class="fa fa-plus"></span>
<span role="tooltip" data-translate="">addLayers</span>
</button>
<button class="btn btn-default" ng-model="activeTools.layers" type="submit"
rel="#layers" gi-btn gnv-tools-btn>
<span class="fa fa-tasks"></span>
<span role="tooltip" data-translate="">Layers</span>
</button>
[...]
</div>
</div>
So I've added a button let's say:
<button class="btn btn-default" ng-model="" type="submit"
rel="#" gi-btn gnv-tools-btn>
<span class="fa fa-beer"></span>
<span role="tooltip">NewButton</span>
</button>
to the template.
Then I've saved my changed and refreshed the MapView-Page on my server. But a new button will not be displayed. Also, changing the comment "Top right buttons" to something else will not make any changes to the displayed MapView-Page.
Even restarting the server won't change anything.
Could somebody explain me why AngularJS reacts like this? Any Ideas?
Turned out I had to run the page in debug mode e.g. http://localhost:8080/geonetwork/srv/ger/catalog.search?debug#/map
to force to load all AngularJS files seperately and without cache.