Create common input field in Angular - html

I have created common input field which can be usable across app and it will work with or without reactive form.
For e.g.: I have created common input angular component but it works with reactive form only as it has formGroup and formControlName.
input.html:
<div class="form-control" *ngIf="isRequired" [formGroup]="parentForm">
<label class="label">
{{labelText}}
</label>
<input [type]="inputType" class="input" [control]="control" [formControlName]="formControlNameText" [placeholder]="placeholderText">
</div>
input.ts:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
})
export class InputComponent implements OnInit {
#Input() parentForm: any;
#Input() labelText: string;
#Input() inputType: string = "text";
#Input() formControlNameText: string;
#Input() placeholderText: string;
#Input() isRequired: boolean = false;
#Input() requiredMsg: string;
#Input() maxlengthMsg: string;
#Input() control: any;
#Input() type: string;
constructor() { }
ngOnInit() { }
}
Call from one of my form Page:
<app-input parentForm="surveyResponseForm" labelText="test" inputType="text" placeholderText="placeholder"
formControlNameText="authorName" control="" isRequired=true id="authorName">
</app-input>
How do I use this common input if I want to use this without form?
I mean how do I use this selector: app-input in a component which doesn't have any form.

You could add an #Input() property to your component, for example useInForm: boolean, and check its value in your template. If useInForm is true, you would use the [formGroup] and formControlName properties, otherwise you would use a regular element without those properties example in the withoutFormTemplate.
<div class="form-control" *ngIf="isRequired && useInForm; else withoutFormTemplate" [formGroup]="parentForm">
<label class="label">
{{labelText}}
</label>
<input [type]="inputType" class="input" [control]="control" [formControlName]="formControlNameText" [placeholder]="placeholderText">
</div>
<ng-template #withoutFormTemplate>
<input [(ngModel)]="control" [type]="inputType" class="input" [placeholder]="placeholderText">
<ng-template>

I solve more or less same problem by creating a FormGroup. It's not easiest solution, but that working on multiple scenarios.
If not, maybe that give you some clues how to solve your problem...
Form component ts
// Reactive Forms
form: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
// Form structure and validators
this.form = this.formBuilder.group({
'user' : this.formBuilder.group({
'username' : ['', Validators.required],
'email' : ['', [Validators.required, Validators.email]]
}),
'identity' : this.formBuilder.group({
'firstname' : ['', Validators.required],
'lastname' : ['', Validators.required],
'address' : this.formBuilder.group({
'street' : ['', Validators.required],
'city' : ['', Validators.required],
})
})
});
}
onSubmit() {
// Get object with same structure as form but only with values
console.log(this.form.value);
alert('Form is ' + (this.form.invalid ? 'invalid' : 'valid'));
}
Form component html
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<form-text [formGroupParent]="form.get(['user'])"
[formGroupControlName]="'username'">
</form-text>
<form-text [formGroupParent]="form.get(['user'])"
[formGroupControlName]="'email'">
</form-text>
<hr>
<form-text [formGroupParent]="form.get(['identity'])"
[formGroupControlName]="'firstname'">
</form-text>
<form-text [formGroupParent]="form.get(['identity'])"
[formGroupControlName]="'lastname'">
</form-text>
<hr>
<form-text [formGroupParent]="form.get(['identity','address'])"
[formGroupControlName]="'street'">
</form-text>
<form-text [formGroupParent]="form.get(['identity','address'])"
[formGroupControlName]="'city'">
</form-text>
<button type="submit">Submit</button>
</form>
Custom input component ts (form-text)
// Needed to bind formControlName
#Input() formGroupParent: FormGroup;
#Input() formGroupControlName: string;
// FormControl store validators
control: FormControl;
ngOnInit() {
// Fetch Form control (validator) from FormGroup parent
this.control = <FormControl>this.formGroupParent.get(this.formGroupControlName);
}
Custom input component html (form-text)
<ng-container [formGroup]="formGroupParent">
<label>{{formGroupControlName}}</label>
<input type="text" formControlName="{{formGroupControlName}}">
</ng-container

Related

How to put Variable in Statement [Angular, TypeScript]

This is in Angular,
I want to use variable to change my conditional statement so I don't need to modify every variable and type again
This is a code in input tag HTML
.
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
.
public form: FormGroup;
private formBuilder: FormBuilder
.
get f() { return this.form.controls; }
.
this.form = this.formBuilder.group({
id: ['00000000-0000-0000-0000-000000000000'],
name: ['', Validators.required],
email: ['', [Validators.required, Validators.pattern("^[a-z0-9._%+-]+#[a-z0-9.-]+\\.[a-z]{2,4}$")]],
oraganize: ['', Validators.required],
});
.
<input
...
[ngClass]="{'is-invalid': f.name.invalid && (f.name.dirty || f.name.touched)}"
>
So my code will change every time when there is a new input such as name, email, oraganize, etc
I want to build a function that I can pass some string and make it super nice not to modify all the time with the long line.
ex:
public checkCondition(attribute: string): boolean{
return (`this.f.${attribute}.invalid && (this.f.${attribute}.dirty || this.f.${attribute}.touched)`);
}
so I can use as...
<input
...
[ngClass]="{'is-invalid': checkCondition("name")}"
>
Please, So can I do it this way or not? or does it just works with only the string, not the condition?
it looks like you where very close. this should work.
public checkCondition(key: string): boolean{
return this.f[key].invalid && (this.f[key].dirty || this.f[key].touched);
}
or you can further simplify it
public checkCondition(key: string): boolean{
return {
'is-invalid': this.f[key].invalid && (this.f[key].dirty || this.f[key].touched)
};
}
and you html
<input [ngClass]="checkCondition('name')" />
Here is what it would look like all together
import { Component } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
form = this.formBuilder.group({
id: ['00000000-0000-0000-0000-000000000000'],
name: ['', Validators.required],
email: ['', [Validators.required, Validators.pattern("^[a-z0-9._%+-]+#[a-z0-9.-]+\\.[a-z]{2,4}$")]],
oraganize: ['', Validators.required],
});
constructor(private formBuilder: FormBuilder) {}
checkCondition(key: string) {
const control = this.form.get(key);
if (!control) {
return;
}
return {
'is-invalid': control.invalid && (control.dirty || control.touched)
};
}
}
<form [formGroup]="form">
<input formControlName="id" [ngClass]="checkCondition('id')" />
<input formControlName="name" [ngClass]="checkCondition('name')" />
<input formControlName="email" [ngClass]="checkCondition('email')" />
<input formControlName="oraganize" [ngClass]="checkCondition('oraganize')" />
</form>
Here is a working stackBlitz
https://stackblitz.com/edit/angular-ivy-cvwmis?file=src%2Fapp%2Fapp.component.html

Dynamic form using *ngFor and submitting values from it

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

Angular Radio Buttons form with unknown number of elements

I am new to Angular and I have an issue with the radio buttons.
I have an array of strings which I want to use to create a form with radio buttons. I do not know the length or the content of the array - the values are taken from an external service.
How can I do this using a form builder in my Angular Component and in html file? Can it be something like this?
question-dto.ts
export class QuestionDto {
questionText: string;
questionOptions: string[];
}
quiz.component.ts
question: QuestionDto = new QuestionDto();
questionOptionsForm: any;
constructor(private quizService: QuizService,
private formBuilder: FormBuilder) { }
ngOnInit() {
this.initForm();
}
initForm() {
this.questionOptionsForm = this.formBuilder.group({
//INIT RADIO BUTTONS HERE
})
}
quiz.component.html
<div>
<p>
Answers
</p>
<form *ngIf="questionOptionsForm">
<div *ngFor="let option of question.questionOptions">
<label>
<input type="radio" class="form-control">
{{option}}
</label>
</div>
</form>
</div>
You should try FormArray https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/
code would look like:
initForm() {
this.questionOptionsForm = this.formBuilder.group({
options: this.formBuilder.array([this.createOption()])
});
}
createOption():FormGroup {
return this.formBuilder.group({
name: '',
description: ''
});
}

How to save text from input to variable in angular?

I'm trying to take text from one of my component.html files and save the input to a variable in a method in one of my .ts files. Can you help?
home.component.html file:
Username: <input ng-model="username"><br>
Password: <input ng-model="pass">
<button (click)="login()">Login</button>
home.component.ts file:
export class HomeComponent implements OnInit {
login()
{
const usr= {{username}};
const password={{pass}};
print("user is"+usr " Password is"+password);
}
}
If you are using angular it should be
ngModel instead of ng-model
Username: <input [(ngModel)]="username"><br>
Password: <input [(ngModel)]="pass">
and in component.ts
export class HomeComponent implements OnInit {
username : string;
password : string;
login()
{
const usr= this.username;
const password= this.password;
console.log("user is"+usr " Password is"+password);
}
}
you need to change ng-model to ngModel
ng-model :- angularjs
ngModel :- Angular 2 or greater
Html
Username: <input [(ngModel)]="username"><br>
Password: <input [(ngModel)]="pass">
<button (click)="login()">Login</button>
Component
export class HomeComponent implements OnInit {
public username: String;
public pass: String;
public function login() {
console.log('User Name: ' + this.username);
console.log('Password: ' + this.pass);
}
}

Angular 2 dialog popup with text field input

I have a simple popup that shows a message with OK button to release it, what I want is to add to this popup a text field that the user will need to type something in it and click OK to submit his answer.
currently it looks like this:
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<button md-button (click)="dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
constructor( public dialog: MdDialogRef<DialogComponent>) { }
}
and im using it like:
public showDialog(message: MessageBoxMessage) {
if (typeof message !== 'undefined') {
let dialogRef: MdDialogRef<DialogComponent> = this._dialog.open(DialogComponent);
dialogRef.componentInstance.title = message.title;
dialogRef.componentInstance.message = message.content;
}
}
how can I change it to have a popup with text-field and ok button tht pass me the value of the text-field?
You can create EventEmitter in your dialog:
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<mat-form-field class="example-full-width">
<input matInput placeholder="Favorite food" #input>
</mat-form-field>
<button mat-button (click)="onOk.emit(input.value); dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
onOk = new EventEmitter();
constructor( public dialog: MatDialogRef<ErrorDialogComponent>) { }
}
then subscribe to it in parent component
dialogRef.componentInstance.onOk.subscribe(result => {
this.resultFromDialog = result;
})
Plunker Example
Another way is passing value to MatDialog.close method
(click)="dialog.close(input.value)"
....
dialogRef.afterClosed().subscribe(result => {
this.resultFromDialog = result;
});
Plunker Example
You can bind the answer to a model like this :
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<input type="text" name="data" [(ngModel)]="data">
<button md-button (click)="dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
data: string;
constructor( public dialog: MdDialogRef<ErrorDialogComponent>) { }
}
And then bind the (click) to a function that sends your data.