Dynamically nested angular material menu - javascript

Please tell me how I can solve the following problem:
I need to implement a dynamically created menu with different nesting levels depending on the data model object. At the moment, using recursion, we managed to create the menu as such, however, there is a problem of assigning the attribute [matMenuTriggerFor] for, directly, the submenu. The problem is that all subsequent submenus in fact refer to the very first, so when you hover over any of the submenus, it causes a "flip" to the original one (example on image: menu, which includes elements: Device, Extension, Queue, Queue member (with submenu elements)). Thus, for a fraction of seconds, I see the other submenu frame (example on image: submenu Grouped list), after which the very first becomes active. Of course, maybe I didn’t do everything right, so I’m turning here. Help me please. Thank you all.
imenu-item.ts
export interface IMenuItem {
name: string | string[];
link: string;
subItems: IMenuItem[];
}
dynamic-menu.service.ts
import {Inject, Injectable} from '#angular/core';
import {APP_CONFIG_ROUTES} from '../../../config/routes/app.config.routes';
import {IAppConfigRoutes} from '../../../config/routes/iapp.config.routes';
import {IMenuItem} from './imenu-item';
import {_} from '#biesbjerg/ngx-translate-extract/dist/utils/utils';
#Injectable({
providedIn: 'root'
})
export class DynamicMenuService {
private readonly appConfig: any;
constructor(#Inject(APP_CONFIG_ROUTES) appConfig: IAppConfigRoutes) {
this.appConfig = appConfig;
}
getMenuItems(): IMenuItem[] {
return [
{
name: _('labels.device'),
link: '/' + this.appConfig.routes.device,
subItems: null
},
{
name: _('labels.extension'),
link: '/' + this.appConfig.routes.extension,
subItems: null
},
{
name: _('labels.queue'),
link: '/' + this.appConfig.routes.queue,
subItems: null
},
{
name: _('labels.queueMember'),
link: null,
subItems: [{
name: _('labels.fullList'),
link: '/' + this.appConfig.routes.queueMember.all,
subItems: null
}, {
name: _('labels.groupedList'),
link: '/' + this.appConfig.routes.queueMember.grouped,
subItems: [{
name: 'subName',
link: 'subLink',
subItems: [{
name: 'subSubName1',
link: 'subSubLink1',
subItems: null
}, {
name: 'subSubName2',
link: 'subSubLink2',
subItems: null
}]
}]
}]
}
];
}
}
dynamic-menu.component.ts
import {Component, Input, OnInit} from '#angular/core';
import {IMenuItem} from './imenu-item';
#Component({
selector: 'app-dynamic-menu',
templateUrl: './dynamic-menu.component.html',
styleUrls: ['./dynamic-menu.component.scss']
})
export class DynamicMenuComponent implements OnInit {
dynamicMenuItemsData: IMenuItem[];
constructor(private dynamicMenuService: DynamicMenuService) {
}
ngOnInit() {
this.dynamicMenuItemsData = this.dynamicMenuService.getMenuItems();
}
}
dynamic-menu.component.html
<div>
<ng-container [ngTemplateOutlet]="recursiveListMenuItems"
[ngTemplateOutletContext]="{$implicit: dynamicMenuItemsData}">
</ng-container>
</div>
<ng-template #recursiveListMenuItems let-listMenuItems>
<div *ngFor="let menuItem of listMenuItems">
<ng-container [ngTemplateOutlet]="menuItem.subItems != null ? subMenuItem : simpleMenuItem"
[ngTemplateOutletContext]="{$implicit: menuItem}">
</ng-container>
</div>
</ng-template>
<ng-template #simpleMenuItem let-menuItemArg>
<a class="mat-button"
mat-menu-item
routerLink="{{menuItemArg.link}}">
<span>{{menuItemArg.name | translate}}</span>
</a>
</ng-template>
<ng-template #subMenuItem let-menuItemArg>
<a class="mat-button"
mat-menu-item
routerLink="{{menuItemArg.link}}"
[matMenuTriggerFor]="subItemsMenu">
<span>{{menuItemArg.name | translate}}</span>
<mat-menu #subItemsMenu="matMenu"
[overlapTrigger]="false">
<ng-container [ngTemplateOutlet]="recursiveListMenuItems"
[ngTemplateOutletContext]="{$implicit: menuItemArg.subItems}">
</ng-container>
</mat-menu>
</a>
</ng-template>

As a result, it turned out, relying on several similar problems with others. The examples from HERE (dynamic nested menu example) and from HERE (the problem with mat-menu hides immediately on opening) helped to figure it out (in the last example it was enough just to update zone.js by npm)

Sorry for the late answer, but maybe you can still find it helpful.
I wrote a little library called ng-action-outlet that is doing that quite neatly in my opinion.
It looks like this:
group: ActionGroup;
constructor(private actionOutlet: ActionOutletFactory) {
this.group = this.actionOutlet.createGroup();
this.group.createButton().setIcon('home').fire$.subscribe(this.callback);
this.group.createButton().setIcon('settings').fire$.subscribe(this.callback);
}
<ng-container *actionOutlet="group"></ng-container>
DEMO: https://stackblitz.com/edit/ng-action-outlet-demo?file=src/app/app.component.ts

Related

How to manipulate limited objects from an array of object using typescript/javascript

I have an array of object, I want to manipulate only 2 out of 3 object, When I am using slice, its gives some error(cannot read the property slice of undefined..) in my project, Here working fine. So I want to filter from typescript only and populate in ngfor. Here is the code below
app.component.html
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<div *ngFor="let data of json.slice(0,02); let i=index">{{data.text}}</div>
app.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular';
json: any;
ngOnInit(): void {
this.json = [
{
id: 1,
text: 'one'
},
{
id: 2,
text: 'two'
},
{
id: 3,
text: 'three'
}
];
}
}
In Angular, Always use Angular Pipes.
Codesandbox Example Here...
<div *ngFor="let data of json | slice:0:2; let i=index">{{data.text}}</div>

Angular material chips removable

I want to remove the matChip on click of cancel icon
here is my code
<mat-chip-list>
<mat-chip
*ngFor="let user of data.users"
[removable]="true"
[selectable]="true"
(removed)="remove(user.id)">
{{user.name}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
Even though [removable]="true" is set it is not removing the clicked chip
I have been following this docs example
Working example. Please, find the update files from your above link (which is available in your question) as below, while other files remains as same. If you want to check the working code, then try to replace the whole code in respective files of your link with below code to same files.
NOTE: for better readability, I have removed input element with add functionality.
chips-input-example.html
<mat-form-field class="example-chip-list">
<mat-chip-list #chipList aria-label="User selection">
<mat-chip *ngFor="let user of data.users" [selectable]="selectable" [removable]="removable"
(removed)="remove(user.id)">
{{user.name}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
</mat-chip-list>
</mat-form-field>
chips-input-example.ts
import {Component} from '#angular/core';
interface User {
name: string;
id: number;
}
interface Data {
users: User[];
}
#Component({
selector: 'chips-input-example',
templateUrl: 'chips-input-example.html',
styleUrls: ['chips-input-example.css'],
})
export class ChipsInputExample {
selectable = true;
removable = true;
data: Data = {
users: [
{name: 'User1', id: 0},
{name: 'User2', id: 1},
{name: 'User3', id: 2},
]};
remove(getId: number): void {
this.data.users = [...this.data.users.filter(({id}) => getId !== id)];
}
}
chips-input-example.css
.example-chip-list {
width: 100%;
}

Angular 4: Output data from dynamic modal to component

I am using material design and have a dialogService set up for dynamically loading MdDialog. I'm trying to make a search dialog with search filters that when submitted it brings you to a search-results component route. And I can't figure out how to output the search data to the search-results component.
./dialog-service.ts
#Injectable()
export class DialogService {
private dynamicModalComponent: DialogComponent;
public init(dynModal: DialogComponent) {
this.dynamicModalComponent = dynModal;
}
public show(component: Type<any>, configuration?: MdDialogConfig) {
this.dynamicModalComponent.showModal(component, configuration);
}
public hide() {
this.dynamicModalComponent.hideModal();
}
}
./modules/search.component.html
<div class="search-component">
<h2>Search</h2>
<md-input-container class="full-width search">
<input mdInput placeholder="search" color="primary" />
</md-input-container>
<div class="radio-groups">
<md-radio-group class="radio-buttons" [(ngModel)]="searchFilterValue">
<md-radio-button class="r-button" [value]="sf.value" *ngFor="let sf
of searchFilter">
{{sf.name}}
</md-radio-button>
</md-radio-group>
</div>
<md-dialog-actions class="actions">
<button md-button (click)="hide()">Cancel</button>
<button md-raised-button (click)="search()"
color="primary">Search</button>
</md-dialog-actions>
</div>
./modules/search.component.ts
import {Component, OnInit} from "#angular/core";
import {DialogService} from "../dialog/dialog.service";
import {Router} from "#angular/router";
#Component({
selector: 'search',
templateUrl: './search.component.html',
styleUrls:['./search.component.scss']
})
export class SearchComponent implements OnInit {
searchFilterValue;
searchFilter = [
{
name: 'Groups',
value: 'groups',
},
{
name: 'People',
value: 'users',
},
{
name: 'Events',
value: 'events',
},
{
name: 'Posts',
value: 'posts',
}
];
constructor(private _dialogService: DialogService,
private router: Router){
this.searchFilterValue = 'groups';
}
ngOnInit(){}
hide() {
this._dialogService.hide();
}
search() {
this.hide();
this.router.navigate(['/search']);
}
}
You have several options:
1) You could use optional or query routing parameters. Then as part of the router.navigate, you'd also pass along the parameters. This is a great option if you need to pass data from this component directly to another component.
2) Another option is to build a service. The service holds onto the search filter values. The search component sets the values into the service and the component then reads the values from the service.
Do one of these options sound like they could work for you?
You can define an output event for hide/close event and pass result as event argument.Other components can subscribe such event to handle result.

How does angular2 distinguish between different <ng-template>?

I am writing a table component, usually the contents of the table are fixed, but some columns may require component users to create a column template, I am prepared to pass the to pass the custom column template, but I encountered a problem, I did not know which columns I needed to customize when I passed the column data.
Now I have simplified the code as follows:
#Component({
selector: 'example',
template: `
<ng-container *ngFor="let item of data">
<div *ngIf="!item.value">
<ng-template [ngTemplateOutlet]="item.ref" [ngOutletContext]="{}"></ng-template>
</div>
<div *ngIf="item.value">{{item.value}}</div>
</ng-container>
`
})
export class DatatableComponent{
#Input() data: object[];
#ContentChildren(TemplateRef) get refs(val: TemplateRef<any>) {
// How to map refs here to map above
}
}
// use it:
#Component({
selector: 'app',
template: `
<example [data]="data">
<ng-template name="test1">test1</ng-template>
<ng-template name="test2">test2</ng-template>
</example>
`
})
export class DatatableComponent{
// I know that you can get references here
// But this hard code is too cumbersome,
// I was wondering if there was any other better way
public data: object[] = [
{name: 'test1'},
{name: 'test2'},
{name: 'test3', value: 'test3'}
]
}

How to sort out all the database and find the right value? AngularFIRE2

I do not know much English, so I use a translator, there may be errors((
I learn AngularFire2 and ran into a problem at this point (the fourth of five):
https://github.com/angular/angularfire2/blob/master/docs/4-querying-lists.md
If we scroll down, we can understand what makes this app.
By pressing the button (eg small) script comes in the items and there check item1, item2, item3, if any one of item (1,2,3) it is small, it takes its text and displays it.
(What is the problem)
And how to make brute force the entire database without giving exact values ​​check item1/2/3
That is too much to do this:
https://hsto.org/files/0a5/d13/4ba/0a5d134ba32440dcb0bd5a2f90c37c86.png
My code:
import { Component } from '#angular/core';
import { AngularFire, FirebaseListObservable, FirebaseObjectObservable } from 'angularfire2';
import { Subject } from 'rxjs/Subject';
#Component({
selector: 'app-root',
template: `
<ul>
<li *ngFor="let item of items | async">
{{ item.text }}
</li>
</ul>
<div>
<h4>Filter by size</h4>
<button (click)="filterBy('small')">Small</button>
<button (click)="filterBy('medium')">Medium</button>
<button (click)="filterBy('large')">Large</button>
</div>
`,
})
export class AppComponent {
items: FirebaseListObservable<any[]>;
sizeSubject: Subject<any>;
constructor(af: AngularFire) {
this.sizeSubject = new Subject();
this.items = af.database.list('/items', {
query: {
orderByChild: 'size',
equalTo: this.sizeSubject
}
});
}
filterBy(size: string) {
this.sizeSubject.next(size);
}
}

Categories