I have an Input field in my angular template-driven form. I want to configure that fields so that it can only take an 11bits number like 01001111100
<input
class="form-control"
id="bitsValue"
[placeholder]="field.templateOptions.placeholder"
pattern="[0-1]{1}"
[(ngModel)]="ABC"
>
How can I do that?
If you were to use reactive forms, create you form for example:
this.myForm = this._sb.fb.group(
{
bitsValue: this._sb.fb.control('', [Validators.required, YourValidatorClass.elevenBitsOnly])
};
export class YourValidatorClass {
static elevenBitsOnly(control: AbstractControl): ValidationErrors | null {
const value = control.value;
//... validate your value meets your 11 bit requirement
return { bitValueReq: 'You must provide value as 11 bits number' };
//... if it doesn't meet the requirement return null
return null;
}
}
Then in your html template:
<div [formGroup]="myForm">
<mat-form-field>
<input matInput type="text" formControlName="bitsValue" />
<mat-error *ngIf="myForm.hasError('bitValueReq'))"> {{ getErrorMessage(myForm.get('bitsValue')) }}</mat-error>
</mat-form-field>
</div>
....component.ts file:
getErrorMessage(control: AbstractControl): string{
const error = control.getError('bitValueReq');
if(error){
return error['bitValueReq'];
}
}
Related
I need to extract DISPLAY_NAME into a saparate array from Amenity_Listin order to work with , but its not working.
HTML
<mat-form-field *ngIf="element.IS_EDIT" appearance="outline">
<input #AmenityInput type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [(ngModel)]="element.NEW_AMENITY_NAME" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" class="" (optionSelected)='onSelect($event,i)'>
<mat-option *ngFor="let item of filteredOptions | async" [value]="item">
{{item}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
TS-
export class ViewAssignedSeatsComponent implements OnInit {
myControl = new FormControl('');
filteredOptions: Observable<string[]>;
displayName : string[];
ngOnInit(): void {
console.log(this.AmenityList);
this.displayName = this.AmenityList.map(a=>a.DISPLAY_NAME);
console.log(this.displayName , typeof(this.displayName));// here type is always objects though I need array
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.AmenityList.filter(option => option.DISPLAY_NAME.toLowerCase().includes(filterValue));
}
onSelect(event:any, index:any){
console.log("called", event.target.value);// here I'm getting undefined , also after search the new value is lost.
}
}
I need just filter on mat-select using mat-autocomplete. But I'm unable to get the display names in array to have them as options for
selection and Search. I need it in this.displayNames as array of
string type instead of object.
Attaching image for Amenity_list.
My Objective is to show a dropdown menu when i start to write # on the input field.
My Component
myControl: FormControl = new FormControl();
options = [
'One',
'Two',
'Three'
];
filteredOptions: Observable<string[]>;
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges
.pipe(
startWith('#'),
map(val => val.length >= 1 ? this.filter(val): [])
);
}
filter(val: string): string[] {
return this.options.filter(option =>
option.toLowerCase().indexOf(val.toLowerCase()) === 0);
console.log(this.options)
}
This is what i have done, but its not working.
HTML
<form class="example-form">
<mat-form-field class="example-full-width">
<input #inputField type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
Your subscription is working great.
You just forget to escape the # in filter function since you don't have an # in your option List.
Try this :
filter(val: string): string[] {
return this.options.filter(option =>
option.toLowerCase().indexOf(val.toLowerCase().replace('#', '')) === 0);
}
Just register to the change event on your input - for example:
(change)='handlerMethode($event)'
Check input for index of # and if you find do what you want.
I want Angular app to filter data from the dropdown, list of products are associated with the product number. Below is the JSON structure.
component.ts
productlistArray = [{
number: "1",
productlist: [
{ name: "product_1_0" },
{ name: "product_1_1" }
]
}, {
number: "2",
productlist: [
{ name: "product_2_0" },
{ name: "product_2_1" }
]
}];
onclickx() {
this.consoleMessages += "called this function";
}
I have an input box (to search by product). when I type product number it must list only products under product number.
component.html
<td>
<div class="form-group">
<input type="text" class="form-control" placeholder="Search By Product No" style="width:300px" [(ngModel)]="searchTerm"/>
</div>
<div class="form-group">
<mat-select (selectionChange)="onclickx($event.value)">
<mat-option *ngFor="let product of productlistArray" [value]="product.number">
{{product.productlist}}
</mat-option>
</mat-select>
</div>
</td>
pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name:"ProductFilter",
pure:true
})
export class ProductFilter implements PipeTransform {
transform(data: any[], searchTerm: string): any[] {
return data.filter(productlistArray =>(productlistArray.number == searchTerm));
}
}
Initially, when there is no searchTerm entered, it will be undefined so the filter will not return any data. Add a condition to return all data if there is no searchTerm. As mentioned by #AJT82 in the comments, this logic should also be placed in the component.
filteredProducts: any; // You should ideally create an interface instead
searchTerm: string;
filter() {
this.filteredProducts = this.searchTerm ? this.productlistArray.filter(productlistArray => productlistArray.number == this.searchTerm) : this.productlistArray;
}
Note: For formControl, use valueChanges to achieve the same
Then call this function on change of the input
<input type="text" class="form-control" [(ngModel)]="searchTerm" (change)="filter()" placeholder="Search By Product No" style="width: 300px;" />
And use filteredProducts in your template
<mat-form-field>
<mat-select placeholder="Product" (selectionChange)="onclickx($event.value)">
<mat-option *ngFor="let product of filteredProducts" [value]="product.number">
{{product.productlist}}
</mat-option>
</mat-select>
</mat-form-field>
StackBlitz
Here is my FormArray (variants):
this.productGroup = this.fb.group({
name: '',
variants: this.fb.array([
this.fb.group({
type: '',
options: ''
})
])
})
I'm using MatChips to store a string array. This array needs to be passed to options:
<div formArrayName="variants" *ngFor="let item of productGroup.controls['variants'].controls; let i = index;">
<div [formGroupName]="i">
<div class="row">
<mat-form-field class="col-12">
<input formControlName="type">
</mat-form-field>
</div>
<div class="row">
<mat-form-field class="col-12">
<mat-chip-list #chipList>
<mat-chip *ngFor="let opt of typesOptions" [selectable]="true"
[removable]="true" (removed)="removeOpt(opt)">
{{opt}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="Conjunto de opções deste Tipo"
formControlName="options"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addOpt($event)">
</mat-chip-list>
</mat-form-field>
</div>
</div>
<div class="row">
Add Variants
Remove Variants
</div>
</div>
Here is the methods:
// Dynamic Methods
addItem(): void {
this.variantsArray = this.productGroup.get('variants') as FormArray;
this.variantsArray.push(this.fb.group({
type: '',
options: ''
}));
}
removeItem(index: number) {
this.variantsArray.removeAt(index);
}
// MatChip Methods
addOpt(item: number, event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
// Add our fruit
if ((value || '').trim()) {
this.typesOptions.push(value.trim());
}
// Reset the input value
if (input) {
input.value = '';
}
}
removeOpt(opt: string): void {
const index = this.typesOptions.indexOf(opt);
if (index >= 0) {
this.typesOptions.splice(index, 1);
}
I'm successfully adding dynamic fields to my variants formArray. However MatChipList is the same for every dynamic field. I need to make MatChipList dynamic as well. Is there a way to achieve this? Like changing <mat-chip-list #chipList+i> or something like that.
EDIT: StackBlitz
I'm not sure the dom reference variable #chiplist is the issue. It looks like the matChipList is backed by the typesOptions array, but you only have one array. So every time you add a matChipList component it is still being backed by the same array as all the others. You need to have an array of typesOptions, an array of arrays. Then when you addItem, you also push a new sub array to typesOptions (and similarly remove one for removeItem).
I haven't coded this up, just a suggestion from looking at the code.
Edit - coded up a solution based on James's stackblitz.
https://stackblitz.com/edit/angular-3od6rd-jkidxf
Note I haven't looked in detail at how the delete variant holds together, ideally I'd probably like to use a key/value pair to track the variant options using the dom input element id as the key (which is in the MatChipInputEvent), instead of relying on the outer loop index.
Some code from the stackblitz:
export class ChipsOverviewExample {
productGroup: FormGroup;
variantsArray: FormArray;
typesOptionsArray: string[][] = [];
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.productGroup = this.fb.group({
name: '',
variants: this.fb.array([
this.fb.group({
type: '',
options: ''
})
]),
});
this.typesOptionsArray.push([]);
}
saveProduct(form: FormGroup) {
console.log(form);
}
// Add new item to FormArray
addItem(): void {
this.variantsArray = this.productGroup.get('variants') as FormArray;
this.variantsArray.push(this.fb.group({
type: '',
options: ''
}));
this.typesOptionsArray.push([]);
}
removeItem(index: number) {
this.variantsArray.removeAt(index);
}
addOpt(event: MatChipInputEvent, index: number): void {
const input = event.input;
const value = event.value;
// Add our fruit
if ((value || '').trim()) {
this.typesOptionsArray[index].push(value.trim());
}
// Reset the input value
if (input) {
input.value = '';
}
}
removeOpt(opt: string, index: number): void {
const optIndex = this.typesOptionsArray[index].indexOf(opt);
if (optIndex >= 0) {
this.typesOptionsArray[index].splice(optIndex, 1);
}
}
}
try to make the formGroup as a new component and input formGroup to it(not formGroupName).
<div formArrayName="variants" *ngFor="let item of productGroup.controls['variants'].controls; let i = index;">
<variant [varientGroup]="item"><varient>
<div class="row">
Add Variants
Remove Variants
</div>
</div>
varient component.html
<div [formGroup]="varientGroup">
<div class="row">
<mat-form-field class="col-12">
<input formControlName="type">
</mat-form-field>
</div>
<div class="row">
<mat-form-field class="col-12">
<mat-chip-list #chipList>
<mat-chip *ngFor="let opt of typesOptions" [selectable]="true"
[removable]="true" (removed)="removeOpt(opt)">
{{opt}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="Conjunto de opções deste Tipo"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addOpt($event)">
</mat-chip-list>
</mat-form-field>
</div>
</div>
in varient.component.ts
#Input()varientGroup: FormGroup
I'm trying to create a contact form. The form looks like this:
<form novalidate [formGroup]="contact" (ngSubmit)="send()">
<p>
<label>Name
<br>
<input type="text" class="input" value="" formControlName="name">
<span class="error">Enter your name</span>
</label>
</p>
<p>
<label>E-mail
<br>
<input type="email" class="input" value="" formControlName="email">
<span class="error">It looks like this email is invalid</span>
</label>
</p>
<p>
<label>Phone
<br>
<input type="text" class="input" value="" formControlName="telefone">
<span class="error">It looks like this phone number is invalid</span>
</label>
</p>
<p>
<label>Message
<br>
<textarea type="text" class="input" value="" formControlName="message"></textarea>
<span class="error">The message can't be empty</span>
</label>
</p>
<p class="submit">
<input type="submit" name="submit" class="bt" value="Send">
</p>
</form>
In this form only the message and the name and email or the phone number fields should be required.
I'm using a formBuilder class, so here's the TypeScript code:
this.contact = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.compose([/*Use custom validador??*/])],
phone: ['', Validators.compose([/*Use custom validador??*/]],
message: ['', Validators.required]
});
I tried using custom validators as a solution, but I could not figure out a solution. Any suggestions?
I created a custom validator directive:
import {
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '#angular/forms';
export const atLeastOne = (validator: ValidatorFn, controls:string[] = null) => (
group: FormGroup,
): ValidationErrors | null => {
if(!controls){
controls = Object.keys(group.controls)
}
const hasAtLeastOne = group && group.controls && controls
.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {
atLeastOne: true,
};
};
To use it, you just do this:
this.form = this.formBuilder.group({
name: ['', Validators.required],
email:[''],
telefone:[''],
message:['', Validators.required],
}, { validator: atLeastOne(Validators.required, ['email','telefone']) });
So email or telefone would be required here. If you leave it empty then any control with a value is fine and you can use it with any type of validator, not just Validators.required.
This is reusable in any form.
Yes, a custom validator is the way to go.
Make your form group like this:
this.contact = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required],
phone: ['', Validators.required],
message: ['', Validators.required]
}, {validator: this.customValidationFunction})
Then have the customValidationFunction check for validation. Made up validation just for example:
customValidationFunction(formGroup): any {
let nameField = formGroup.controls['name'].value; //access any of your form fields like this
return (nameField.length < 5) ? { nameLengthFive: true } : null;
}
Change each input like this (changing your p tags to divs. Substitute the control name for each and change syntax for the hidden span tag validation where appropriate):
<div [ngClass]="{'has-error':!contact.controls['name'].valid && contact.controls['name'].touched}">
<label>Name</label>
<input class="input" type="text" [formControl]="contact.controls['name']">
<span [hidden]="!contact.hasError('nameLengthFive')" class="error">Enter your name</span>
</div>
I have updated my validator snippet to support string and number types
I was inspired by Todd Skelton. This is a very simple Validator that does just what you ask for and nothing else:
/**
* Validates if at least one of the provided fields has a value.
* Fields can only be of type number or string.
* #param fields name of the form fields that should be checked
*/
export function atLeastOne(...fields: string[]) {
return (fg: FormGroup): ValidationErrors | null => {
return fields.some(fieldName => {
const field = fg.get(fieldName).value;
if (typeof field === 'number') return field && field >= 0 ? true : false;
if (typeof field === 'string') return field && field.length > 0 ? true : false;
})
? null
: ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
};
}
And here is how I use it:
ngOnInit(): void {
this.form = this.formBuilder.group(
{
field: [this.field],
anotherField: [this.anotherField],
},
{ validator: atLeastOne('field','anotherField') },
);
}
Slightly refactored Florian's answer for the lazy as it was making ts-sonar angry (cognitive-complexity rule):
const isFieldEmpty = (fieldName: string, fg: FormGroup) => {
const field = fg.get(fieldName).value;
if (typeof field === 'number') { return field && field >= 0 ? true : false; }
if (typeof field === 'string') { return field && field.length > 0 ? true : false; }
};
export function atLeastOne(...fields: string[]) {
return (fg: FormGroup): ValidationErrors | null => {
return fields.some(fieldName => isFieldEmpty(fieldName, fg))
? null
: ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
};
}