Display input box on condition in angular reactive form - javascript

I am making angular 6 application where i am using angular reactive form.
Html:
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" style="display: flex">
<select (ngModelChange)="changeAction($event)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
Ts:
form: FormGroup;
showLabel: boolean = false;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
}
addCreds() {
const creds = this.form.controls.credentials as FormArray;
creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
}
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-form-array-example-yksojj
In this given example there will be an add button, upon clicking that button you will get a select-box with values as set,wait,go and an input called name.. Upon click over add button the same row will be added and forms each object inside array.
Also you can see an if condition inside html for label,
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
The thing i am in the need was in the select box if i choose set or go then the label needs to displayed otherwise it should not be displayed for which i have written,
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
To be clear enough If the user clicks three times add button then the dropdown and name field alone needs to be displayed for three times whereas if the user selects the value from dropdown as set or go then the label input needs to be displayed to that particular row alone where the dropdown has the value set and go.. If the selection was wait then there should not be label box for the row which has dropdown value as wait.
Kindly help me to achieve the expected result..

If you add disabled:true property to formControl it will exclude fromControl from formGroup then you can enable formControl manually
creds.push(this.fb.group({
action: '',
name: '',
label: {disabled:true, value: ""}
}));
Then enable using enable method
changeAction(e,index) {
if(e === "set" || e === "go") {
this.showLabel = true;
this.form.get('credentials').at(index).get('label').enable();
} else {
this.showLabel = false;
this.form.get('credentials').at(index).get('label').disable();
}
}
Example:https://stackblitz.com/edit/angular-form-array-example-5buwyr

Please, NOT use (ngModelChange) or changeAction($event) to get the value of a control in an array -well, ngModelChange is for Template driven form, not for Reactive Form.
First change your form, create the form using a div with formArrayName="credentials", and a inner div *ngFor="let creds of form.get('credentials').controls
<!--a div with formArrayName--->
<div formArrayName="credentials" >
<!--a div *ngFor over "controls", in this div don't forget formGroupName-->
<div *ngFor="let creds of form.get('credentials').controls; let i = index"
[formGroupName]="i" style="display: flex">
<select formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}">
{{option.value}}
</option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="??????????????">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
Well, now the condition. To get the value of "action" you can use
form.get('credentials').at(i).value.action
//or, better
creds.value.action
So, your div becomes like
<div *ngIf="creds.value.action=='set' ||
creds.value.action=='go'">
<input placeholder="Label" formControlName="label">
</div>
This aproach avoid unnecesary code in your .ts.

this.showLabel
The scope of this variable is your whole component. Therefore, turning it on or off will show and hide all inputs.
You need a per-row value (creds.showLabel in your interface), or use this in your template :
*ngIf="['set','go'].includes(creds.action)"
Updated Stackblitz
By the way, this :
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
is more elegant written this way :
changeAction(e) {
this.showLabel = ['set','go'].includes(e)
}
or this way :
changeAction(e) {
this.showLabel = e in ['set','go']
}

Well, Jeremy's answer is pretty nice and enforces to use most of the native apis given by platform/language, however, here is a traditional approach to understand what is the actual flow and scope of objects, etc,etc...
Root Cause: The show hide field is made global to the scope of component, not at per formgroup level. So changing a single value, and using for all, will affect to all.
Solution:
Use Jeremy's answer for clean coding and less error prone code.
Manage an array of variable that will take care of sho/hide detail for each form group.
In the below answer, added some comments for easy understanding, and added console.log to see what exactly happening. Also, played with index i created in *ngFor and showed how you can make use of these things in future.
import { Component } from '#angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent {
form: FormGroup ;
showLabel = [];
creds : FormArray;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
this.creds = this.form.controls.credentials as FormArray;
}
addCreds() {
this.creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
// every time adding new foem grp, adding lable show/hide data also.
this.showLabel.push(false);
}
changeAction(e, i) {
//every time input clikced, managing show/hide for that particular data also
console.log("iii", i, e.target.value);
if( e.target.value == "set" || e.target.value == "go") {
this.showLabel[i] = true;
} else {
this.showLabel[i] = false;
}
}
}
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" >
<select (change)="changeAction($event, i)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel[i]">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
See live stackblitz working code
Note: Traditional is meant here.. as we every time do i.e. handle our problem on our own and create new problem on us to solve. It's not a tradition. :P

Related

Angular pass checkbox value to custom pipe

I'm looping through the values and displaying checkboxes which I'm showing, now in addition there is an search box filter on the top of list of checkboxes basically to filter out checkbox list.
HTML
<input [(ngModel)]="searchText" class="lmn-input lmn-select-input-inner mt-input" placeholder="Search by key word" id="mngd-seg"/>
<div formArrayName="checkBoxValueList"
*ngFor="let dataConceptList of scopeSetDetails.controls['checkBoxValueList'].controls | searchFilter : searchText; let i = index">
<label class="lmn-selection-control">
<span class="lmn-checkbox">
<input class="lmn-control-input" type="checkbox" [formControlName]="i">
<i class="lmn-control-icon lmnicon lmnicon-check"></i>
</span>
<span>Data Concept </span>
<span style="padding-left: 150px">{{ checkBoxValueList[i].scopeSets }}</span>
</label>
</div>
Therefore I've created an custom pipe 'searchFilter' and using this pipe above in *ngFor
PIPE
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'searchFilter',
})
export class FilterPipe implements PipeTransform {
transform(items: any, searchText: string): any {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter((it) => {
return it.name.toLowerCase().includes(searchText);
});
}
}
In TS I'm initializing the checkboxes to false (with checkboxes, the Angular form control value is bound to the "checked" state (not the "value" attribute), so it should be either true or false) since it needs to be unchecked initially.
TS
const formControls = this.checkBoxValueList.map(
(control) => new FormControl(false)
);
const selectAllControl = new FormControl(false);
this.scopeSetDetails = this.fb.group({
search_text: this.fb.control(''),
checkBoxValueList: new FormArray(formControls, this.minSelectedScopeSet(1)),
selectAll: selectAllControl,
});
Now issue is in filter pipe I'm getting items as an array of FormControl whose value property is 'false'. I need the checkbox text somehow and initially all checkboxes should be unchecked. how can I achieve this?

How to get the values of default checked checkbox

I was working on a project of multiple checkbox. There, I want the checkboxes to be checked from the start and the value to be in the form(I am using reactive form). The user can unselect the boxes according to their wish and the data will be stored accordingly. This is the stackblitz of the project. There I was able to make the checkbox checked from the beginning, but when I hit the submit button there is no data when I console-logged. I think this is some binding issue,but I couldn't figure out what is exactly the problem.
Can someone help?
Thanks in advance.
This is code:
<form [formGroup]="form" (ngSubmit)="submit()">
<div class="form-group">
<label for="website">Website:</label>
<div *ngFor="let web of websiteList">
<label>
<input
type="checkbox"
[value]="web.id"
(change)="onCheckboxChange($event)"
[checked]="web.isSelected"
/>
{{ web.name }}
</label>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
form: FormGroup;
websiteList: any = [
{ id: 1, name: 'HDTuto.com', isSelected: true },
{ id: 2, name: 'HDTuto.com', isSelected: true },
{ id: 3, name: 'NiceSnippets.com', isSelected: true },
];
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
website: this.fb.array([], [Validators.required]),
});
}
ngOnInit() {}
onCheckboxChange(e: any) {
const website: FormArray = this.form.get('website') as FormArray;
console.log('checking ->', e);
if (e.target.checked) {
website.push(new FormControl(e.target.value));
console.log('website ->', website);
} else {
//console.log(e);
const index = website.controls.findIndex((x) => {
console.log('x.value ->', x.value);
console.log('target.value ->', e.target.value);
x.value === e.target.value;
});
website.removeAt(index);
}
}
submit() {
console.log(this.form.value);
}
https://stackblitz.com/edit/angular-ivy-qar4ph?file=src/app/app.component.ts
Pay attention to changes in template:
Added formArrayName attribute to checkboxes wrapper and formControlName attribute to input element.
Removed change and checked attributes
In the component ts file:
Added initial form array values
Added mapping to submit method
Removed onCheckboxChange method

Show hide text box when specific option is selected from dropdown inside dynamic form Angular

I have a form where a user could add one/more div of Address on click of add button.
I want if user select options=5 from the dropdown, want to show and hide textbox in that particular address Div.
Component Code
get contactFormGroup() {
return this.form.get('Array') as FormArray;
}
ngOnInit() {
this.form= this.fb.group({
Array: this.fb.array([])
});
}
createContact(): FormGroup {
return this.fb.group({
ABC: [null, Validators.compose([Validators.required])],
Test: [null, Validators.compose([Validators.required])]
});
}
addContact() {
this.Group.push(this.createContact());
}
showValue(event) {
const selectedValue = event;
if (selectedValue === '5') {
this.showValuetxtbox = true;
} else {
this.showValuetxtbox = false;
}
}
As you are looping to add the divs, you could use a template reference variable on the drop down. e.g #select then refer to that in the *ngIf:
<form [formGroup]="addExpressionform">
<div formArrayName="expressionArray">
<div *ngFor="let item of contactFormGroup.controls; let i = index;" [formGroupName]="i">
<carbon-dropdown #select
(optionSelected)="showValue($event)"
[formControlN]="'UOM'"
[options]="listOptions" [formGroup]="item"
name="UOM"
>
</carbon-dropdown>
<carbon-text-input *ngIf="select.value == 5"
[formControlN]="'Value'"
[formGroup]="item"
name="Value"
>
</carbon-text-input>
<carbon-button type="primary" (click)="submit()" id="save-parameter">Save</carbon-button>
</div>
</div>
</form>
Simplified StackBlitz demo.
Take a look at this Stackblitz, it's referred to in the Angular docs and could serve as boilerplate to what you are trying to achieve.
You should isolate every possible type of question by creating a different class for each one, so you can shape the data and then use ngSwitch to dynamically create the HTML accordingly.
Question base class:
export class QuestionBase<T> {
controlType: string;
value: T;
key: string;
label: string;
// etc
constructor(options) {
// constructor logic
}
}
Some special class that inherents from base class
import { QuestionBase } from './question-base';
export class SpecialQuestion extends QuestionBase<string> {
controlType = 'specialQuestion';
type: string;
// special Question
specialValue: string;
constructor(options) {
super(options);
this.type = options['type'] || '';
}
}
Then, a question component:
<div [formGroup]="form">
<label>{{question.label}}</label>
<div [ngSwitch]="question.controlType">
// controls logic
<input *ngSwitchCase="'textbox'" >
<select *ngSwitchCase="'specialQuestion'"></select>
</div>
</div>
Then you throw this into a container component where you loop through the entire questions array.
This way your code will be future proof and reusable as you add/change functionality to your forms down the road. You won't have to create spaghetti to meet edge case requirements like an extra input field.

I need to send values from my select options

I am using angular I want to send information about what element I selected in the select option. Specifically I want to send the data-value to a variable in my product-form.component.ts.
I tried using ngModel but I keep getting errors saying that it doesn't recognize (click)=selectCategory1(category) use a function I am using template forms for my forms that could be the reason. You can see my code live at :
https://stackblitz.com/github/RashellSmith/Dashboard-FrontEnd
Product Form component html
<div class="form-group">
<label for="productCategory">Product Category</label>
<select [(ngModel)]="model.category" (click)=selectCategory1(category) name="category" class="form-control" id="productCategory" (click)="value()">
<option *ngFor="let category of categories" (click)=selectCategory1(category) data-value="{{category.category}}" id={{category.categoryName}} >{{category.categoryName}}</option>
</select>
</div>
Product Form component ts
export class ProductFormComponent implements OnInit {
suppliers: Supplier[];
categories: Category[];
value: number;
model = new Newproduct("name",new Category( this.value,"name"),66,33,true,new Supplier(null,null),76);
selectCategory1(Category){
console.log(Category);
}
submitted = false;
get diagnostic() { return JSON.stringify(this.model); }
onSubmit() { this.submitted = true; }
constructor(private supplierService: SupplierService,private categoryService: CategoryService) { }
ngOnInit() {
this.supplierService.getAll().subscribe(data => {
this.suppliers = data;
});
this.categoryService.getAll().subscribe(data => {
this.categories = data;
});
}
}
You are definitely trying to over-complicate things. You need to bind a simple (change) method to your select list which would be be triggered on value change. You can either pass the value to this function using template reference variable as
<Select #select (change)="SelectChanged(select.value)">
Or you can bind [(ngModel)] directive and access that directly in your component class on (change)
A sample template code:
<select [(ngModel)]="selectedOption" (change)="GetSelectedValue(selectedOption)">
<option *ngFor="let option of options" [ngValue]="option">{{option}}</option>
</select>
And component class would look like
GetSelectedValue(val) {
//do something with val
}
Stackblitz at: https://stackblitz.com/edit/angular-r4d7ul

Angular Material Autocomplete - How to allow user to add item not in suggested list?

I'm trying to implement the autocomplete component from Angular Material:
https://material.angular.io/components/autocomplete/overview
It works well for letting the user select a particular item from the suggested list but I also want to allow the user to add items not in the list.
So lets say the suggested list has the following items:
Cats
Birds
Dogs
And the user starts typing "Do" and the autocomplete shows "Dogs" as the suggested option (because I'm also filtering the list based on what they type). But then the user continues typing "Dolls" and now nothing is displayed in the autocomplete suggestions. Then the user hits enter and it gets added to the list.
Current behavior is that if what the user typed doesn't exist in the list then they are unable to add the item.
If you add an enter key listener to the input field, you can process the entered value and add it to the options if it doesn't exist. You can also dynamically add whatever the user enters to the list of filtered options as an "add new item" option, or add an "add" icon to the field (e.g. as a matSuffix). Or you can do all three:
Stackblitz
HTML
<form class="example-form">
<mat-form-field class="example-full-width">
<input matInput placeholder="Item" aria-label="Item" [matAutocomplete]="auto" [formControl]="itemCtrl" (keyup.enter)="addOption()">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="optionSelected($event.option)">
<mat-option *ngFor="let item of filteredItems | async" [value]="item">
<span>{{ item }}</span>
</mat-option>
</mat-autocomplete>
<button *ngIf="showAddButton && itemCtrl.value" matSuffix mat-button mat-icon-button (click)="addOption()"><mat-icon matTooltip='Add "{{itemCtrl.value}}"'>add</mat-icon></button>
</mat-form-field>
</form>
TS
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
import { Observable } from 'rxjs/Observable';
import { startWith } from 'rxjs/operators/startWith';
import { map } from 'rxjs/operators/map';
/**
* #title Autocomplete with add new item option
*/
#Component({
selector: 'autocomplete-overview-example',
templateUrl: 'autocomplete-overview-example.html',
styleUrls: ['autocomplete-overview-example.css']
})
export class AutocompleteOverviewExample {
itemCtrl: FormControl;
filteredItems: Observable<any[]>;
showAddButton: boolean = false;
prompt = 'Press <enter> to add "';
items: string[] = [
'Cats',
'Birds',
'Dogs'
];
constructor() {
this.itemCtrl = new FormControl();
this.filteredItems = this.itemCtrl.valueChanges
.pipe(
startWith(''),
map(item => item ? this.filterItems(item) : this.items.slice())
);
}
filterItems(name: string) {
let results = this.items.filter(item =>
item.toLowerCase().indexOf(name.toLowerCase()) === 0);
this.showAddButton = results.length === 0;
if (this.showAddButton) {
results = [this.prompt + name + '"'];
}
return results;
}
optionSelected(option) {
if (option.value.indexOf(this.prompt) === 0) {
this.addOption();
}
}
addOption() {
let option = this.removePromptFromOption(this.itemCtrl.value);
if (!this.items.some(entry => entry === option)) {
const index = this.items.push(option) - 1;
this.itemCtrl.setValue(this.items[index]);
}
}
removePromptFromOption(option) {
if (option.startsWith(this.prompt)) {
option = option.substring(this.prompt.length, option.length -1);
}
return option;
}
}
It's weird that the user can add an item in the suggested list. The list is suggested to the user by someone who knows what to suggest. But anyway...
The user can type anything in the field and ignore the suggestions. By ignoring the suggested Dogs and typing Dolls, user can press an "Add" button which will add whatever is typed in (Dolls) to the options array.
For example, you can do it by listening to the submit event on the form:
(ngSubmit)="options.push(myControl.value); myControl.reset()"
Here's the complete demo as well.

Categories

Resources