Expand/collapse list items in recursive tree menu in angular - json

How can i achieve that my nested lists expand on click?
At the moment it just opens the first level.
sidebar.component.html
<ul>
<ng-template #recursiveList let-list>
<li *ngFor="let item of list" (click)="listClick($event, item)">
{{item.name}}
<ul *ngIf="item.folders?.length > 0" [ngClass]="{ 'subfolder': selectedItem == item }">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.folders }"></ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: list }"></ng-container>
</ul>
Click from sidebar.component.ts
listClick(event, newValue) {
console.log(newValue);
this.selectedItem = newValue;
}
The first level works like it should. I click on the folder name and it expands. But when I click on the list element on the next level the list collapses instead of expanding further.

I think the event is bubbling up to the parent i.e. when you click on a child, you are also clicking on the parent element too. Adding event.stopPropagation() should stop the event bubbling to the parent. I.e.
listClick(event, newValue) {
console.log(newValue);
this.selectedItem = newValue;
event.stopPropagation();
}
Update: I am not sure how your data is being fetched, so I am not sure if this is 100% right. But here is a working example of how it should work (you do need the event.stopPropagation();) I have added && item.showSubfolders the *ngIf which gets toggled on click:
<ul>
<ng-template #recursiveList let-list>
<li *ngFor="let item of list" (click)="listClick($event, item)">
{{item.name}}
<ul *ngIf="item.folders?.length > 0 && item.showSubfolders" [ngClass]="{ 'subfolder': selectedItem == item }">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.folders }"></ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: list }"></ng-container>
</ul>
And the Listclick:
listClick(event, newValue) {
console.log(newValue);
this.selectedItem = newValue;
newValue.showSubfolders = !newValue.showSubfolders
event.stopPropagation()
}
working example: https://stackblitz.com/edit/angular-emz37r

Related

How to use Angular ngIf diretive to display or hide elements?

<div *ngFor = "let item of meusItems, let i=index" [ngClass]="{'selected':item[i] == i}">
<li> Nome: {{item.item.name}}</li>
<li> Nome: {{item.item.descricao}}</li>
<select class="custom-select">
<option *ngFor =" let soldado of meusSoldados"> {{soldado.soldado.name}}</option>
</select>
<button ></button>
</div>
I am using a ngFor to display the items of my database. Inside that div i also show a button and a select, but i only want to display them when the item is selected.
Imagine item 1 is selected, then the button and select is displayed for that item, but all others have no button or select.
I imagine that we can probably do it with a simple ngIf but im not seeing how?
Any help is appreciated.
An li element is not a valid direct child of a div element - only a ul element can be a direct parent of an li. what you really need to do is to nest the content inside the repeating li and have an ng-container with an *ngIf on it to conditionally show the content if the item is selected.
Note that I have followed your logic to determine if the item is selected - but there are better ways of doing that.
Also - spans are inline level elements - so you will need styling to display themn correctly and space them out - I would use flex - with the li having display: flex set on it and perhaps justify-content: space-between to separate out the spans.
<ul class="meus-items-list">
<li *ngFor = "let item of meusItems, let i=index" [ngClass]="{'selected':item[i] == i}">
<span> Nome: {{item.item.name}}</span>
<span> Nome: {{item.item.descricao}}</span>
<ng-container *ngIf="item[i] == i">
<select class="custom-select">
<option *ngFor =" let soldado of meusSoldados"> {{soldado.soldado.name}}</option>
</select>
<button >Click me</button>
</ng-container>
</li>
</ul>
You could also do this with a ul / li nested inside the li
<ul class="meus-items-list">
<li *ngFor = "let item of meusItems, let i=index" [ngClass]="{'selected':item[i] == i}">
<ul>
<li> Nome: {{item.item.name}}</li>
<li> Nome: {{item.item.descricao}}</li>
<li *ngIf="item[i] == i">
<select class="custom-select">
<option *ngFor =" let soldado of meusSoldados"> {{soldado.soldado.name}}</option>
</select>
<button >Click me</button>
</li>
</ul>
</li>
</ul>
You could even do this with CSS alone - just by applying display none to the select and button elements in all li's except for the selected one. This will still have these elements in the DOM so is probably not my first thought as to how to do it.
li:not(.selected) select,
li:not(.selected) button {
display: none;
}
For example this is a sample code how it appears :
HTML :
<div *ngIf="selected" class="alert alert-success box-msg" role="alert">
<strong>List Saved!</strong> Your changes has been saved.
</div>
TS :
export class AppComponent implements OnInit{
(...)
public selected = false;
(...)
saveTodos(): void {
//show box msg
this.selected= true;
//wait 3 Seconds and hide
setTimeout(function() {
this.selected= false;
console.log(this.selected);
}.bind(this), 3000);
}
}

Angular - css style changes in the list of objects in *ngFor

I have two components, parent:
<ng-container *ngIf="itemList != null">
<div *ngFor="let item of itemList">
<component-item [componentItem]="item"></component-item>
</div>
</ng-container>
and child (component-item):
<div class="row myClass" [ngClass]="{'selected': isSelected }" (click)="method()">
...
</div>
As a result I have list of items. I have two css styles: default and "selected". I would like to change styles of items after clicking on them like: when I click on the first item it should change to "selected" and then after clicking second item it should change to "selected" and the first one return to the default. My variable "isSelected" is a boolean type and I change its value on "true" in "method()". How can I change its value on "false" when I select another item from the list?
Try like this, it may work
In component.ts File
cssEnabled:any="";
private method(itemName:string)
{
this.cssEnabled=itemName;
}
In compontent.html :
<div class="row myClass" [ngClass]="{'selected': componentItem==cssEnabled}" (click)="method(componentItem)">
...
</div>

Hide element using ngFor and ngIf

I want to hide the other 3 elements in li element after the one of the list was clicked (the clicked list remain unhide), as I try it hide all the li element.
payment.component.ts
paymentLists = [
{
name: 'IpayEasy',
},
{
name: 'Credit Card',
},
{
name: 'Internet Banking',
},
{
name: '7-Eleven',
},
];
selectedIndex: number;
select(index:number) {
this.selectedIndex = index;
}
payment.component.html
<ul *ngIf="selectedIndex == paymentList">
<li (click)="select(paymentList)"
*ngFor="let paymentList of paymentLists; let i=index">
<span>{{paymentList.name}}</span>
</li>
</ul>
Here what have I tried,
demo
Before:
IpayEasy
Credit Card
Internet Banking
7-Eleven (clicked li)
After:
7-Eleven (li element remain unhide)
You need to update your template as following
Move ngFor to ng-container element
Update ngIf condition to be true only if there is no selected index or the matching selected index
Pass index in select function
Updated html will be as follows
<ul>
<ng-container *ngFor="let paymentList of paymentLists; let i=index" >
<li (click)="select(i)" *ngIf="selectedIndex === undefined || selectedIndex == i" [ngClass]="{'tab__list--selected--mobile': selectedIndex == paymentList}">
<span>{{paymentList.name}}</span>
</li>
</ng-container>
</ul>
For reference, here is the working version
try
<ul *ngIf="selectedIndex == paymentList">
<ng-container *ngFor="let paymentList of paymentLists; let i=index">
<li (click)="select(paymentList)" *ngIf="!selectedIndex || selectedIndex=i">
<span>{{paymentList.name}}</span>
</li>
</ng-container>
</ul>
you can use this code instead of yours :
your ts:
select(index) {
this.paymentLists = [index];
}
your HTML:
<ul *ngIf="selectedIndex == paymentList">
<li (click)="select(paymentList)"
*ngFor="let paymentList of paymentLists; let i=index">
<span>{{paymentList.name}}</span>
</li>
</ul>
Just for the record, Since my previous answer wasn't very clear. Here is a thorough explanation to the solution for the aforementioned problem.
paymentLists = [
{
name: 'IpayEasy',
},
{
name: 'Credit Card',
},
{
name: 'Internet Banking',
},
{
name: '7-Eleven',
},
];
selectedIndex: number;
select(index:number) {
this.selectedIndex = this.paymentLists.findIndex(x => x.name==paymentListNameObject.name);
this.paymentListSelected = true;
}
in the above mentioned code, the select function recieves an object instead of the index number. which can be corrected as above. Also i added a variable paymentListSelected. this variable tracks if a particular payment method has been selected.
In the HTML, you could get rid of *ngIf="selectedIndex == paymentList" and use the following:
<ul>
<li *ngFor="let paymentList of paymentLists; let i=index" (click)="select(paymentList)"
[ngClass]="{'tab__list--selected--mobile': ((i == selectedIndex)&&paymentListSelected),'hide-tab__list--unselected--mobile': paymentListSelected}">
<span>{{paymentList.name}}</span>
</li>
</ul>
Here i add 2 classes tab__list--selected--mobile which is applied to the selected payment method based on the index number which was selected by the user. And to hide the other options, i added hide-tab__list--unselected--mobile to all other options.
Finally here is a working link just in case the explanation wasn't clear enough.
https://stackblitz.com/edit/angular-display-list-d19ffv
This answer i guess qualifies for not getting DELETED!!!!!

Indirect Recursive Ng-Template in Angular 6

<ng-template #list let-Items>
<ul>
<li *ngFor="let i of Items">
<ng-container [ngTemplateOutlet]="container" [ngTemplateOutletContext]="{$implicit: i}"></ng-container>
</li>
</ul>
</ng-template>
<ng-template #container let-list>
// some code
<ng-container [ngTemplateOutlet]="list" [ngTemplateOutletContext]="
{$implicit: list.columns}"></ng-container>
</ng-template>
I have a template "container" that includes another template "item".. the container "item" includes "container" again
This is not working, I'm getting this error:
ERROR TypeError: templateRef.createEmbeddedView is not a function
Any ideas? Thanks.
This issue is quite old so you may already have found the answer but in case someone else encounter this.
The problem you had is a collision in the naming of the variable list (let-list) in your container template that overrides the list template.
If you rename the template to listTpl for example (or the variable) it should work correctly:
<ng-template #listTpl let-Items>
<ul>
<li *ngFor="let i of Items">
<ng-container [ngTemplateOutlet]="container" [ngTemplateOutletContext]="{$implicit: i}"></ng-container>
</li>
</ul>
</ng-template>
<ng-template #container let-list>
// some code
<ng-container [ngTemplateOutlet]="listTpl" [ngTemplateOutletContext]="
{$implicit: list.columns}"></ng-container>
</ng-template>
For recursion inside template, we need to understand how ng-template works along with templateOutlet in angular. If we master it the we could make the recursion easily For the reference follow following code.
<ul>
<ng-container *ngTemplateOutlet="recursiveListTmpl; context:{ $implicit: serviceNavLinks }"></ng-container>
</ul>
<ng-template #recursiveListTmpl let-list>
<li *ngFor="let item of list">
<a class="list-group-item no-border">
{{item.linkTranslationKey | translate }}
</a>
<ul *ngIf="item.childNavLinks && item.childNavLinks.length > 0">
<ng-container *ngTemplateOutlet="recursiveListTmpl; context:{ $implicit: item.childNavLinks }"></ng-container>
</ul>
</li>
This is tested in Angular 8. Did not checked for earlier versions of angular.

How to toggle mat-expansion-panel with button click?

Is there any way in which I can expand a particular mat-expansion-panel by clicking an external button?
I have tried linking to the ID of the panel, but with no success...
<mat-expansion-panel id="panel1"> ... </>
...
<button (click)="document.getElementById('panel1').toggle()>Click me</button>
Here is my stackblitz code for example
My eventual plan is to use this method to open different panels within a list generated from an array: <mat-expansion-panel *ngFor="let d of data"> ...
In your html file:
<mat-expansion-panel [expanded]="panelOpenState">
<mat-expansion-panel-header>
<mat-panel-title>
TITLE
</mat-panel-title>
</mat-expansion-panel-header>
<p>BODY</p>
</mat-expansion-panel>
<button mat-raised-button (click)="togglePanel">TOGGLE</button>
In your TS file:
panelOpenState: boolean = false;
togglePanel() {
this.panelOpenState = !this.panelOpenState
}
If you use *ngFor to generate the expansion panels:
<mat-expansion-panel [expanded]="isOpen" *ngFor="let d of data">
<mat-expansion-panel-header>
{{ d.header }}
</mat-expansion-panel-header>
<p>{{ d.content }}</p>
</mat-expansion-panel>
<button mat-raised-button (click)="togglePanel">TOGGLE</button>
If you press the button all of the expanded panels opens
simultaneously.
To open only one panel with one button, add a "expanded" property to the data array for each element like this:
data = [
{id:1, header:'HEADER 1', content:'CONTENT 1', expanded: false},
{id:2, header:'HEADER 2', content:'CONTENT 2', expanded: false},
{id:3, header:'HEADER 3', content:'CONTENT 3', expanded: false},
{id:4, header:'HEADER 4', content:'CONTENT 4', expanded: false},
]
Then in your template:
<mat-expansion-panel [(ngModel)]="d.expanded"
[expanded]="d.expanded" *ngFor="let d of data" ngDefaultControl>
<mat-expansion-panel-header>
<button (click)="toggle(d.expanded)">TOGGLE</button>
{{ d.header }}
</mat-expansion-panel-header>
<p>{{ d.content }}</p>
</mat-expansion-panel>
And the method raised by the button click:
toggle(expanded) {
expanded = !expanded;
}
<mat-expansion-panel [disabled]="true"
#mep="matExpansionPanel"
*ngFor="let foo of list">
<mat-expansion-panel-header>
<button (click)="mep.expanded = !mep.expanded">Toggle</button>
</mat-expansion-panel-header>
<p>Text</p>
</mat-expansion-panel>
Use two-way binding on the expanded attribute of mat-expansion-panel. Here is an example live in StackBlitz:
https://stackblitz.com/edit/angular-gtsqg8
<button (click)='xpandStatus=xpandStatus?false:true'>Toggle it</button>
<p>
<mat-expansion-panel [(expanded)]="xpandStatus">
<mat-expansion-panel-header>
<mat-panel-title>This an expansion panel</mat-panel-title>
<mat-panel-description>xpandStatus is {{xpandStatus}}</mat-panel-description>
</mat-expansion-panel-header>
Two-way binding on the expanded attribute gives us a way to store and manipulate the expansion status.
</mat-expansion-panel>
</p>
You can use the method toggle().
First give the element an id.
<mat-expansion-panel #matExpansionPanel>
Next, access the element from javascript. Import necessary libraries (MatExpansionPanel, ViewChild)
#ViewChild(MatExpansionPanel, {static: true}) matExpansionPanelElement: MatExpansionPanel;
Lastly, call the toggle function
this.matExpansionPanelElement.toggle(); //open(), close()
<mat-nav-list>
<mat-expansion-panel *ngFor="let row of rows" #mep="matExpansionPanel">
<mat-expansion-panel-header>
{{row}}
</mat-expansion-panel-header>
<h2>Test</h2>
<button (click)="mep.toggle()">Toggle</button>
</mat-expansion-panel>
</mat-nav-list>
Working Example:
https://stackblitz.com/edit/mat-expansion-panel-vymjsq?file=app%2Fexpansion-overview-example.html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<mat-accordion displayMode="flat" multi class="mat-table">
<section matSort class="mat-elevation-z8 mat-header-row">
<span class="mat-header-cell" mat-sort-header="vesselName"></span>
<span class="mat-header-cell" mat-sort-header="vesselName">d</span>
</section>
<mat-expansion-panel [disabled]="true" #mep="matExpansionPanel"
*ngFor="let d of data">
<mat-expansion-panel-header>
<span class="mat-cell" (click)="mep.expanded = !mep.expanded">
<mat-icon class="icon" *ngIf="!mep.expanded">expand_more</mat-icon>
<mat-icon class="icon" *ngIf="mep.expanded">expand_less</mat-icon>
</span>
<span (click)="dClicked(d)" class="mat-cell">{{d.dataSet}}</span>
</mat-expansion-panel-header>
<div><pre>{{d | json}}</pre></div>
</mat-expansion-panel>
<div class="well" *ngIf="!d || d.length == 0">
<p>There are no d for this.</p>
</div>
</mat-accordion>
html:
<mat-accordion >
<mat-expansion-panel #first="matExpansionPanel">
<mat-expansion-panel-header>
<mat-panel-title>...</mat-panel-title>
</mat-expansion-panel-header>
...
</mat-expansion-panel>
<mat-expansion-panel #second="matExpansionPanel" expanded="true">
<mat-expansion-panel-header>
<mat-panel-title>...</mat-panel-title>
</mat-expansion-panel-header>
...
</mat-expansion-panel>
</mat-accordion>
<button (click)="doSomething(first, second)">Click</button>
ts:
import { Component } from '#angular/core';
import { MatExpansionPanel } from '#angular/material';
#Component({
selector: 'app-home',
templateUrl: './home.component.html'
})
export class HomeComponent {
doSomething(first: MatExpansionPanel, second: MatExpansionPanel) {
if (first.expanded ) { // check if first panel is expanded
first.close(); // close first panel
second.open(); // open second panel
// ...
}
}
}
Read more