Change selected mat-chips within/after selectionChange - html

The concept is that I want to filter displayed information on a page based on which mat-chips are selected.
The HTML:
<mat-chip-list [multiple]="true">
<mat-chip
class="item-filter-mat-chip"
*ngFor="let itemFilter of itemFilters"
[selectable]="true"
[selected]="itemFilter.selected"
[value]="itemFilter"
(selectionChange)="updateItemFilter($event)"
(click)="chip.toggleSelected()"
#chip="matChip">
{{itemFilter.displayValue}}
</mat-chip>
</mat-chip-list>
The TypeScript item:
export interface ItemFilter {
itemType: ItemType[];
displayValue: string;
selected: boolean;
}
However I want one of the mat-chips to be the "default" that is selected if all other chips are not selected. However it seems like the logic to do the selection change has to happen in updateItemFilter, which then creates an ExpressionChangedAfterItHasBeenCheckedError. This is because I'm trying to change the selection values inside of the selectionChange method called.
The situation is this. The default filter is selected. Once a different filter is selected, the default filter should be de-selected. Multiple other filters can be selected. If the default filter is selected, all other filters should be de-selected.

After digging in, the fix appears to be no longer use (selectionChange) on the mat-chip and instead change the selection AND update the filter inside a custom (click) method.
<mat-chip-list [multiple]="true">
<mat-chip
class="item-filter-mat-chip"
*ngFor="let itemFilter of itemFilters"
[selectable]="true"
[selected]="itemFilter.selected"
[value]="itemFilter"
(click)="updateItemFilter(chip)"
#chip="matChip">
{{itemFilter.displayValue}}
</mat-chip>
</mat-chip-list>

Related

Is it possible to use [maxSelectedLabels] property in an ngif condition?

I'm using Prime NG Multiselect component and I want to show selectedItemsLabel="{0} Selected" when there are more than 3 selected checkboxes, but if all of the checkboxes are selected, then selectedItemsLabel="All" should be shown in the placeholder.
I'm new to angular and I been following documentation of this MultiSelect component, yet this doesn't show the options to able to implement multiple conditions of properties, and I was wondering if it's even possible.
Example of how It might be
<ng-template pTemplate="filter" let-value let-filter="filterCallback">
<p-multiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
<ng-template let-option pTemplate="item">
<div>
<span class="p-ml-1">{{ option.name }}</span>
</div>
<div *ngIf="[maxSelectedLabels="routeOptions.length - 1"] Then selectedItemsLabel="All"></div>
</ng-template>
</p-multiSelect>
</ng-template>
Yes, you can. First give the component a ref with # like this:
<p-multiSelect
#myMultiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
.......
Then you have access to it:
<div *ngIf="myMultiSelect.maxSelectedLabels === routeOptions.length - 1">Im visible</div>
If the option of maxSelectedLables is the length - 1 of routeOptions then the div is visible. That is how ngIf works
BUT
Thats not what you want. You wanna set the selectedItemsLabel property. And you have it not understand correctly. You set the maxSelectedLables to 3 as example AND set the selectedItemsLabel directly, too! The text of the selectedItemsLabel will be only shown if needed (controlled by the component).
<h5>Basic</h5>
<p-multiSelect #meins [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="3" selectedItemsLabel="{0} items selected">
</p-multiSelect>
Look here the Stackblitz!
The documentation of ng-prime will helps, too and say:
selectedItemsLabel: Label to display after exceeding max selected labels e.g. ({0} items selected), defaults "ellipsis" keyword to indicate a text-overflow.
UPDATE 18.02.2023
You wanna show "ALL" only if all items selected. So add the onChange event and bind the selectedItemsLabel. Why binding? It has some problems with a condition in it. So we make it inside the code.
HTML
<p-multiSelect [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="2" [selectedItemsLabel]="bindTest" (onChange)="onChange()">
</p-multiSelect>
Inside the code do the follow with onChange:
Code
onChange() {
if (this.selectedCities.length === this.cities.length) {
this.bindTest = "ALL";
this.changeRef.detectChanges();
}
else {
this.bindTest = "{0} items selected";
this.changeRef.detectChanges();
}
}
Now it works how you wish. One important thing: We use changeRef.detectChanges(); Without this the components selected text will not changing directly. Import it in the components constructor:
constructor(
private countryService: CountryService,
private primengConfig: PrimeNGConfig,
private changeRef: ChangeDetectorRef
) {
.....
I made a Stackblitz of the problem: https://stackblitz.com/edit/primeng-tablefilter-demo-ipt7y1?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
(Expand the page to the left to view the column filter in the stackblitz)
If you notice, the clear button doesn't clear the selected textbox anymore. After some testing it seems the [(ngModel)] breaks it, I think it got to do something with two-way binding? It is not shown in the stackblitz, but if you include
(onChange)="filter($event.value)"
the clear button still clears the filter from the table, but not in the selected textbox.
I found out that there is this property
[showClear]="true"
That adds an X at the end of the textbox that clears it out. Sadly, the styling/positioning is not what I need.
What could be the ways to fix the clear button ? Add a ts function to clear out the selected values? If so, how to bind it to the clear button because it is generated from
<p-columnFilter
display="menu"
menu property and I had no luck to find the way to try add/change functionality to that button.

Is there any way to add href link for mat-chips component as property or attribute

I am trying to add a new icon and possibly a href on the mat-chip component but I want it as properties and not to hardcode into that.
So the idea is to have it as a component so I can call it from another component and give data.
For example links, and icons are on the left side and right sides.
Icons on left should be an edit icon and the right will be default remove.
But at the hovering, both of them need to be highlighted.
So let's say if from another component we define the left icon then it will be shown and if not then will be not shown.
Or can it be done within a directive too?
I have till now created like this but I think I need more to do on that.
See stackblitz.
Maybe instead of ngIf is any other way to use it.
But I want for example that in the child component to send the index so then I can use it into href.
But in the child, I cannot use the index so I can iterate through the components and use the href for each of them and I want to use this component for different data.
see components in stack blitz.
users and group components.
1 of them has a variable name and the other one has a username.
https://stackblitz.com/edit/angular-3vp4xw-a9jeot?file=src/app/chips-autocomplete-example.ts
What about somethin like that :
You create a component like 'mat-chip-link':
<mat-chip *ngFor="let fruit of fruits" (removed)="remove(fruit)">
<ng-content select="[.beforeLink]"></ng-content>
<a
[href]="link"
class="mat-standard-chip"
target="_blank"
style="margin: 0; padding: 0"
>My link</a
>
<ng-content select="[.afterLink]"></ng-content>
</mat-chip>
(I am not sure about the ng-content selector, check the doc here for more info. https://angular.io/guide/content-projection).
Which has an input like #Input link: string;
Then from the parent you can call this component like that
<mat-chip-link *ngFor="let fruit of fruits" (removed)="remove(fruit)" [link]="test">
<mat-icon matChipRemove class="beforeLink" *ngIf="editable">cancel</mat-icon>
<mat-icon matChipRemove class="afterLink" *ngIf="removable">cancel</mat-icon>
</mat-chip>
It might be done easier, for example (Typescript):
Template of custom component mat-chip-link
<mat-chip-link (click)="this.clickEventHandler($event)"/>
<mat-chip>
<ng-content>
</ng-content>
</mat-chip>
</mat-chip-link>
Component
private _href: string | null = null;
#Input('href') public set href(value: string | null) {
this._href = value;
}
public clickEventHandler(e: MouseEvent) {
if(this._href !== null) {
window.open(this._href, '_blank');
} else {
console.log('No href specifief');
}
}
Usage
<mat-chip-link [href]="https://www.stackoverflow.com">
Jump to StackOverflow 🙌🏼
</mat-chip-link>

How to disable item from different component in Angular

So I have Tags component and I imported that component in my Chart-details component. In my Chart Details component I have a checkbox that will disable all the input box, drop down box, buttons that are located inside chart-details page but some reason my imported tags component is not disabling when I click the checkbox. Any suggestion or help on how to fix this so that a user cannot add or remove tags when the checkbox is clicked will be really appreciated.
Chart Details Component. HTML
//Check box to disable all the input, drop down, buttons
<mat-checkbox *ngIf="chart && workspace" color="primary" [disabled]="this.workspace.type === WorkspaceType.user"
[(ngModel)]="chart.isPublished" (ngModelChange)="publishChange($event)">Published</mat-checkbox>
//Example Button
<button color="primary" mat-flat-button (click)="saveClick()" [disabled]="this.chart.isPublished">Save</button>
// Imported Tags Component
<mc-tags [_normalTags]="chart.tags" [removable]="true" [selectable]="true"
(added)="tagAdded($event)" (removed)="tagRemoved($event)" [disabled]="this.chart.isPublished" >
</mc-tags>
I have added [disabled]="this.chart.isPublished" but I got an error saying "Can't bind to 'disabled' since it isn't a known property of 'mc-tags'. ". Also I tried (disabled) but still not working and user can still add or remove tags even though checkbox is checked.
Tags Component.HTML
<mat-chip-list #chipList [disabled]="true">
<mat-chip *ngFor="let chip of normalTags" [selectable]="selectable"
[removable]="removable"
(removed)="removeTags(chip)">
{{chip.tag}}
</mat-chip>
<input matInput #input [(ngModel)]="tagIn" [formControl]="tagCtrl2"
[matAutocomplete]="auto" />
</mat-chip-list>
Right now I have to do [disabled]="true" on mat-chip-list in Tags component.html so that user can't add or remove it. I don't want to hard code this and want to control this using Chart Detail Component Checkbox.
The disable matchip and input look like this (PIC)
It's not gonna run but I've attached the whole code for these two component over here https://stackblitz.com/edit/angular-ivy-wwfcai . thanks
Have you declared "#Input() disabled" in your child component class?
In Child.component.ts
#Input() disabled: any;
ngOnInit() {
if(disabled){
//set properties to true/false accordingly
}
}
In Parent.component.html
[disabled]="this.workspace.type === WorkspaceType.user"

Is there a way to add material select option at the end of the list?

I want to whenever I have a , when I choose an option, add it at the end of the list, instead of placing it as its order...
You can check material's stackblitz:
https://stackblitz.com/angular/jdgkdlbeldj?file=src%2Fapp%2Fselect-multiple-example.ts
If I have this list:
toppingList: string[] = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato'];
And I select initially Mushroom and Pepperoni ->MyList = [Mushroom, Pepperoni].
If I add a new option, such as Onion, I would like to be having MyList as:
[Mushroom, Pepperoni, Onion], but I get it as [Mushroom, Onion, Pepperoni].
How can I do it?
Angular material select provide sortComparator input property. It will accept comparater fuction to sort the selected value.
component.html
<mat-form-field>
<mat-label>Toppings</mat-label>
<mat-select [sortComparator]="sortComparator" [formControl]="toppings" multiple>
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
</mat-select>
</mat-form-field>
component.ts
sortComparator(a:any, b:any){
return 1;
}
Example
Instead using a form binding manually check when selectionChange event gets triggered and then compare the new selection with your saved selection. When a new selected element isn't selected in your list then push it into it when when in your new selection is an item missing remove this from your list.
<mat-form-field>
<mat-select (selectionChange)="changeMySelection($event)" [value]="myList">
// your options ....
</mat-select>
</mat-form-field>

Change the style of specific items inside ngFor

I have a set of icons that are displayed using ngFor like this:
<mat-chip *ngFor="let category of categories" (click)="selected(category)" [selected]="category.selected" [matTooltip]="category.name">
<mat-icon id="iconBar"> {{category.icon}} </mat-icon>
</mat-chip>
and I am trying to change the color of one specific icon on a button press, my trial is to have this function executes when the button is pressed:
recolorRequired(){
var loopedIcon = document.getElementById("iconBar");
console.log(loopedIcon.textContent);
var requiredIcon = loopedIcon.textContent;
//recolor
}
The result of console.log(loopedIcon.textContent); is the first icon only, from my understanding that because I am using same id for multiple elements? is there a way I can obtain information about the whole icon collection from ngFor?
Yea it is because you are using an id in a loop, id is usually not meant to be used on multiple items, you can use a class instead.
<mat-chip *ngFor="let category of categories" (click)="selected(category)" [selected]="category.selected" [matTooltip]="category.name">
<mat-icon class="iconBar"> {{category.icon}} </mat-icon>
</mat-chip>
your function becomes
recolorRequired(){
var iconBars = document.getElementsByClassName("iconBar");
//iconBars will be an array of elements who has iconBar as class
//loop through iconBars and find the element you want
//recolor
}