Display mat-label on custom mat-form-field component, angular - html

I'm creating a custom input component to be used in my angular material forms, but assigning a mat-label in the form field where the component is used has no effect.
My component HTML is as follows:
<form *ngIf="val">
<table>
<td>
+
</td>
<td>
<input matInput
id="country"
type="text"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="val.country"
class="input"
placeholder="27"
maxlength="2"
(keyup)="phoneFieldFocusHandler($event)"
required>
</td>
<td>
<input matInput
id="area"
type="text"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="val.area"
class="input"
placeholder="949"
maxlength="3"
(keyup)="phoneFieldFocusHandler($event)"
required>
</td>
<td>
<input matInput
id="prefix"
type="text"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="val.prefix"
class="input"
placeholder="555"
maxlength="3"
(keyup)="phoneFieldFocusHandler($event)"
required>
</td>
<td>
<input matInput
id="line"
type="text"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="val.line"
class="input"
placeholder="5555"
maxlength="4"
(keyup)="phoneFieldFocusHandler($event)"
required>
</td>
</table>
</form>
And the corresponding typescript:
import { coerceBooleanProperty } from '#angular/cdk/coercion';
import { Component, forwardRef, HostBinding, Input, OnDestroy, OnInit } from '#angular/core';
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '#angular/forms';
import { MatFormFieldControl } from '#angular/material/form-field';
import { Subject } from 'rxjs';
import { PhoneNumber } from '../phone-number.model';
#Component({
selector: 'app-phone-input',
templateUrl: './phone-input.component.html',
styleUrls: ['./phone-input.component.scss'],
providers: [
{ provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
},
{
provide: MatFormFieldControl,
useExisting: PhoneInputComponent
}
]
})
export class PhoneInputComponent implements OnInit, OnDestroy, ControlValueAccessor, MatFormFieldControl<any> {
#HostBinding('attr.id')
externalId = '';
val: PhoneNumber = new PhoneNumber();
stateChanges = new Subject<void>();
private _ID = '';
/*tslint:disable-next-line*/
_placeholder: string;
/*tslint:disable-next-line*/
_required: boolean;
/*tslint:disable-next-line*/
_disabled: boolean;
ngControl: NgControl;
focused: boolean;
empty: boolean;
shouldLabelFloat: boolean;
errorState: boolean;
userAriaDescribedBy?: string;
#Input()
set id(value: string) {
this._ID = value;
this.externalId = null;
}
get id(): string {
return this._ID;
}
#Input()
get placeholder(): string {
return this._placeholder;
}
set placeholder(plh) {
this._placeholder = plh;
this.stateChanges.next();
}
#Input()
get required(): boolean {
return this._required;
}
set required(req) {
this._required = coerceBooleanProperty(req);
this.stateChanges.next();
}
#Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(dis) {
this._disabled = coerceBooleanProperty(dis);
this.stateChanges.next();
}
setDescribedByIds(ids: string[]): void {
this.userAriaDescribedBy = ids.join(' ');
}
onContainerClick(event: MouseEvent): void {}
writeValue(value: any): void {
this.value = value;
}
// upon UI element value changes, this method gets triggered
registerOnChange(fn: any): void {
this.onChange = fn;
}
// upon touching the element, this method gets triggered
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
onChange: any = () => {};
onTouch: any = () => {};
set value(val: PhoneNumber) {
if (val !== undefined && this.val !== val) {
this.val = val;
this.onChange(val);
this.onTouch(val);
}
}
constructor() { }
ngOnInit(): void {
}
ngOnDestroy(): void {
this.stateChanges.complete();
}
// Internally handle moving focus from one input to the next
phoneFieldFocusHandler(e: KeyboardEvent): void {
const target = e.target as any as HTMLInputElement;
if (target.value.length >= target.maxLength) {
const parent = target.parentElement.parentElement;
const children = (Array.prototype.slice.call(parent.children) as Array<HTMLElement>).map(el => el.children[0] as HTMLInputElement);
const curr = children.indexOf(target);
if (!(curr >= children.length - 1)) {
children[curr + 1].focus();
}
}
}
}
It is used in the form as:
<mat-form-field>
<mat-label>Phone Number</mat-label>
<app-phone-input formControlName="phone"></app-phone-input>
</mat-form-field>
But the label is not displayed.
Do I need to define a space in my custom component where the label will be shown?

Related

Angular: json to formBuilder to json

From my server I am receiving a json that contains questions and different options:
[
{"description":"what is a color","questionID":"1","options":[{"response":"blue","optionID":"1"},{"response":"red","optionID":"2"},{"response":"football","optionID":"3"}]},
{"description":"what is a sport","questionID":"2","options":[{"response":"working","optionID":"4"},{"response":"playing","optionID":"5"},{"response":"dad","optionID":"6"},{"response":"chess","optionID":"7"}]}
]
With the formbuilder I created a form for this:
If I press submit I would like to send this json to my server:
{
"answers": [{"questionID":"1", "selectedoptionIDS":[{"selectedOptionID":"2"}]},
{"questionID":"2", "selectedoptionIDS":[{"selectedOptionID":"1"},{"selectedOptionID":"3"}]}
],
"email": "test#test.com"
}
I know how I can build my form with the formbuilder but when I press submit I am having troubles with responding the right JSON. Certainly because I can not work with this checkboxes. Can somebody help me with this?
Html page
<form [formGroup]="examForm" (ngSubmit)="onSubmit(examForm.value)">
<div formArrayName="answers">
<div *ngFor="let question of questions; let i=index">
<label>{{i+1}}) {{question.description}}</label>
<br />
<div *ngFor="let response of question.options">
<input type="checkbox" value="response.optionID" />
{{response.response}}
</div>
</div>
</div>
<label>Email:</label>
<input class="form-control" id="email" type="text" formControlName="email">
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</form>
TS Page
import { Component, OnInit } from '#angular/core';
import { ExamSimulatorService } from '../services/exam-simulator.service';
import { ActivatedRoute } from '#angular/router';
import { FormBuilder, FormArray } from '#angular/forms';
#Component({
selector: 'app-exam',
templateUrl: './exam.component.html'
})
export class ExamComponent implements OnInit {
software;
questions;
examForm;
constructor(
private examSimulatorService: ExamSimulatorService,
private formBuilder: FormBuilder
) {
this.examForm = this.formBuilder.group({
email: "",
answers: this.formBuilder.array([
this.initAnswer()])
})
}
buildForm() {
for (var i = 0; i < this.questions.length() + 1; i++) {
this.addAnswer();
}
}
initAnswer() {
return this.formBuilder.group({
questionID: "",
selectedOptionIDs: this.formBuilder.array([
this.initOptions()
])
})
}
initOptions() {
return this.formBuilder.group({
selectedOptionID: ""
})
}
addAnswer() {
const answers = <FormArray>this.examForm["controls"]["answers"];
answers.push(this.initAnswer())
console.log(this.examForm);
}
addOption(i) {
const options = <FormArray>this.examForm["controls"]["answers"]["controls"][i]["controls"]["selectedOptionIDs"]
options.push(this.initOptions())
}
ngOnInit() {
this.activatedRoute.paramMap
.subscribe(params => {
this.software = params['params']['software'];
this.examSimulatorService.getExam(this.software).subscribe(response =>
this.questions = response["questions"]["questionList"]);
})
setTimeout(() => this.buildForm(), 200)
}
onSubmit(values) {
//this.examSimulatorService.addQuestion(values).subscribe(
// (responses) => {
// console.log(responses);
// });
//this.options.clear();
console.log(values);
}
}
Instead of using your own model you can use full help from the Reactive form. The final model is not exactly you required but you can make workaround from it. You can see the working example at here https://stackblitz.com/edit/angular-1gtfmf
Component
export class ExamComponent implements OnInit {
#Input() name: string;
questionsList;
examForm: FormGroup;
dataModel: any; //active model
constructor(
private examSimulatorService: QuestionService,
private formBuilder: FormBuilder
) { }
get question(): FormGroup {
return this.formBuilder.group(
{
questionID: "",
description: "",
options: this.formBuilder.array([])
}
);
}
get option(): FormGroup {
return this.formBuilder.group({
optionID: "",
response: "",
selected: false
});
}
ngOnInit() {
this.dataModel = Object.create(null);
this.examForm = this.formBuilder.group({
email: ['', [Validators.required]],
questions: this.formBuilder.array([])
});
this.examSimulatorService.getAllQuestion().subscribe(response => {
this.questionsList = response.data;
this.loadForm(this.questionsList);
console.log(this.questionsList);
});
this.examForm.valueChanges.subscribe(data => {
this.dataModel = data;
});
}
loadForm(data) {
for (let ques = 0; ques < data.length; ques++) {
const questionsFormArray = this.examForm.get("questions") as FormArray;
questionsFormArray.push(this.question);
for (let opt = 0; opt < data[ques].options.length; opt++) {
const optionsFormsArray = questionsFormArray.at(ques).get("options") as FormArray;
optionsFormsArray.push(this.option);
}
}
this.examForm.controls.questions.patchValue(data);
}
showSavedValue() {
return this.dataModel;
}
showValue() {
return this.examForm.getRawValue();
}
onSubmit(values) {
console.log(values);
}
}
Html
<form [formGroup]="examForm" (ngSubmit)="onSubmit(examForm.value)">
<div>
<label>Email:</label>
<input class="form-control" id="email" type="text" formControlName="email">
</div>
<div formArrayName="questions">
<div *ngFor="let question of examForm.get('questions').controls;let questionIndex=index" [formGroupName]="questionIndex">
<label>{{questionIndex+1}} </label> {{examForm.value.questions[questionIndex].description}}
<div formArrayName="options">
<div *ngFor="let option of question.get('options').controls; let optionIndex=index" [formGroupName]="optionIndex">
<input type="checkbox" formControlName="selected" value="" /> {{examForm.value.questions[questionIndex].options[optionIndex].response}}
</div>
</div>
</div>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
</form>
<pre> {{showSavedValue() | json }}
<pre>{{showValue() | json}}</pre>

How can I populate user info into edit form angular

I have an app that you can import student firstname and lastname data into and it writes to mongodb. I can create new students, and I can view the list of students in the db, but when updating, I can't seem to get the student data to populate into the firstname and lastname input fields.
In my list-students-component.ts, i can select update and it will forward to (the new-student-component) to edit students based on the student ID.
I can't seem to figure out to get this data to populate
<div>
<ul *ngFor ="let student of students let i = index">
<li> {{student._id}}: {{student.firstName}} {{student.lastName}} (index {{i}})
<button mat-button color="warn" (click)="onDelete(student._id)">DELETE</button>
<button mat-button color="accent" [routerLink]="['/editStudent', student._id]">UPDATE</button>
</li>
</ul>
</div>
<div>
<h3>{{mode}} Student Form</h3>
<form (ngSubmit)="onSubmit()" #myForm="ngForm">
<mat-form-field >
<input matInput placeholder="First Name" id="firstname" [(ngModel)]=" firstName" name="firstname" >
</mat-form-field>
<mat-form-field >
<input matInput placeholder="Last Name" id="lastname" [(ngModel)]=" lastName" name="lastname" >
</mat-form-field>
<p>
<button type="submit" mat-raised-button color="primary">Submit</button>
</p>
</form>
</div>
import { Component, OnInit, Input } from '#angular/core';
import { StudentService } from '../student.service';
import { Router } from '#angular/router';
import { ActivatedRoute, ParamMap } from '#angular/router';
import { FormGroup, FormBuilder, FormControl } from '#angular/forms';
#Component({
selector: 'app-new-student-form',
templateUrl: './new-student-form.component.html',
styleUrls: ['./new-student-form.component.css']
})
export class NewStudentFormComponent implements OnInit {
#Input() firstName: string;
#Input() lastName: string;
private mode = 'Add New'; //default mode
private id: string; //student ID
public ngForm = new FormGroup({
firstName: new FormControl('firstName'),
lastName: new FormControl('lastName')
})
constructor(private _myService: StudentService, private router: Router,
public route: ActivatedRoute) { }
onSubmit() {
console.log("You submitted: " + this.firstName + " " + this.lastName);
this._myService.addStudents(this.firstName, this.lastName);
this.router.navigate(['/listStudents'])
.then(() => {
location.reload();
});
}
ngOnInit() {
this.route.paramMap.subscribe((paramMap: ParamMap) => {
if (paramMap.has('_id')) {
this.mode = 'edit'; /*request had a parameter _id */
this.id = paramMap.get('_id');
}
else {
this.mode = 'Add New';
this.id = null;
}
if (this.mode == 'Add New')
this._myService.addStudents(this.firstName, this.lastName);
if (this.mode == 'edit')
this.ngForm.value;
this._myService.updateStudent(this.id, this.firstName, this.lastName);
});
}
}

How to make an angular component reusable? (Angular 2+)

I made an input box for an IP Address with Port. This input box is made of 5 inputs box within a div. This is a basic example.
<div class="form-inline" style="display: inline-block">
<input required name="block1" class="form-control" #block1="ngModel" type="text" [(ngModel)]="ipBlock1">.
<input required name="block2" class="form-control" #block2="ngModel" type="text" [(ngModel)]="ipBlock2">.
<input required name="block3" class="form-control" #block3="ngModel" type="text" [(ngModel)]="ipBlock3">.
<input required name="block4" class="form-control" #block4="ngModel" type="text" [(ngModel)]="ipBlock4">:
<input required name="block5" class="form-control" #block5="ngModel" type="text" [(ngModel)]="ipBlock5">
</div>
In ip-address-input.component.ts file I have:
#Input() ipProtocol
#Output() ipAddressCreated: EventEmitter<any> = new EventEmitter<{ipAddress: string}>();
ipBlock1: string;
ipBlock2: string;
//some logic like string concatenation (ipBlock1 + '.' + ipBlock2 + '.' + ...)
In app.component.html:
<ip-address-input [ipProtocol]="dhcpRangeStart"></ip-address-input>
<ip-address-input [ipProtocol]="dhcpRangeStop"></ip-address-input>
But when I check for example for the first IP it returns the last entered IP. How I can make this component reusable (make multiple instances of it)
Image with the actual IP Input box:
I've built a few controls similar to yours by leveraging ControlValueAccessor and NG_VALUE_ACCESSOR from angular forms. Basically, these provide you with a pattern to build your own custom and reusable form controls.
Below is an example code but you can also follow this tutorial to get your component built.
Component:
import { Component, OnInit, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl } from '#angular/forms';
const IP_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => IpInputComponent),
multi: true
};
#Component({
selector: 'ip-address-input',
providers: [IP_VALUE_ACCESSOR],
templateUrl: './ip-input.component.html',
styleUrls: ['./ip-input.component.css']
})
export class IpInputComponent implements OnInit, ControlValueAccessor {
ipBlock1: string;
ipBlock2: string;
ipBlock3: string;
ipBlock4: string;
ipBlock5: string;
disabled: boolean;
onChange: Function;
onTouched: Function;
get value(): string {
return ipBlock1 + '.' + ipBlock2 + '.' + ipBlock3 + '.' + ipBlock4 + ':' + ipBlock5;
}
constructor() {
this.onChange = (_: any) => {};
this.onTouched = () => {};
this.disabled = false;
}
ngOnInit() {
}
writeValue(obj: any): void {
if(obj) {
let arr = obj.split('.');
this.ipBlock1 = arr[0];
this.ipBlock2 = arr[1];
this.ipBlock3 = arr[2];
this.ipBlock4 = arr[3].split(':')[0];
this.ipBlock5 = arr[3].split(':')[1];
}
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
Template:
<div class="form-inline" style="display: inline-block">
<input required class="form-control" type="number" [ngModel]="ipBlock1" />.
<input required class="form-control" type="number" [ngModel]="ipBlock2" />.
<input required class="form-control" type="number" [ngModel]="ipBlock3" />.
<input required class="form-control" type="number" [ngModel]="ipBlock4" />:
<input required class="form-control" type="number" [ngModel]="ipBlock5" />
</div>
Usage:
<ip-address-input formControlName="ipAddress" (value)="ipAddress.value"></ip-address-input>
HTH

IE Datepicker too long

I have a date picker that is to be IE friendly. However when I use it, it's displaying far too long.
HTML:
<label class="mr-sm-2" for="startDate">Start Date:</label>
<date-picker
class="mr-sm-2"
name="startDate"
formControlName="startDate">
</date-picker>
datepicker.component.ts:
import * as _ from "lodash" ;
import * as moment from 'moment';
import { Component, Input, forwardRef, ViewEncapsulation } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'date-picker',
template: `
<ng-container *ngIf="isGoodBrowser; else datePickerFallback">
<input
type="date"
class="form-control text-right"
[class.is-invalid]="invalid"
[(ngModel)]="value"
[disabled]="disabled"
/>
</ng-container>
<ng-template #datePickerFallback>
<div class="fallback-date-picker" [class.invalid]="invalid">
<my-date-picker
[options]="options"
[disabled]="disabled"
[(ngModel)]="value"
(dateChanged)="value = $event">
</my-date-picker>
</div>
</ng-template>
`,
encapsulation: ViewEncapsulation.None,
styles: [
`.fallback-date-picker.invalid .mydp {
border-color: #dc3545;
}`,
`.fallback-date-picker .mydp .selection {
text-align: right;
padding-right: 65px;
}`
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerComponent),
multi: true
}
]
})
export class DatePickerComponent implements ControlValueAccessor {
private _value: any = null;
private propagateChange = _.identity;
isGoodBrowser: boolean = true;
disabled: boolean = false;
options: any = {
dateFormat: 'dd/mm/yyyy',
editableDateField: false
};
constructor() {
const userAgent = window.navigator.userAgent;
if (userAgent.indexOf('MSIE') >= 0) this.isGoodBrowser = false;
if (userAgent.indexOf('Trident') >= 0) this.isGoodBrowser = false;
if (userAgent.indexOf('Edge') >= 0) this.isGoodBrowser = true;
}
#Input() invalid: boolean = false;
#Input()
set value(v: any) {
if (v) {
if (_.isString(v)) {
const mDate = moment(v);
if (this.isGoodBrowser) {
this._value = v;
} else {
this._value = {
date: {
year: mDate.year(),
month: mDate.month() + 1,
day: mDate.date()
}
};
}
this.propagateChange(v);
} else if (v && v.jsdate) {
const mDate = moment(v.jsdate);
this.propagateChange(mDate.format('YYYY-MM-DD'));
} else {
this.propagateChange(null);
}
} else {
this._value = null;
this.propagateChange(null);
}
}
get value() {
return this._value;
}
writeValue(value: any) {
this.value = value ? value : null;
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched() { }
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
}
The images shows how the date picker displays, it goes across off the screen.
I feel like the problem might be in the html layout as this doesn't happen everytime I use the date picker.
I have solved the issue and it has nothing to do with the code shown.

Angular 4 : How to use pattern in an input list

I have a form html and the button submit becomes enable only when the form is valid. That is, when a particular input contains the recommended pattern. I need to use this pattern in an inputlist. It works with a simple input but with the input list, the list disappears :
<input list="refNumbers" pattern="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" formControlName="refNb" type="text" name="article" maxlength="8" size="15" required title="8 characters" />
<datalist id="refNumbers">
<option *ngFor="let ref of listOfArticles">{{ref.refNumber.input}}</option>
</datalist>
Otherwise, how can i disable button submit if the ref number selected is not in the datalist ? Because it seems that the valid condition on the form is not enough :
<button type="submit" [disabled]="!myFormGroup.valid" >Valider</button>
component.ts :
import { Component, OnInit } from '#angular/core';
import 'rxjs/add/operator/switchMap';
import { ManagementArbologistiqueService } from "../management-arbologistique.service";
import { ActivatedRoute, Params } from '#angular/router';
import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '#angular/forms';
#Component({
selector: 'app-arbologistique',
templateUrl: './arbologistique.component.html',
styleUrls: ['./arbologistique.component.css']
})
export class ArbologistiqueComponent implements OnInit {
private reponseTest: String;
private listOfArticles :Array<Object>
private pathDownload: any;
private myFormGroup: FormGroup;
fileToUpload: File = null;
private buttonSubmitEnabled: boolean = false;
constructor(public fb: FormBuilder, private managementArbo: ManagementArbologistiqueService, private route: ActivatedRoute) { }
ngOnInit() {
this.myFormGroup = this.fb.group({
itemRows: this.fb.array([this.initItemRows()])
})
this.myFormGroup.valueChanges.subscribe(x => this.buttonSubmitEnabled = false);
this.getListBdd();
}
initItemRows() {
return this.fb.group({
... //other fields
refNb: ['',Validators.required],
... //other fields
})
}
addRow(index: number) {
console.log("functionAddRow called");
const control = <FormArray>this.myFormGroup.controls['itemRows'];
control.insert(index, this.initItemRows());
}
deleteRow(index: number) {
console.log("functionDeleteRow called");
const control = <FormArray>this.myFormGroup.controls['itemRows'];
control.removeAt(index);
}
sendForm() {
this.buttonSubmitEnabled=true;
console.log("functionExportCalled");
this.route.params.subscribe((params: Params) => {
let subroute = "exportation";
this.managementArbo.postProducts(subroute, JSON.stringify(this.myFormGroup.value))
.subscribe(
res => { this.reponseTest = res; console.log('reponse:' + res); }
,
err => console.log(err),
() => console.log('getProducts done'));
});
}
getListBdd() {
this.route.params.subscribe((params: Params) => {
let subroute = "getRefNumber";
this.managementArbo.getProducts(subroute)
.subscribe(
res => { this.listOfArticles = res; console.log('reponse:' + res); }
,
err => console.log(err),
() => console.log('getProducts done'));
});
}
get refNb() {
return this.myFormGroup.get('itemRows.refNb');
}
}
If you want to add Validation in reactive form you can use build in Validators
initItemRows() {
const regEx="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]";
return this.fb.group({
... //other fields
refNb: ['',Validators.required,Validators.pattern(regEx)],
... //other fields
})
}
<button type="submit" [disabled]="!myFormGroup.valid" >Valider</button>