How to focus a control that is inside a FormArray? - html

I have a FormArray with an email field and I need to focus the field whenever a new control is added inside FormArray for the user to type the email.
I tried with #ViewChild but it doesn't work correctly as it will repeat the elements with the same id #emailInput.
component.ts
public addEmail(): void {
this.emailArray.push(this.createControlEmail());
// Here I need to focus on the control that has been added.
this.form.markAsDirty();
}
component.html
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start"
[formGroupName]="indexControlEmail">
<mat-form-field fxFlex="60" [floatLabel]="true">
<input #emailInput matInput placeholder="E-mail" formControlName="email">
</mat-form-field>
<button mat-icon-button aria-label="delete" matTooltip="Remove email"
[matTooltipPosition]="'after'" [matTooltipShowDelay]="1000"
(click)="removeEmail(indexControlEmail)">
<mat-icon class="secondary-text">close</mat-icon>
</button>
</div>
</div>

You can use ViewChildren to get list of email inputs on page.
Any time a child element is added, removed, or moved, the query list
will be updated, and the changes observable of the query list will
emit a new value.
#ViewChildren('emailInput') emailInputs: QueryList<ElementRef>;
Each time you do addEmail focus on last emailInput:
addEmail() {
this.emailInputs.changes.pipe(take(1)).subscribe({
next: changes => changes.last.nativeElement.focus()
});
this.emailArray.push(this.createControlEmail());
// const newControl = this.fb.control('');
// this.emailArray.push(newControl);
}

Related

How to disable item from different component in Angular

So I have Tags component and I imported that component in my Chart-details component. In my Chart Details component I have a checkbox that will disable all the input box, drop down box, buttons that are located inside chart-details page but some reason my imported tags component is not disabling when I click the checkbox. Any suggestion or help on how to fix this so that a user cannot add or remove tags when the checkbox is clicked will be really appreciated.
Chart Details Component. HTML
//Check box to disable all the input, drop down, buttons
<mat-checkbox *ngIf="chart && workspace" color="primary" [disabled]="this.workspace.type === WorkspaceType.user"
[(ngModel)]="chart.isPublished" (ngModelChange)="publishChange($event)">Published</mat-checkbox>
//Example Button
<button color="primary" mat-flat-button (click)="saveClick()" [disabled]="this.chart.isPublished">Save</button>
// Imported Tags Component
<mc-tags [_normalTags]="chart.tags" [removable]="true" [selectable]="true"
(added)="tagAdded($event)" (removed)="tagRemoved($event)" [disabled]="this.chart.isPublished" >
</mc-tags>
I have added [disabled]="this.chart.isPublished" but I got an error saying "Can't bind to 'disabled' since it isn't a known property of 'mc-tags'. ". Also I tried (disabled) but still not working and user can still add or remove tags even though checkbox is checked.
Tags Component.HTML
<mat-chip-list #chipList [disabled]="true">
<mat-chip *ngFor="let chip of normalTags" [selectable]="selectable"
[removable]="removable"
(removed)="removeTags(chip)">
{{chip.tag}}
</mat-chip>
<input matInput #input [(ngModel)]="tagIn" [formControl]="tagCtrl2"
[matAutocomplete]="auto" />
</mat-chip-list>
Right now I have to do [disabled]="true" on mat-chip-list in Tags component.html so that user can't add or remove it. I don't want to hard code this and want to control this using Chart Detail Component Checkbox.
The disable matchip and input look like this (PIC)
It's not gonna run but I've attached the whole code for these two component over here https://stackblitz.com/edit/angular-ivy-wwfcai . thanks
Have you declared "#Input() disabled" in your child component class?
In Child.component.ts
#Input() disabled: any;
ngOnInit() {
if(disabled){
//set properties to true/false accordingly
}
}
In Parent.component.html
[disabled]="this.workspace.type === WorkspaceType.user"

Validation using if statement using input binding in Angular

I am creating a reusable component for Input field. I created a Boolean tag named "IsValid" inside my typescript file to show validation message.
My typescript file
export class InputControlsComponent implements OnInit {
#Input() IsValid;
#Input() className;
#Input() controlName;
#Input() inputType;
constructor() { }
ngOnInit() {
}
}
Html File
<div>
<input type="{{inputType}}" class="{{className}}" >
<span class="error-message" *ngIf="!IsValid">This Field is required</span>
</div>
How it is being used
<div>
<label>Name</label>
<app-input-controls inputType="text" controlName="Address" className="form-control" IsValid = "false">
</app-input-controls>
</div>
I am able to change the value of the Boolean tag of typescript file but the error message is not changing on change of the Boolean tag.
You can use ngModel like [(IsValid)],
If you use ngModel,your model is binding two way.
For example set your html
<app-input-validation [inputType]="'text'" [controlName]="Address" [className]="form-control" [(IsValid)]="isValidDate">
</app-input-validation>
You can set isValid property on parent component whenever you want.
Only write this.isValidText=true;
Please examine example
change !IsValid to IsValid. it will work
<div>
<input type="{{inputType}}" class="{{className}}" >
<span class="error-message" *ngIf="IsValid">This Field is required</span>
</div>

Reactive form angular using FormArrayName

In my form I need to add phones, I have seen that in the database you are saving a string array with the phone numbers that I added, but the moment the value returned from the database is set to the form, only the first array value is shown .
I would like to display all array values that contain number of phones in string format.
component.html:
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start">
<div formArrayName="phones" fxFlex="30">
<div *ngFor="let phone of phones.controls; index as i">
<mat-form-field>
<input matInput [formControlName]="i" placeholder="Phone Number">
</mat-form-field>
</div>
</div>
<div fxFlex="30">
<button mat-raised-button class="blue" (click)="addPhone()">Add</button>
</div>
</div>
component.ts:
this.myForm = this._formBuilder.group({
phones: new FormArray([new FormControl('')]),
});
this.values$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((values) => {
// values = {phones: ['999999', '88888', '77777']}
if (values) {
this.myForm.patchValue(values, { emitEvent: false });
}
});
get phones(): FormArray { return this.myForm.get('phones') as FormArray; }
addPhone(): void { this.phones.push(new FormControl('')); }
Even returning more than one value inside the array only displays the first array value on my form, what am I doing wrong?
patchValue won't create any form controls for you, so when you call patchValue it only sets the value for the first form control in your form array, because you only created one.
You have to call addPhone for each phone number or create form inside the describe when you already know how many form controls there should be, but you still need to create as many form controls as number of elements in your array.
this.myForm = this._formBuilder.group({
phones: new FormArray(),
});
this.values$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((values) => {
if (values && values.phones) {
phones.forEach(phone => this.addPhone(phone));
}
});
get phones(): FormArray { return this.myForm.get('phones') as FormArray; }
addPhone(phone = ''): void { this.phones.push(new FormControl(phone)); }

Angular required validation does not let form valid until input is edited

I have a below html where I have implemented HTML5 required property:
<div class="col-sm-3">
<label>{{question.placeholder}}<span *ngIf="question.required">*</span></label>
</div>
<div class="col-sm-9">
<mat-form-field style="width:100%">
<input matInput [placeholder]="question.placeholder" [id]="question.key" [maxLength]="question.maxLength" [formControlName]="question.key"
type="email" [required]="question.required"[readonly]="question.readonly" [value]="question.value" (keyup)="checkValue($event, question)"
(paste)="checkPasteValue($event, question)">
<mat-hint align="end">{{question.value ? question.value.length : 0}} / {{question.maxLength}}</mat-hint>
</mat-form-field>
</div>
It is an email field.
[required]="question.required"
Below is a form button which needs to enabled when form is valid.
<button mat-raised-button class="md-primary md-raised pull-right" color="primary" style="margin-right:10px;" *ngIf="isEditMode" [disabled]="!dataEntryForm.valid"
(click)="updateDataEntry($event)">UPDATE</button>
button does not enable until input is dirty, whereas it is pre filled. So it should be satisfied for required. So what should be used in such case?
EDIT
Form is pre-filled by using service:
editSectionDataItem(item: any) {
var me = this;
// / var decodedItem = this.decodeDataArray([item]);
// console.log(item, this.originalData);
var decodedItem = this.originalData.filter(function (originalItem) {
return originalItem.id == item.id;
});
console.log(decodedItem, item);
var mission = {
widgetConfig: this.widgetConfig,
settings: this.settings,
fields: this.fields,
isEditMode: true,
data: flattenJSON(decodedItem[0])
}
console.log(mission);
this._widgetService.missionToOpenDataEntryForm(mission);
}
You should use [(ngModel)] instead of [value].
[(ngModel)] is basically what gives Angular the validation properties of the form. Without it, you can't track the validation state of the input (at least on a template-driven form).
More info about this on the Angular docs, here and here

Angular 5 [object HTMLDivElement]

When I click the button, I'm sending the 'copyRow' element to the 'copyItem' method. I'm equalizer the 'copyItem' element with the 'item' variable in the 'Typescript' file.
This 'item in the html file' variable when I want to show '[object htmldivelement]' I'm getting as output.
create.component.html
<div class="questions">
<div class="form-group copy-row" #copyRow>
<label>Question</label>
<input type="text" class="form-control" placeholder="Question title">
</div>
{{ item }}
</div>
<button type="button" class="btn btn-primary" (click)="copyItem(copyRow)">Add</button>
create.component.ts
item;
constructor() { }
ngOnInit() {
}
copyItem(row) {
this.item = row;
}
EDIT
My aim is to do a survey project.
When I click on the 'Add' button, the same '#copyRow' element will show in the {{ item }} section. However, I get an output like the second link.
1: http://prntscr.com/j1ncp1
2: http://prntscr.com/j1nd19
I'm not sure what you want to achieve with this but this is the explanation of what is happening in your code.
#copyRow is a reference to the HTML element & in this case it is a div element. So when you're passing the reference using copyItem function, you are actually passing an HTML element.
Putting these things together, the copyItem method gets following signature -
public item: HTMLElement;
public copyItem(row: HTMLElement): void {
this.item = row;
//this is how you get inner HTML
console.log(row.innerHTML);
//this is to get inner text
console.log(row.innerText);
}
This is the reason why you are getting [object HTMLDivElement] in the template for item binding (you are trying to display an object).
You can simply use {{item.innerHTML}} or {{item.innerText}} to display the inner content of selected HTML element.
Let me know if I'm missing anything.
EDIT - Alternative Way (Binding in Template)
If you are not doing additional stuff in the component, the binding can be as simple as assigning the HTML element reference directly to the item property in template itself -
<div class="questions">
<div class="form-group copy-row" #copyRow>
<label>Question</label>
<input type="text" class="form-control" placeholder="Question title">
</div>
{{ item?.innerHtml }}
{{ item?.innerText }}
</div>
<button type="button" class="btn btn-primary" (click)="item = copyRow">Add</button>
EDIT 2 (as per discussion in comments)
Try this template to iterate over same HTML on button click -
<div class="questions">
<ng-container *ngFor="let item of items">
<div class="form-group copy-row">
<label>Question</label>
<input type="text" class="form-control" placeholder="Question title" />
</div>
</ng-container>
<button type="button" class="btn btn-primary" (click)="items = items || []; items.push(1);">Add</button>
Just initialise your items array as -
public items: Array<number> = [1];
I hope this helps :)
Use ViewChild and ElementRef
import { Component, ViewChild, ElementRef } from '#angular/core'
#ViewChild('item')
item: ElementRef;
constructor() { }
ngOnInit() {
}
copyItem() {
// this.item -> now you have the reference of the element
}