Angular4+ show/hide child element in HTML template - html

I am trying to implement a simple "hover on a comment to show a reply button" implementation in my angular application. Is there a way to achieve this effect by using template reference variables only?
Something along the lines of...
<mat-list>
<mat-list-item *ngFor="let comment of comments" #SomeRefToThisParticularElement (mouseenter)="SomeRefToThisParticularElement=true" (mouseleave)="SomeRefToThisParticularElement=false">
<h4>{{comment.text}}</h4>
<p> 3 replies </p>
<mat-icon *ngIf="SomeRefToThisParticularElement==true">reply</mat-icon>
</mat-list-item>
</mat-list>
Obviously, the above doesn't work cause angular won't let you assign or set template variables the way I have shown in the snippet above. But I would like to explore options to achieve this at the html template level.
Is this a good approach to do this?
Edit
Click here for a summary of possible solutions.

try this, Hide and show based on the hover index of an array.
<mat-list>
<mat-list-item *ngFor="let comment of comments; let i = index" (mouseenter)="commentIndex= i" (mouseleave)="commentIndex = -1">
<h4 mat-line>{{comment.text}}</h4>
<p mat-line style="font-size:x-small; color:rgba(0, 0, 0, 0.54)"> 3 replies </p>
<mat-icon *ngIf="commentIndex == i">reply</mat-icon>
</mat-list-item>
</mat-list>

Use a dynamic object to do this, like comment.isVisible. Now the isVisible object is dynamically created and it will be modify the value while hover in/out.
Please try this way
<mat-list>
<mat-list-item *ngFor="let comment of comments" (mouseenter)="comment.isVisible = true" (mouseleave)="comment.isVisible= false" >
<h4 mat-line >{{comment.text}}</h4>
<p mat-line style="font-size:x-small; color:rgba(0, 0, 0, 0.54)"> 3 replies </p>
<mat-icon *ngIf="comment.isVisible">reply</mat-icon>
</mat-list-item>
</mat-list>

Simplest Solution (Credit: Rahul Sharma) - use index of the element and set that as the value of a dynamic variable that is used within an *ngIf statement for show/hide behavior of an element.
Next Best Solution (Credit: Ramesh Rajendran) - Add a dynamic property to the iterable object. This assumes that the iterable object is not immutable.
Thanks to the respective contributors. Hope this helps someone in the future.

you can use css:
mat-icon{
color: transparent;
}
mat-icon:hover, mat-icon:active{
color:black;
}

Related

Variabilizing the # from <mat-menu>

I'm currently trying to generate a dynamic menu from a JSON blob in my typescript files, Angular project.
I'm using the / component from Angular Material.
My json menu has this structure :
{
id: 0,
titreTranslate: 'menu-accueil',
isDescriptionRequired: true,
routerLink: '/accueil',
icon: faHome,
isAllowed: true,
hasSubOptions: false,
trigger: 'accueil'
}
My code look something like this :
<mat-toolbar-row class="navigation-row">
<span *ngFor="let option of menuOptions">
<button mat-button
[matMenuTriggerFor]="admin"
routerLink="{{option.routerLink}}"
(keyup.enter)="router.navigate([option.routerLink], { queryParams: option.queryParams })"
[routerLinkActive]="['active-menu']"
[queryParams]="option.queryParams"
class="link bouton-menu-gauche flex-row"
*ngIf="option.isAllowed"
>
<fa-icon
*ngIf="option.icon"
class="primary-color"
[icon]="option.icon"></fa-icon>
{{ option.titreTranslate | translate }}
</button>
<mat-menu #{{option.trigger}}="matMenu">
<span *ngFor="let subOption of option.menuOptions">
<button mat-menu-item
*ngIf="option.menuOptions"
routerLink="{{subOption.routerLink}}"
(keyup.enter)="router.navigate([subOption.routerLink], { queryParams: subOption.queryParams })"
[routerLinkActive]="['active-menu']"
[queryParams]="subOption.queryParams"
class="link bouton-menu-gauche flex-row">
<fa-icon
*ngIf="subOption.icon"
class="primary-color"
[icon]="subOption.icon"></fa-icon>
{{ subOption.titreTranslate | translate }}
</button>
</span>
</mat-menu>
</span>
</mat-toolbar-row>
The line with " <mat-menu #{{option.trigger}}="matMenu"> " is what concerns me here ; I've tried many ways to variabilize this #, such as putting it directly in the Json menu or trying different syntax ; It always fail, and won't give me the code structure i want.
If i had to guess, i'd say the generated code should look like : <mat-menu #"accueil"="matMenu">, with "" that fail to compile ; but i don't get any compilation errors, so i'm lost here.
Does anybody had to work with that kind of structure before ?
(apologies for my english if it's bad, i'm french)
Check out this answer:
https://stackoverflow.com/a/44441164/2025458
It's easier to search for the answer if you call things by the name they are given in the documentation, the '#' is a template reference or template variable
And since it's inside a structural directive (*ngfor or any other directive strating with *)
It binds to a template created by the structural directive, so every loop of the ngfor generates its own nested template with its own instance of the variable, so you can just use one name

How can I convert input text on HTML to individual box-like structures each time user types a word separated by comma (see picture)

I would like to know how I can convert text data into individual boxes as shown in the picture each time user enters a word separated after a comma (I am currently creating this as an input field on Angular)
You can simply use a variable with ngModel and a *ngFor over this variable.split(',')
<input [(ngModel)]="name">
<ng-container *ngIf="name">
<div *ngFor="let item of name.split(',')">
{{item}}
</div>
</ng-container>
NOTE: You can use mat-chip-list in the way
<mat-chip-list *ngIf="name">
<mat-chip
*ngFor="let item of name.split(',');let i=index">
{{ item }}
<button matChipRemove>
<mat-icon (click)="remove(i)">cancel</mat-icon>
</button>
</mat-chip>
</mat-chip-list>
where
remove(index:number)
{
const list=this.name.split(",") //get the list
list.splice(index,1) //remove the element at position "index"
this.name=list.length?list.join(','):null //give value to name using join
}
stackblitz
NOTE: If you only need the "list" based in a variable just give value to the variable
Looks like you are looking for something like Angular Material Chips.
https://material.angular.io/components/chips/examples

Reference ngFor value in component

I have a nested ngFor statement. I need to retrieve the value of my first ngFor on button click.
I have tried the following:
use template reference variable
use attribute binding
use Input decorator
This is my code:
<mat-expansion-panel *ngFor="let item of Datasource;">
<mat-expansion-panel-header style="display:flex" class="mat-row">
{{item.Header}}
</mat-expansion-panel-header>
<mat-selection-list [(ngModel)]="selectedOptions">
<mat-list-option *ngFor="let line of item.match; let i= index;" [value]="line">
<div class="container-name">
<div class="col-6">{{i}} - {line.user.Name }} vs {{ line.user.Address }}</div>
</mat-list-option>
</mat-selection-list>
<div style="text-align:center; padding: 20px">
<button mat-raised-button color="primary" (click)="submit()" type="submit">Add</button>
</div>
</mat-expansion-panel>
Can this be achieved?
Well, you need to clone that object properties first. As that object is linked to the template, when you manipulate it, it is manipulated on template too. You can use var obj = Object.assign({}, actual obj) and then do the manipulation on obj instead of actual one. Then it will not get affected in template. Hope it helps.

mat-list in angular 5

I have a question about implement mat-list in angular 5.
I had this code, when I had the divider insede the mat-list-item but the divider line didn't show always
<mat-list >
<mat-list-item class="pathitem" *ngFor="let item of Lst" style="height: 76px;">
<div>
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
</div>
<mat-divider class="background-divider"></mat-divider>
</mat-list-item>
</mat-list>
then I change that for this:
<mat-list *ngFor="let item of LstTramosFiltro" >
<mat-list-item class="pathitem" style="height: 76px;">
<div>
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
</div>
</mat-list-item>
<mat-divider class="background-divider"></mat-divider>
</mat-list>
My question is which of the two is the correct solution if you use ngfor in mat-list or use in mat-list-item; and where the use of the mat-divider is correct
thanks for your help.
First, ngfor should be placed in the mat-list-item instead of mat-list, otherwise you will get multiple list and each have multiple mat-list-item, and if you have multiple sections you can use mat-divider to divide into different sections, the mat-divider should be placed between two mat-list-item not inside the mat-list-item. Check https://material.angular.io/components/list/overview#lists-with-multiple-sections for details.
the first one is current. The item you need to iterate over is the one that should have ngFor. If you want to include the divider, you may wrap both items into ng-template like this:
<mat-list>
<ng-template *ngFor="let item of LstTramosFiltro">
<mat-list-item class="pathitem" style="height: 76px;">
<div>
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
</div>
</mat-list-item>
<mat-divider class="background-divider"></mat-divider>
</ng-template>
</mat-list>
You should use the ngFor for the mat-list-item, not mat-list, assuming you only want to display a single list of items.
Doc references: https://material.angular.io/components/list/examples, https://material.angular.io/components/divider/overview
If you want the divider to show after every item except the last, it can be inserted as part of the mat-list-item (which I think you did), like so:
<mat-list>
<mat-list-item class="pathitem" *ngFor="let item of Lst; last as last" style="height: 76px;">
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
<mat-divider class="background-divider" *ngIf="!last"></mat-divider>
</mat-list-item>
</mat-list>

How to show/hide in Angular2

I have a component that show/hide element by clicking a button.
This is my html
<div *ngFor="let history of histories | sortdate: '-dateModified'">
<p><b>{{ history.remarks }}</b> - <i>{{history.dateModified | date:'short'}}</i></p>
<a href="google.com"
[class.datatable-icon-right]="history.$$expanded"
[class.datatable-icon-down]="!history.$$expanded"
title="Expand/Collapse Row"
(click)="toggleExpandRow(history)"></a>
<!-- hide/show this by clicking the button above.-->
<div *ngFor="let step of history.steps; let i = index">
<b>{{i+1}}.</b> {{step}}
<span class="clear"></span>
</div>
<hr />
</div>
and my .ts
toggleExpandRow(row) {
console.log('Toggled Expand Row!', row);
//row
return false;
}
trying to search but, can't find any same sample.
On jquery, I can do this, but on Angular2, I am having hard time to figure this.
There are two options:
1- You can use the hidden directive to show or hide any element
<div [hidden]="!edited" class="alert alert-success box-msg" role="alert">
<strong>List Saved!</strong> Your changes has been saved.
</div>
2- You can use the ngIf control directive to add or remove the element. This is different of the hidden directive because it does not show / hide the element, but it add / remove from the DOM. You can loose unsaved data of the element. It can be the better choice for an edit component that is cancelled.
<div *ngIf="edited" class="alert alert-success box-msg" role="alert">
<strong>List Saved!</strong> Your changes has been saved.
</div>
Use the ngIf in your repeated rows. Create a boolean property called showStep to indicate whether the row should be expanded or not.
<div *ngFor="let step of history.steps; let i = index" ngIf="history.showStep">
<b>{{i+1}}.</b> {{step}}
<span class="clear"></span>
</div>
Then, in your .ts file:
toggleExpandRow(history) {
history.showStep = !history.showStep
//note the same porperty of showStep that is used in your html
}
Extra:
In fact, to save a few lines of codes, you don't even need the toggleExpandRow function at all. You can do it inline in your html:
//other attributes omitted for brevity
<a (click)="history.showStep = !history.showStep">