I have a set of mat checkboxes and mat selects that gets populated based on values from a config file. Once these components are created on the page they need to get selected based on the data associated with the user displayed.I have written functions to make them selected based on the data, but don't see them working as expected. Here is my code:
html:
<table class="table">
<tr class="node-width" *ngFor="let element of configArray">
<mat-checkbox color="primary" name="element.type" [checked]="isCheckboxChecked(element.type)"
(change)="getCheckBoxvalue($event, element)">{{element.type}}
</mat-checkbox>
<mat-select placeholder="Select Sub Type" name="subtype" [formControl]="subtypes"
(selectionChange)="getSelectValue($event.value, element)" multiple>
<mat-option *ngFor="let subtype of element.subtypes" [value]="subtype"
[selected]="isOptionSelected(element.type, subtype)">
{{subtype}}
</mat-option>
</mat-select>
</tr>
</table>
typescript:
isCheckboxChecked(elementtype: string): boolean {
this.getTypesForTheCurrentApplication.forEach((item) => {
if (item.element === elementtype) {
this.checked = true;
} else {
this.checked = false;
}
});
this.changeDetector.markForCheck();
return this.checked;
}
isOptionSelected(elementtype: string, subtype: string): boolean {
this.getTypesForTheCurrentApplication.forEach((item) => {
if (item.element === elementtype) {
item.subtypes.forEach((element) => {
if (element === subtype) {
this.selected = true;
} else {
this.selected = false;
}
});
}
});
this.changeDetector.markForCheck();
return this.selected;
}
The function named 'getTypesForTheCurrentApplication' is called to query the back end db to get the json array values for the data displayed. Any help is greatly appreciated.
Thanks.
Related
How to do a drop-down with search. I don't know how to implement the search here in my drop down. hoping someone can help me. I can do the dropdown with search if the data I'm getting is static.
here in my code, the data I'm supplying in my drop is dynamic supplier: SupplierDTO[]; This (supplier) is my created model wherein I'm fetching the fields I need.
.html
<mat-form-field class="box" appearance="fill">
<mat-label>Supplier</mat-label>
<mat-select formControlName="supplierId" (selectionChange)="selectedSupplier($event)">
<mat-option *ngFor="let items of supplier" [value]="items.id">
{{items.companyName}}
</mat-option>
</mat-select>
</mat-form-field>
.ts
export class DeliveryReceiptComponent implements OnInit {
supplier: SupplierDTO[];
}
ngOnInit(): void {
this.selectedSupplier;
}
selectedSupplier(trigger: MatSelectChange)
{
this.selectedSupplierID = trigger.value;
this.supplierSvc.getSupplierDR(this.selectedSupplierID).subscribe((response: any) => {
var splitted = response.split(":", 4)
this.deliveryReceiptForm.get('Bank').patchValue(splitted[0]);
this.deliveryReceiptForm.get('AccountName').patchValue(splitted[1]);
this.deliveryReceiptForm.get('AccountNumber').patchValue(splitted[2]);
this.deliveryReceiptForm.get('Branch').patchValue(splitted[3]);
if(this.deliveryReceiptForm.get('AccountName').value === this.dbmRequired ){
this.APRDisplayed = true;
this.deliveryReceiptForm.get('purchaseRequestNumber').addValidators(Validators.required);
this.deliveryReceiptForm.get('purchaseRequestDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('drNumFrSupplier').addValidators(Validators.required);
this.deliveryReceiptForm.get('drDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('aprNum').addValidators(Validators.required);
this.deliveryReceiptForm.get('aprDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('purchaseOrderDate').setValue('0001-01-01');
this.deliveryReceiptForm.get('saleInvoiceDate').setValue('0001-01-01');
this.deliveryReceiptForm.get('aprDate').reset();
}
else{
this.APRDisplayed = false;
this.deliveryReceiptForm.get('purchaseOrderNumber').addValidators(Validators.required);
this.deliveryReceiptForm.get('purchaseOrderDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('purchaseRequestNumber').addValidators(Validators.required);
this.deliveryReceiptForm.get('purchaseRequestDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('saleInvoiceDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('saleInvoiceNumber').addValidators(Validators.required);
this.deliveryReceiptForm.get('drNumFrSupplier').addValidators(Validators.required);
this.deliveryReceiptForm.get('drDate').addValidators(Validators.required);
this.deliveryReceiptForm.get('aprDate').setValue('0001-01-01');
}
})
}
It seems like your response is not an array, but an object instead. Therefore you should map the following:
getData(){
return this.http.get('https://localhost:44350/api/EamisDeliveryReceipt/list')
.pipe(
map(response => response.items.map(suppliers => suppliers.supplier.companyName)
)
)
}
Hint: You should probably define an interface for the type returned by this API. This way it's way easier to perform any mapping on the data.
To make a demo of the problem I have a table where I show a list of details and several mat-select that when selecting an option the "size" of this marked option is loaded in the td.
The problem is that it does not load the "size" in the same row where an option is selected. How I can get this? The size is shown in the td but in order from top to bottom.
For example if there is no option marked and I select an option from row 3. The "size" of this option would be loaded in the first row and if I mark an option in any other row the data is loaded following the order of the rows.
I have the demo in stackblitz: DemoStackblitz
HTML
<table class="table">
<tr>
<th>Titles</th>
<th>Size</th>
<th>Detail</th>
<th>Duration</th>
</tr>
<tr *ngFor="let row of dataDetails let i = index">
<td>
<mat-form-field appearance="outline">
<mat-select [formControl]="dataCtrl" placeholder="Select" [compareWith]="compareItems" #singleSelect>
<mat-option>
<ngx-mat-select-search [formControl]="dataFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let op of filteredData | async" [value]="op.id" (click)="onSelect(op)"
[disabled]="op.condition===1">
{{op.title}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
<td>{{OptionSelected[i]?.size}}</td>
<td>{{row.detail}}</td>
<td>{{row.duration}}</td>
</tr>
</table>
TS
dataTitles: any = DataTitles;
dataDetails: any = DataDetails;
dataCtrl: FormControl = new FormControl();
dataFilterCtrl: FormControl = new FormControl();
filteredData: ReplaySubject<any> = new ReplaySubject<any>(1);
#ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;
_onDestroy = new Subject<void>();
ngOnInit() {
this.dataCtrl.setValue(this.dataDetails[0]);
this.filteredData.next(this.dataTitles.slice());
this.dataFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterData();
});
}
compareItems(i1: any, i2: any) {
return i1 && i2 && i1.key === i2.key;
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
OptionSelected: any = [];
onSelect(option: any) {
var objIndex = this.dataTitles.findIndex(x => x.id == option.id);
if (this.dataTitles[objIndex].title !== 'None') {
this.dataTitles[objIndex].condition = 1;
}
if (this.OptionSelected.length === 0) {
this.OptionSelected.push({ id: option.id, size: option.size });
} else {
let check = this.OptionSelected.map((item: any) => item.id).includes(
option.id
);
if (check === false) {
this.OptionSelected.push({ id: option.id, size: option.size });
}
}
console.log(this.OptionSelected);
}
filterData() {
if (!this.dataTitles) {
return;
}
let search = this.dataFilterCtrl.value;
if (!search) {
this.filteredData.next(this.dataTitles.slice());
return;
} else {
search = search.toLowerCase();
}
this.filteredData.next(
this.dataTitles.filter(
(x: any) => x.title.toLowerCase().indexOf(search) > -1
)
);
}
Issue
You´r pushing the current selected value into the OptionSelected during the call of
onSelect. This causes the mismatch of selected row data and which row actualy shows the data.
Pseudo code example:
Select TitleA in Row 4
Push new Data into OptionSelected
OptionSelected[0]?.size displays data in the first row
Solution
You are already tracking the value of the current selected value with the template variable #singleSelect over here:
<mat-select ... #singleSelect>
The <mat-select> component has a value input.
#Input()
value: any Value of the select control.
So you can display the current value by simply using the template ref:
<td>{{singleSelect.value}}</td>
Stackblitz example
I have implemented mat-multi-select option. I have a search functionality and I am able to select multiple options at once. Post selection when I click generate button, I want to clear all the selections. I am able to clear the values from search bar but values are still selected if I open the multi select dropdown. How can I clear those values.
HTML Code
<mat-select [formControl]="cMultiCtrl" [multiple]="true" required (selectionChange)="selectionChange($event)">
<mat-option>
<ngx-mat-select-search placeholderLabel="Search" noEntriesFoundLabel="No Matching Value Found" [formControl]="cMultiFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let val of filteredCMulti | async" [value]="val.value">
{{val.name}}
</mat-option>
</mat-select>
// the button on click of which I want to clear everything post API call
<button mat-raised-button (click)="generate()" class="download-button">Generate
</button>
TS Code
public filteredCMulti: ReplaySubject<any[]> = new ReplaySubject<any[]>(1);
public cMultiCtrl: FormControl = new FormControl();
ngOnInit() {
this.filteredCMulti.next(this.cvalues.slice());
this.cMultiFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterBanksMulti();
});
}
ngAfterViewInit() {
this.setInitialValue();
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
private setInitialValue() {
this.filteredCMulti
.pipe(take(1), takeUntil(this._onDestroy))
.subscribe(() => {
this.singleSelect.compareWith = (a, b) => a.id === b.id;
});
}
selectionChange(event){
this.cvalue = event.value;
}
private filterBanksMulti() {
if (!this.cvalues) {
return;
}
let search = this.cMultiFilterCtrl.value;
if (!search) {
this.filteredCMulti.next(this.cvalues.slice());
return;
} else {
search = search.toLowerCase();
}
// filter the banks
this.filteredCMulti.next(
this.cvalues.filter(bank => bank.name.toLowerCase().indexOf(search) > -1)
);
}
generate(){
let msg = '';
// some logic here
else{
this.commonService.showSnackBar("Value generated")
this.filteredCMulti = new ReplaySubject; // this clears the search bar but not values, they are still selected
this.table();
}}
})
}
Provide an element ref to your <mat-select>
<mat-select #matRef [formControl]="cMultiCtrl" [multiple]="true" required (selectionChange)="selectionChange($event)">
<mat-option>
<ngx-mat-select-search placeholderLabel="Search" noEntriesFoundLabel="No Matching Value Found" [formControl]="cMultiFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let val of filteredCMulti | async" [value]="val.value">
{{val.name}}
</mat-option>
</mat-select>
And update your ts file like:
#ViewChild('matRef') matRef: MatSelect;
clear() {
this.matRef.options.forEach((data: MatOption) => data.deselect());
}
You can optimize clear() by conditionally calling deselect()
I am trying to get a value from nested array from a Form Values
[['A'], ['B']]-- from this array
['A','B'] --trying to get value like this
stackblitz example
https://stackblitz.com/edit/angular-4d5vfj-p5adyk?file=main.ts
<button (click)="addNewChipList()">Add new Chip</button><br><br>
<form [formGroup]="myForm">
<ng-container formArrayName="names"
*ngFor="let item of myForm.get('names').controls; let i = index;">
<mat-form-field class="example-chip-list" [formGroupName]="i">
<mat-chip-list #chipList >
<mat-chip *ngFor="let val of item.value.val"
[selectable]="selectable"
[removable]="removable"
(removed)="removeChip(item, val)">
{{val}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input [placeholder]="item.value.name"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="addChip($event, item)">
</mat-chip-list>
<mat-error>Atleast 1 name need to be added</mat-error>
</mat-form-field>
</ng-container>
<button (click)="save()">save</button><br><br>
</form>
component.ts
The componet file where I am trying to get form value
export class ChipListValidationExample implements OnInit {
#ViewChild('chipList') chipList: MatChipList;
public myForm: FormGroup;
// name chips
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
// data
data = {
names: [this.initName('name1'), this.initName('name2', [['A'],
['B']])]
}
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
names: this.fb.array(this.data.names, this.validateArrayNotEmpty)
});
}
ngOnInit() {
this.myForm.get('names').statusChanges.subscribe(
status => this.chipList.errorState = status === 'INVALID'
);
}
initName(name: string, val: string[] = []): FormControl {
return this.fb.control({ name, val});
}
validateArrayNotEmpty(c: FormControl) {
if (c.value && c.value.length === 0) {
return {
validateArrayNotEmpty: { valid: false }
};
}
return null;
}
addChip(event: MatChipInputEvent, ctrl: FormControl): void {
const input = event.input;
const value = event.value;
// Add name
if ((value || '').trim()) {
const control = ctrl;
control.value.val.push(value.trim());
console.log(control.value);
}
// Reset the input value
if (input) {
input.value = '';
}
}
removeChip(ctrl, val) {
const idx = ctrl.value.val.findIndex(item => item === val);
ctrl.value.val.splice(idx, 1);
}
addNewChipList() {
const items = this.myForm.get('names') as FormArray;
items.push(this.initName(`name${items.length + 1}`));
}
save(){
console.log("FormValues",this.myForm.value)
}
}
I am trying to get the form value as ['A','B']
Flattening some arrays is a trivial task with plain JS. Using Array.flat, you can do this in one line.
The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
Syntax
var newArray = arr.flat([depth]);
Parameters
depth Optional
The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
Return value
A new array with the sub-array elements concatenated into it.
//one level deep
var x = [['A'], ['B']];
console.log(x.flat());
// => ['A', 'B']
//2 levels deep
var y = [[['A', 'B'], ['C']], ['D']];
console.log(y.flat(2));
// => ['A', 'B', 'C', 'D']
I have made toggle buttons like component in Angular so I can use everywhere I need, but I am trying to solve something and I need your help.
I need only to show me the selected toggle button so the other toggle button don't show.
When I click in the selected toggle button then show me the other toggle buttons for example like expand and collapse, if I click the selected toggle button than show me the everything what is in that array.
The selected toggle button comes from another component with ngModel which tells by the component which is selected
I have tried slice but didn't work.
This is the component of toggle button.
<div id="toggle-button" fxLayout="row" fxLayoutAlign="start end">
<label [style.width]="labelWidth" [style.paddingRight]="label.length > 0 ? '10px' : '0'">
{{label}}
</label>
<div *ngFor="let option of options | slice:0:1; let first = first; let last = last" [ngClass]="{'first': first, 'last': last, 'selected': option.value === value, 'divider' : !last, 'clickable': !readonly, 'not-selectable': readonly}"
[style.width]="optionWidth" (click)="select(option.value)" fxLayout="row" fxLayoutAlign="center center">
<span (click)="options.length">{{option.text}}</span>
</div>
</div>
export class ToggleButtonComponent implements OnInit, ControlValueAccessor {
#Input() options: ToggleOption[] = []
#Input() label = ""
#Input() value: any
#Input() labelWidth = ""
#Input() optionWidth = ""
#Input() readonly = false
#Output() toggle = new EventEmitter<any>()
onChangeCallback: (selected: any) => void = () => { }
onTouchedCallback: (selected: any) => void = () => { }
constructor() {
}
ngOnInit() {
console.log(this.value)
}
writeValue(selected: any): void {
this.value = selected
}
registerOnChange(callback: (selected: any) => void): void {
this.onChangeCallback = callback
}
registerOnTouched(callback: (selected: any) => void): void {
this.onTouchedCallback = callback
}
select(selected: any) {
if (!this.readonly) {
this.value = selected
this.onChangeCallback(selected)
this.onTouchedCallback(selected)
this.toggle.emit(selected)
}
}
}
export interface ToggleOption {
text: string
value: any
}
And this is another component where I declare the toggle buttons.
readonly categoryOptions: ToggleOption[] = [
{ text: "BUS", value: 0 },
{ text: "BOS", value: 1 },
{ text: "BIS", value: 2 }
]
<app-toggle-button label="Category" labelWidth="75px" [options]="categoryOptions" [(ngModel)]="valueItem.category"></app-toggle-button>
Aim
The aim to have multiple toggle buttons with multiple options.
Solution
You need to have toggleState variable to show/hide other buttons.
A variable value to check for current selected buttons which.
You just need to tweak in your ts and html file as -
ts
Add new variable called toggleState to hold toggle state and change the state whenever select function is called.
toggleState = false;
select(selected: any) {
if (!this.readonly) {
this.value = selected
this.onChangeCallback(selected)
this.onTouchedCallback(selected)
this.toggle.emit(selected)
this.toggleState = !this.toggleState; //<-- toggle state here
}
}
html
Just check for current value and toggle state using the syntax *ngIf="option.value == value || toggleState
"
<div id="toggle-button" fxLayout="row" fxLayoutAlign="start end">
<label [style.width]="labelWidth" [style.paddingRight]="label.length > 0 ? '10px' : '0'">
{{label}}
</label>
<div *ngFor="let option of options; let first = first; let last = last" [ngClass]="{'first': first, 'last': last, 'selected': option.value === value, 'divider' : !last, 'clickable': !readonly, 'not-selectable': readonly}"
[style.width]="optionWidth" (click)="select(option.value)" fxLayout="row" fxLayoutAlign="center center"
*ngIf="option.value == value || toggleState">
<span (click)="options.length">{{option.text}}</span>
</div>
</div>
Toggle false on click anywhere else
You can use the HostListener to handle this -
constructor(private elementRef: ElementRef) {}
#HostListener('document:click', ['$event'])
public documentClick(event: MouseEvent): void {
const targetElement = event.target as HTMLElement;
// Check if the click was outside the element
if (targetElement && !this.elementRef.nativeElement.contains(targetElement)) {
this.toggleState = false; //<-- you can emit if required.
}
}
Note : Ideally this kind scenario should be handle through Directive.
I would use a Pipe, so like this?
https://stackblitz.com/edit/angular-5u5wn1