My goal is to create Angular Material Chips that are unique to each expansion panel. For example, panel 1 should have chips called chip 1 and chip 2, while panel 2 should have chip 3 and chip 4. However, the panels should all show up on a singular page (such as localhost:4200).
When I run my current code, it produces chips that appear on every panel, and when a chip is deleted, it is deleted from every panel as well. I believe that the solution lies in creating an *ngFor loop with tags being replaced with p.tags, where adding a tag will be unique to that object’s tags array. However, I can’t figure out how push that data back into a singular object and then display that data on my html page.
Here's what the .ts code looks like.
import { Component, OnInit } from "#angular/core";
import { MatChipInputEvent } from "#angular/material/chips";
import { COMMA, ENTER } from "#angular/cdk/keycodes";
export interface People {
people: Person[];
}
export interface Person {
name: string;
id: string;
tags: string[];
}
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class PersonComponent implements OnInit {
persons;
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
tags: string[] = [];
constructor(private personService: PersonService) {}
ngOnInit() {
this.personService
.getStudents()
.subscribe((data) => (this.persons = data.persons));
}
add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
if ((value || "").trim()) {
this.tags.push(value.trim());
}
if (input) {
input.value = "";
}
}
remove(tag: string): void {
const index = this.tags.indexOf(tag);
if (index >= 0) {
this.tags.splice(index, 1);
}
}
}
Here's what the .html code looks like.
<mat-expansion-panel *ngFor="let p of persons>
<mat-expansion-panel-header [collapsedHeight]="'125px'" [expandedHeight]="'125px'">
{{ p.name }}
</mat-expansion-panel-header>
<mat-form-field>
<mat-chip-list #chipList>
<!-- tags should be switched to p.tags here I think -->
<mat-chip *ngFor="let tag of tags" [selectable]="selectable"
[removable]="removable" (removed)="remove(tag)">
{{tag}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input placeholder="Add a tag"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
</mat-form-field>
</mat-expansion-panel>
Related
I'm learning Angular and wondering how I can't hide some item and show certain items only when the user select specific item in the drop down list.
For example, In my page, I have Chart TextBox, Text TextBox, Grid TextBox and a drop down list that contain Chart, Text, and Grid. when ever user select Text, I want to show only Text TextBox and hide the rest.
right now, the three chart type options are showing on drop drop list when ever I run the project but it's not doing anything when I select Text and also I got an error on my ngIf saying that
"Property 'text' does not exist on type 'ChartType'."
I would be really appreciate if can somebody teach me or help me.
The problem I have is in the project I found from from github, the data for drop down list is in the another file called chart.model.ts and it written like this
export class ChartUtil {
public static getChartTypeDisplay(type: ChartType): string {
switch (type) {
case ChartType.chart:
return "Chart"
case ChartType.text:
return "Text";
case ChartType.grid:
return "Grid";
default:
return "unknown";
}
}
}
and display it like this
design.component.ts
chartTypes: TypeListItem[] = [];
setupTypes() {
let keys = Object.keys(ChartType);
for (let i = 0; i < (keys.length / 2); i++) {
this.chartTypes.push({ value: parseInt(keys[i]), display: ChartUtil.getChartTypeDisplay(parseInt(keys[i])) })
}
html
<ng-container *ngIf="chart.chartTypes == chartTypes.text">
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<input matInput />
</mat-form-field>
I can't uploaded the full project on stackblitz but I uploaded all the code from those file over https://stackblitz.com/edit/angular-ivy-dmf3vn
This is normally how you would tackle a ng-switch
Component Code (badly done)
import { Component, VERSION, OnInit } from '#angular/core';
export class ChartType {
chart = 1;
text = 2;
grid = 3;
}
export class TypeListItem {
public value = 0;
public display = '';
public chartType = -1;
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
public chartTypesEnum = new ChartType();
public chartTypes: TypeListItem[] = [];
ngOnInit() {
let keys = Object.keys(this.chartTypesEnum);
for (let i = 0; i < (keys.length ); i++) {
this.chartTypes.push(
{
value: parseInt(ChartType[keys[i]]),
chartType: this.chartTypesEnum[keys[i]],
display: `This is a ${keys[i]}`
})
}
}
}
HTML (again badly done but simple)
<ng-container *ngFor="let chart of chartTypes">
<ng-container [ngSwitch]="chart.chartType" >
<div *ngSwitchCase="chartTypesEnum.chart">Chart</div>
<div *ngSwitchCase="chartTypesEnum.grid">Grid</div>
<div *ngSwitchCase="chartTypesEnum.text">Text</div>
</ng-container>
</ng-container>
Example
https://stackblitz.com/edit/angular-ivy-bievwr
Example 2
https://stackblitz.com/edit/angular-ivy-jhv4bq
This solution will render an Angular Material dropdown List using Enum with the ChartTypes insted of the your switch.
The Component:
import { Component, OnInit } from '#angular/core';
export enum ChartTypes {
Chart = 'Chart',
Text = 'Text',
Grid = 'Grid',
}
#Component({
selector: 'app-select-by-type',
templateUrl: './select-by-type.component.html',
styleUrls: ['./select-by-type.component.css']
})
export class SelectByTypeComponent implements OnInit {
// create an enum variable to work with on the view
chartTypes = ChartTypes;
// initialize the dropdown to a default value (in this case it's Chart)
chartsValue: string = ChartTypes.Chart;
constructor() { }
ngOnInit() {
}
}
The View:
<mat-form-field appearance="fill">
<mat-select required [(value)]="chartsValue">
<mat-option *ngFor="let chartType of chartTypes | keyvalue" [value]="chartType.value">{{chartType.value}}
</mat-option>
</mat-select>
<mat-label>Chart Type</mat-label>
</mat-form-field>
<ng-container *ngIf="chartsValue === chartTypes.Text">
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<input matInput />
</mat-form-field>
</ng-container>
<ng-container *ngIf="chartsValue === chartTypes.Chart">
Chart In Template
</ng-container>
<ng-container *ngIf="chartsValue === chartTypes.Grid">
Grid In Template
</ng-container>
this is my angular materiel auto complete code
<input type="search" id="setId" name="setId" [attr.list]='collectionType' [(ngModel)]="selValue" class="text-box"
placeholder="--Select--" (focus)="ValidateParent()" (keyup.tab)="test()" (keyup)="EmitValues($event)" [id]="setId"
[matAutocomplete]="auto" [title]="selValue" [placeholder]='WaterMarkText'>
<div [hidden]="IsCascading">
<mat-autocomplete [id]="collectionType" #auto="matAutocomplete" (optionSelected)='onChange($event)'>
<mat-option *ngFor="let items of codeList" [value]="items.text" [attr.data-text]='items.text' [id]="items.value">
{{items.text}}
</mat-option>
</mat-autocomplete>
</div>
Angular material had a problem with tab selection.like the materiel auto complete not able to select the value while click the tab button. but it's working while click the enter button. So manually I need to overwrite the enter key event on tab key event. How could possible?
Improve my comment, and based on the response we can create a directive
import {
Directive,
AfterViewInit,
OnDestroy,
Optional
} from '#angular/core';
import {
MatAutocompleteTrigger
} from '#angular/material';
#Directive({
selector: '[tab-directive]'
})
export class TabDirective implements AfterViewInit, OnDestroy {
observable: any;
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger) {}
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption) {
this.autoTrigger.writeValue(this.autoTrigger.activeOption.value)
}
})
}
ngOnDestroy() {
this.observable.unsubscribe();
}
}
You use:
<input tab-directive type="text" matInput [formControl]="myControl"
[matAutocomplete]="auto" >
(see stackblitz)
Update We can control only tab.key, else always you close, you get the selected value, so
#Directive({
selector: '[tab-directive]'
})
export class TabDirective {
observable: any;
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger) {}
#HostListener('keydown.tab', ['$event.target']) onBlur() {
if (this.autoTrigger.activeOption) {
this.autoTrigger.writeValue(this.autoTrigger.activeOption.value)
}
}
}
(see a new stackblitz)
Update 2 I don't believe this answer has so many upvotes because it's wrong. As #Andrew allen comments, the directive not update the control. Well, It's late, but I try to solve. One Option is use
this.autoTrigger._onChange(this.autoTrigger.activeOption.value)
Anohter idea is inject the ngControl, so
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger,
#Optional() private control: NgControl) {}
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption) {
const value = this.autoTrigger.activeOption.value;
if (this.control)
this.control.control.setValue(value, {
emit: false
});
this.autoTrigger.writeValue(value);
}
})
}
I'm a little late to the party; so there's one annoying issue with Eliseo's answer: It autocompletes even when the user has already selected a different option from the panel with a mouse click. The workaround I finally came up with was not straight forward at all.
/**
* Selects currently active item on TAB
* #example
* <input vxTabActiveOption [matAutocomplete]="matAutocomplate">
* <mat-autocomplete #matAutocomplate="matAutocomplete" autoActiveFirstOption>
*/
#Directive({ selector: '[vxTabActiveOption]' })
export class TabActiveOptionDirective implements AfterViewInit, OnDestroy {
private readonly destroy$ = new Subject<void>();
/**
* Whether or not the autocomplete panel was open before the event
*/
private panelOpen = false;
constructor(
private autoTrigger: MatAutocompleteTrigger,
private control: NgControl
) { }
ngAfterViewInit() {
const autocomplete = this.autoTrigger.autocomplete;
merge(
autocomplete.opened.pipe(map(() => true)),
autocomplete.closed.pipe(map(() => false))
).pipe(
takeUntil(this.destroy$),
delay(0)
).subscribe(value => this.panelOpen = value);
}
#HostListener('keydown.tab')
onBlur() {
if (this.panelOpen && this.autoTrigger.activeOption) {
const value = this.autoTrigger.activeOption.value;
this.control.control.setValue(value, { emit: false });
this.autoTrigger.writeValue(value);
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Yes, I know it's an old question, but there're a better aproach that is use a directive like
#Directive({ selector: '[tab-selected]' })
export class TabSelected implements AfterViewInit {
constructor(private auto: MatAutocomplete) {}
ngAfterViewInit() {
this.auto._keyManager.onKeydown = (event: KeyboardEvent) => {
switch (event.keyCode) {
case TAB:
if (this.auto.isOpen) {
const option = this.auto.options.find(x => x.active);
if (option) {
option.select();
event.preventDefault();
return;
}
}
this.auto._keyManager.tabOut.next();
break;
case DOWN_ARROW:
this.auto._keyManager.setNextItemActive();
break;
case UP_ARROW:
this.auto._keyManager.setPreviousItemActive();
break;
}
};
}
}
You use like
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Number</mat-label>
<input type="text"
placeholder="Pick one"
aria-label="Number"
matInput
[formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete tab-selected #auto="matAutocomplete"
autoActiveFirstOption >
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
You can be on work in this stackblitz
I have a database table airline and I want to show all data from the table in a drop down list and then to be able to choose one item from the list.
airline.ts
export class Airline {
airline_id: number;
name: string;
symbol: string;
}
My HTML code, that doesn't work (only shows drop down list without data inside):
<mat-form-field>
<mat-select placeholder="Airlines" [(ngModel)]="selectedValue" name="airline">
<mat-option *ngFor="let airline of airlines" [value]="airline.name">
{{airline.symbol}}
</mat-option>
</mat-select>
</mat-form-field>
Actually it looks like this (without data inside the list):
airlineService.ts
export class AirlineService {
private baseUrl = 'http://localhost:8080/api/airlines';
constructor(private http: HttpClient) {
}
getAirline(airline_id: number): Observable<Airline> {
return this.http.get<Airline>(`${this.baseUrl}/${airline_id}`);
}
getAirlinesList(): Observable<any> {
return this.http.get(`${this.baseUrl}`);
}
}
airlines-list.component.ts
export class AirlinesListComponent implements OnInit {
airlines: Observable<Airline[]>;
airline_id: number;
selectedValue: string;
#Input() airline: Airline;
constructor(private airlineService: AirlineService) { }
ngOnInit() {
this.reloadData();
}
reloadData() {
this.airlines = this.airlineService.getAirlinesList();
}
}
Since you declared the airlines variable as an Observable, you need to treat is as such when referring to it in the template.
Change your ngFor from this:
*ngFor="let airline of airlines"
to this:
*ngFor="let airline of (airlines | async)"
I have a stackblitz as a guide.
I am wanting to display a list of material cards that I click an 'edit' button, to which I can edit text fields, and when I click on the 'save' icon, it of course saves by triggering an function etc.
I am struggling however to get to grips with how this all works within Angular and the Material nature of my app.
html
<form id="myForm" [formGroup]="thisIsMyForm">
<mat-card [formGroup]="x" *ngFor="let x of data; let i = index">
<mat-form-field>
<label for="{{x.name}}">Name</label>
<input formControlName="name" id="{{x.name}}" matInput value="{{x.name}}">
</mat-form-field>
<mat-form-field>
<label for="{{x.type}}">Type</label>
<input formControlName="type" id="{{x.type}}" matInput value="{{x.type}}"/>
</mat-form-field>
</mat-card>
</form>
ts
import { Component, ViewChild } from '#angular/core';
import {MatSnackBar} from '#angular/material';
import {FormArray, FormBuilder, FormGroup, Validators} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
thisIsMyForm: FormGroup
data = [
{name:"one", type:"one"},
{name:"two", type:"two"},
{name:"three", type:"three"},
];
constructor(private formBuilder: FormBuilder) {}
onSubmit() {
// Here I would like to be able to access the values of the 'forms'
}
}
You are diving into the deep end for sure, trying to build a dynamic reactive form within the scope of an *ngFor is a challenge. I will walk you through it the best I can.
You will need to create an array for controls, in your constructor create your form setting formArrayName as an empty array using this.formBuild.array([])... call this whatever you want, I just used formArrayName for demonstration purposes.
After the form is instantiated with an empty array call this.buildForm()
constructor(private formBuilder: FormBuilder) {
this.thisIsMyForm = new FormGroup({
formArrayName: this.formBuilder.array([])
})
this.buildForm();
}
In your buildForm() iterate over your data[] and push controls for each index while assigning the default value and a default state of disabled.
buildForm() {
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
Object.keys(this.data).forEach((i) => {
controlArray.push(
this.formBuilder.group({
name: new FormControl({ value: this.data[i].name, disabled: true }),
type: new FormControl({ value: this.data[i].type, disabled: true })
})
)
})
console.log(controlArray)
}
Please Note: console.log(controlArray.controls) results in the following output... each index is a FormGroup with two controls name and type
0: FormGroup
1: FormGroup
2: FormGroup
In your html you will need to establish a container hierarchy that mimics the thisIsMyForm you just created.
parent:thisIsMyForm
child:formArrayName
grandchild:i as formGroupName
grandchild is important because it matches the console log of controlArray.controls in previous step
<form id="myForm" [formGroup]="thisIsMyForm">
<div [formArrayName]="'formArrayName'">
<mat-card *ngFor="let x of data; let i = index">
<div [formGroupName]="i">
Create edit and save buttons based on control disabled state
<button *ngIf="formControlState(i)" (click)="toggleEdit(i)">Enable Edit</button>
<button *ngIf="!formControlState(i)" (click)="toggleEdit(i)">Save</button>
Create methods in component to receive the index as an argument and handle logic to hide buttons and toggle input fields enable and disable state.
toggleEdit(i) {
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
if(controlArray.controls[i].status === 'DISABLED'){
controlArray.controls[i].enable()
}else{
controlArray.controls[i].disable()
}
}
formControlState(i){
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
return controlArray.controls[i].disabled
}
Submit button that console.log's the form value when clicked... also disable button while any of the input formControls are in enabled state.
<button [disabled]="thisIsMyForm.get('formArrayName').enabled" (click)="onSubmit()">Submit Form</button>
onSubmit() {
// Here I would like to be able to access the values of the 'forms'
console.log(this.thisIsMyForm.value)
}
Stackblitz
https://stackblitz.com/edit/dynamic-form-ngfor-otbuzn?embed=1&file=src/app/app.component.ts
Doing it with QueryList:
your html (this is an example):
<ng-container *ngFor="let x of data; let i = index">
<div class="ctr">
<span #names class="item">{{x.name}}</span>
<span #types class="item">{{x.type}}</span>
<span class="button" (click)="showData(i)">Show data</span>
</div>
</ng-container>
<h2>Selected values: </h2>
Selected name: {{selectedName}} <br>
Selected type: {{selectedType}}
some css just for the style
.ctr{
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.item{
margin-right:40px;
}
.button{
border: 1px solid black;
padding: 2px 5px 2px 5px;
cursor: pointer;
}
the component:
import { Component, QueryList, ViewChildren } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
#ViewChildren('names') names:QueryList<any>;
#ViewChildren('types') types:QueryList<any>;
selectedName: string;
selectedType: string;
data = [
{name:"one", type:1},
{name:"two", type:2},
{name:"three", type:3},
];
showData(index){
let namesArray = this.names.toArray();
let typesArray = this.types.toArray();
this.selectedName = namesArray[index].nativeElement.innerHTML;
this.selectedType = typesArray[index].nativeElement.innerHTML;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-j2n398?file=src%2Fapp%2Fapp.component.ts
I have an ag-grid which is rendering table from a .json file and an external Quick filter that is searching through ag-grid on key input on the filter. After someone searches the search term is displayed in the form of angular material chip with a "X" sign to close the chip with remove function. I want to reload the ag-grid to its default state once someone cancel/close the chip and also to include multiple filters in it using the chip. Here is my sample code, but I'm struggling with setting it up.
Html-
<div class="container">
<mat-form-field class="demo-chip-list" *ngIf="gridApi">
<mat-chip-list #chipList>
<div style="width:100%; margin-left:10%;"><label><span class="search-button">Search Funds</span></label>.
<input class="search-input"
[ngModel]="filterText"
(ngModelChange)=
"gridApi.setQuickFilter
($event)"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)" />.
</div><br/><div style="width:100%; margin-left:10%;"><mat-chip *ngFor="let fruit of fruits"
[selectable]="selectable"
[removable]="removable"
(click)="onGridReady(params)"
(remove)="remove(fruit)">
{{fruit.name}}
<mat-icon matChipRemove *ngIf="removable" ><sup>x</sup></mat-icon></mat-chip></div></mat-chip-list>.
</mat-form-field>
<div class="header" style="display:inline"></div><div> <ag-grid-angular
style="position:absolute;padding-left:5%; bottom:0px;width: 90%; height: 350px;" #agGrid id="myGrid" class="ag-fresh" [columnDefs]="columnDefs"
[animateRows]="true"
[enableRangeSelection]="true"
[enableSorting]="true"
[enableFilter]="true"
[pagination]="true"
(gridReady)="onGridReady($event)">
</ag-grid-angular></div></div>
Component-
#Component({
selector:
'app-funds-table',
templateUrl:
'./funds-table.component.html',
styleUrls:
['./funds-table.component.css']
})
export class
FundsTableComponent
implements OnInit {
visible: boolean = true;
selectable: boolean = true;
removable: boolean = true;
addOnBlur: boolean = true;
// Enter, comma
separatorKeysCodes = [ENTER, COMMA];
fruits = [
{ name: 'ABC' }
];
add(event: MatChipInputEvent): void
{
let input = event.input;
let value = event.value;
// Add our fruit
if ((value || '').trim()) {
this.fruits.push({ name:
value.trim() });
}
// Reset the input value
if (input) {
input.value = '';
}
}
remove(fruit: any): void {
let index =
this.fruits.indexOf(fruit);
if (index >= 0) {
this.fruits.splice(index, 1);
}
}
private gridApi;
private gridColumnApi;
private columnDefs;
private filterText = "";
ngOnInit() {}
constructor(private http:
HttpClient ){
this.columnDefs = [{headerName:
"Ticker", field: "Ticker"},
{headerName: "Id", field: "Id"},
{headerName: "Utilities", field:
"Utilities"}
];
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi =
params.columnApi;
this.http.get
("/fundsData/fund_info.json". )
.subscribe
(data =>
{this.gridApi.setRowData(data);
});
}
}
According doc:
You can reset filter via direct api call
api.setQuickFilter(''); - empty for reset filter