Angular ngIf formGroup - html

I have a form on Angular that allows to display an input according to the value selected in a drop-down list.
Here is an example of my code:
(If two is selected an input appears)
https://stackblitz.com/edit/angular-fqkfyx
If I leave the [formGroup] = "usForm" the input display does not work. On the other hand if I delete [formGroup] = "usForm" of the tag form my code works as I want. So the problem is related to [formGroup] = "usForm"
My html:
<div class="offset-md-2">
<form [formGroup]="usForm">
<div class="div-champs">
<p id="champs">Type
<span id="required">*</span>
</p>
<div class="select-style ">
<select [(ngModel)]="selectedOption" name="type">
<option style="display:none">
<option [value]="o.name" *ngFor="let o of options">
{{o.name}}
</option>
</select>
</div>
</div>
<p id="champs" *ngIf="selectedOption == 'two'">Appears
<input type="appears" class="form-control" name="appears">
</p>
</form>
</div>
My component.ts:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder } from '#angular/forms';
#Component({
selector: 'app-create-us',
templateUrl: './create-us.component.html',
styleUrls: ['./create-us.component.css']
})
export class CreateUsComponent implements OnInit {
public usForm: FormGroup;
public selectedOption: string;
constructor(private fb: FormBuilder) {
}
ngOnInit() {
this.createForm();
}
createForm() {
this.usForm = this.fb.group({
'type': [null, ],
'appears': [null, ],
});
}
options = [
{ name: 'first', value: 1 },
{ name: 'two', value: 2 }
];
}
I simplified my problem as much as in reality it is in a large form with a dozen input
I need your help, thanks in advance
Regards,
Valentin

You should be using formControlName instead of [(ngModel)].
And then in comparison, you should be comparing to usForm.value.type instead of the selectedValue.
Give this a try:
<div class="offset-md-2">
<form [formGroup]="usForm">
<div class="div-champs">
<p id="champs">Type
<span id="required">*</span>
</p>
<div class="select-style ">
<select formControlName="type" name="type">
<option style="display:none">
<option [value]="o.name" *ngFor="let o of options">
{{o.name}}
</option>
</select>
</div>
</div>
<p id="champs" *ngIf="usForm.value.type == 'two'">Appears
<input type="appears" class="form-control" name="appears">
</p>
</form>
</div>
Here's a Sample StackBlitz for your ref.

Your template is loaded before form group is created. Add ngIf to wail while form group will be created:
<div class="offset-md-2" *ngIf="usForm">
<form [formGroup]="usForm">

Related

Display only shows last value of an Angular for loop even though devtools HTML has correct values

I'm using a for loop to display different sections of my blog for editing purposes.
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Route, ParamMap } from '#angular/router';
import { HttpClient } from '#angular/common/http';
import { Content } from '../model/Content';
#Component({
selector: 'app-edit-story',
templateUrl: './edit-story.component.html',
styleUrls: ['./edit-story.component.css']
})
export class EditStoryComponent implements OnInit {
story: any;
storyName: any;
isMe: boolean= false;
constructor(private route: ActivatedRoute, private httpClient: HttpClient) { }
ngOnInit(): void {
if (localStorage.getItem('userId')=='62e348924d52fa7420bb96bc') {
this.isMe = true;
}
this.storyName = this.route.snapshot.paramMap.get('storyName');
var url = 'http://localhost:3002/api/stories/' + this.storyName;
this.httpClient.get(url).subscribe(data => {
this.story = data;
console.log(this.story);
})
}
editStory() {
}
addContent() {
var newContent = new Content("", "", "");
this.story.contents.push(newContent);
}
}
<div *ngIf="isMe">
<form #editStoryForm = "ngForm" (ngSubmit)="editStory()" class="addStory">
<label for="title">Title</label>
<input name="title" type="text" [(ngModel)]="story.title" req/>
<label for="subtitle">Subtitle</label>
<input type="subtitle" [(ngModel)]="story.subtitle" name="subtitle" req>
<label for="name">Name</label>
<input type="name" [(ngModel)]="story.name" name="name" req>
<button (click)="addContent()">Add Content</button>
<div *ngFor="let content of story.contents">
<label for="type">Type</label>
<select name='type' [(ngModel)]="content.type" value="{{content.type}}">
<option value=''>Pick one</option>
<option value='text' selected="selected">text</option>
<option value='image'>image</option>
</select>
<label for="text">Text</label>
<textarea name="text" cols="50" rows="7" [(ngModel)]="content.text">{{content.text}}</textarea>
<label for="url">url</label>
<input name="url" type="text" value="{{content.url}}" [(ngModel)]="content.url">
</div>
<button type="submit">Edit</button>
</form>
</div>
In the console.log to display the story, the contents array appears fine. Even when I open devtools and check the HTML elements, the values are correct.
devtools showing the innerHTML values are all different
However the page itself has all these element with only the values of the last array item.
contarary to the devtools html, the page displays the last array values over and over
please help.
You are using template-driven forms. The problem is in the way you're registering the child controls using ngModel and the name attribute.
In your ngFor you do:
<textarea name="text" [(ngModel)]="content.text">{{content.text}}</textarea>
So basically you're assigning the same name to all children.
Instead, you should do something like this:
<div *ngFor="let content of story.contents; index as i">
<!-- other elements -->
<label for="text">Text</label>
<textarea name="{{ 'text-' + i }}" [(ngModel)]="content.text">
{{ content.text }}
</textarea>
<!-- other elements -->
</div>
You may also find the last section of this article helpful.
Cheers.

Object is possibly 'null' in Reactive Forms Angular

I need your help!I am newbie in Angular Reactive Forms and I am trying to make simple form with validation using Angular's Reactive Forms. But I get an error in this part of my code (ngIf directive, property 'invalid'):
<div class="container">
<form class="card" [formGroup]="form" (ngSubmit)="submit()">
<h1>Angular Forms</h1>
<div class="form-control">
<label>Email</label>
<input type="text" placeholder="Email" formControlName="email">
<div
*ngIf="form.get('email').invalid"
class="validation">
</div>
</div>
<div class="form-control">
<label>Пароль</label>
<input type="password" placeholder="Пароль" formControlName="password">
<div class="validation"></div>
</div>
<div class="card">
<h2>Адрес</h2>
<div class="form-control">
<label>Страна</label>
<select>
<option value="ru">Россия</option>
<option value="ua">Украина</option>
<option value="by">Беларусь</option>
</select>
</div>
<div class="form-control">
<input type="text">
</div>
<button class="btn" type="button">Выбрать столицу</button>
</div>
<div class="card">
<h2>Ваши навыки</h2>
<button class="btn" type="button">Добавить умение</button>
<div class="form-control">
<label></label>
<input type="text">
</div>
</div>
<button class="btn" type="submit" [disabled]="form.invalid">Отправить</button>
</form>
</div>
TypeScript code:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent implements OnInit {
title = 'forms';
form!: FormGroup;
ngOnInit() {
this.form = new FormGroup({
email: new FormControl('',
[Validators.email,
Validators.required]),
password: new FormControl(null,
[Validators.required,
Validators.minLength(8)])
})
}
submit() {
console.log('Form submitted', this.form);
const formData = { ...this.form.value };
console.log('Form Data:', formData);
}
}
I use Angular 12 and I followed guide on Udemy, so this is very strange, because my mentor's code works correct. I created FromGroup and FormControls, gave them names, so I am really confused about this error. Do you have any ideas how can I fix it?
The Object is possibly 'null' error can happen due to strict type checking and can be solved in 2 ways:
Either assert that you are absolutely sure that can never be null, by using the ! (not null assertion operator)
Use the ? (optional chaining operator) to stop an eventual error from happening in case the object is indeed null
So you can replace the if statement with form.get('email')?.invalid and it should work. A similar question has been asked here.

How to add a search filter to a select option in angular

I'm trying to add to add a search filter in my select option list because there are many options and I think that without a search, it will not be easy for the user to find the option that he wants to select.
I hope you will understand me because I'm not good at English.
Here's my code (it's just a part of my table)
<ng-container *ngFor="let menaceProcessus of menaceProcessusTab">
<tr>
<td colspan="4" bgcolor="#f1f1f1"><b>{{menaceProcessus?.processus?.nom}}</b></td>
</tr>
<ng-container *ngFor="let actif of menaceProcessus?.actifs">
<tr>
<td [rowSpan]="actif?.menaces?.length+1">{{actif?.actif?.nom}}</td>
</tr>
<ng-container *ngFor="let mnVuln of actif?.menaces">
<tr>
<td>{{mnVuln?.vulnerabilite?.nom}}</td>
<td>
<select class="form-control"
(change)="mnVuln?.menaceActif?.menace.id = $event.target.value;
updateMenaceProcessus()">
<option></option>
<option *ngFor="let menace of menaces"
[value]="menace.id"
[selected]="menace.id === mnVuln?.menaceActif?.menace.id">
{{menace.nom}}</option>
</select>
</td>
<td>
<input class="form-control"
type="text" [value]="mnVuln?.menaceActif?.probabilite">
</td>
</tr>
</ng-container>
</ng-container>
</ng-container>
If you want to filter in select option, you can use datalist control of HTML. If you use it, there is no need to do extra coding for filtering. It has built-in functionality for filtering.
HTML :
<input list="menace" name="menace">
<datalist id="menace">
<option *ngFor="let menace of menaces">{{menace.nom}} </option>
</datalist>
I think you can use ng-select: https://www.npmjs.com/package/#ng-select/ng-select
for Yor Requirement
If you want to filter your array menaces by typing on the first letter, then it is possible to filter your array like this:
HTML:
<select class="form-control"
(change)="mnVuln?.menaceActif?.menace.id = $event.target.value;
updateMenaceProcessus();
filterMenaces($event)">
<option></option>
<option *ngFor="let menace of menaces"
[value]="menace.id"
[selected]="menace.id === mnVuln?.menaceActif?.menace.id">
{{menace.nom}}</option>
</select>
TypeScript:
origMenaces = [];
methodAPIToGetMenaces() {
this.yourService()
.subscribe(s=> {
this.menaces = s;
this.origMenaces = s;
});
}
filterMenaces(str: string) {
if (typeof str === 'string') {
this.menaces = this.origMenaces.filter(a => a.toLowerCase()
.startsWith(str.toLowerCase()));
}
}
UPDATE 1:
If you want to filter by input value:
HTML:
<input type="text"
(ngModelChange)="filterItem($event)"
[(ngModel)]="filterText">
<br>
<select
#selectList
[(ngModel)]="myDropDown"
(ngModelChange)="onChangeofOptions($event)">
<option value="empty"></option>
<option *ngFor="let item of items">
{{item}}
</option>
</select>
<p>items {{ items | json }}</p>
TypeScript:
import { Component, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 4';
myDropDown : string;
items = ['one', 'two', 'three'];
origItems = ['one', 'two', 'three'];
#ViewChild('selectList', { static: false }) selectList: ElementRef;
onChangeofOptions(newGov) {
console.log(newGov);
}
filterItem(event){
if(!event){
this.items = this.origItems;
} // when nothing has typed*/
if (typeof event === 'string') {
console.log(event);
this.items = this.origItems.filter(a => a.toLowerCase()
.startsWith(event.toLowerCase()));
}
console.log(this.items.length);
this.selectList.nativeElement.size = this.items.length + 1 ;
}
}
Please, see work example at stackblitz
I couldn't find any easy way to do this for the select, but the autocomplete
component seems to do something very close to what is desired. The only issue with it is, that it does not require the value to be one of the options. That's why I created the following directive:
// mat-autocomplete-force-select.directive.ts
import { Directive, Input } from '#angular/core';
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from '#angular/forms';
import { MatAutocomplete } from '#angular/material/autocomplete';
#Directive({
selector: '[matAutocomplete][appMatAutocompleteForceSelect]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: MatAutocompleteForceSelectDirective,
multi: true,
}
]
})
export class MatAutocompleteForceSelectDirective implements Validator {
#Input('matAutocomplete')
matAutocomplete!: MatAutocomplete;
validate(control: AbstractControl): ValidationErrors | null {
if (this.matAutocomplete.options && !this.isOptionOfAutocomplete(control.value)) {
return { 'notAnAutocompleteValue' : true }
}
return null;
}
private isOptionOfAutocomplete(value: string) {
return this.matAutocomplete.options.find(option => option.value === value) !== undefined;
}
}
After that, you can add the directive to the input of the autocomplete, and it will have an error of notAnAutocompleteValue if it's not that.
<input matInput type="text" appMatAutocompleteForceSelect [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" >
<!-- ... -->
</mat-autocomplete>
For anyone unfamiliar with the autocomplete component, https://material.angular.io/components/autocomplete/overview will be helpful.
Cheers :)
<input list="menace" name="menace">
<datalist id="menace">
<option *ngFor="let menace of menaces">{{menace.nom}} </option>
</datalist>
This works fine and in the case of a form just add a form control name and the value can be picked. Answered by #ParthDhankecha

Not able to store dynamic FormArray in FormGroup

I have a FormGroup which has three FormControl fields and one FormArray fields as shown in the figure below. The requirement is to take the manager name from user and once add button is pressed, manager details should be displayed in table. In table a remove button is provided, when remove button is pressed manager should be removed form the table and list. When the form is submitted list of managers should be saved. Everything works fine except formArray logic.
I tried to find a solution to this online (followed various links:- https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/,
Angular 4 Form FormArray Add a Button to add or delete a form input row), but did not helped much. There is not much material on how to store formArray in formGroup. Please suggest.
Below is my code, please have a look:-
1. manager-create-modal.component.html
<div>
<form [formGroup]="createForm" (ngSubmit)="onFormCreation()">
<div class="row">
<div class="column">
<div class="form-inline">
<div class="form-group">
<label for="remote_access_method">Remote Access Method: <font color="orange"> *</font> </label>
<input type="text" size='38' class="form-control" formControlName="remote_access_method" >
</div>
</div>
<br>
<div class="form-inline">
<div class="form-group">
<label for="status">Current Status: <font color="orange"> *</font> </label>
<input type="text" size='38' class="form-control" formControlName="status">
</div>
</div>
<br>
<div class="form-inline">
<div class="form-group">
<label for="secregid">Registration ID:<font color="orange"> *</font> </label>
<input type="text" size='38' class="form-control" formControlName="secregid">
</div>
</div>
<br><br>
<div class="form-inline">
<div class="form-group">
<br><br>
<div formArrayName="manager_formArray">
Enter name: <input type="text" class="form-control" formControlName="MgrName" size='50' >
<button type="button" class="btn btn-primary btn-sm" (click)="addPM()">Add</button>
<br><br>
<table class="table table-hover">
<tr><th>#</th><th>Manager Name</th><th>Remove</th></tr>
<tr *ngFor="let pm of createForm.get('manager_formArray').value; let id = index">
<td>{{id+1}}</td>
<td>{{pm.MgrName}}</td>
<td>
<span class="table-remove">
<button type="button" class="btn btn-primary btn-sm" (click)="removeMgr(id)">Remove</button>
</span>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<br>
</div>
<br><br>
<div class="form-group">
<button class="btn btn-primary">Submit</button>
</div>
</form>
</div>
2. manager-create-modal.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, FormArray, FormControl, Validators } from '#angular/forms';
#Component({
selector: 'app-manager-create-modal',
templateUrl: './manager-create-modal.component.html',
styleUrls: ['./manager-create-modal.component.css']
})
export class ManagerCreateModalComponent implements OnInit {
createForm: FormGroup;
manager_formArray: FormArray;
remote_access_method: FormControl;
status: FormControl;
secregid: FormControl;
constructor(private formBuilder: FormBuilder) { }
createFormControls(){
this.remote_access_method = new FormControl('');
this.status = new FormControl('');
this.secregid = new FormControl('');
this.manager_formArray = new FormArray([ this.createItem() ]);
}
createItem(): FormGroup {
return this.formBuilder.group({
MgrName: ''
});
}
createFormVariables(){
this.createForm = new FormGroup({
remote_access_method : this.remote_access_method,
status : this.status,
secregid : this.secregid,
manager_formArray: this.manager_formArray,
})
}
ngOnInit() {
this.createFormControls();
this.createFormVariables();
}
addPM(mgr: any): void {
console.log("inside addPM");
this.manager_formArray.push(this.formBuilder.group({MgrName:''}));
console.log("list after addition:"+this.manager_formArray.value);
for(let i = 0; i < this.manager_formArray.length; i++) {
console.log(this.manager_formArray.at(i).value);
}
}
get managerFormArray() {
return this.manager_formArray.get('MgrName') as FormArray;
}
onFormCreation(){
console.log("success")
}
}
The manager name is not displayed in the table and I keep on getting below error:-
ERROR Error: Cannot find control with path: 'manager_formArray ->
MgrName' at _throwError (forms.js:1731) at setUpControl
(forms.js:1639) at
FormGroupDirective.push../node_modules/#angular/forms/fesm5/forms.js.FormGroupDirective.addControl
(forms.js:4456) at
FormControlName.push../node_modules/#angular/forms/fesm5/forms.js.FormControlName._setUpControl
(forms.js:4961) at
FormControlName.push../node_modules/#angular/forms/fesm5/forms.js.FormControlName.ngOnChanges
(forms.js:4911) at checkAndUpdateDirectiveInline (core.js:9031)
at checkAndUpdateNodeInline (core.js:10299) at
checkAndUpdateNode (core.js:10261) at debugCheckAndUpdateNode
(core.js:10894) at debugCheckDirectivesFn (core.js:10854) inside
addPM manager-create-modal.component.ts:50 list after
addition:[object Object],[object Object]
manager-create-modal.component.ts:53 {MgrName: ""}
manager-create-modal.component.ts:53 {MgrName: ""}
I even don't understand why elements are not getting added to manager_formArray. Please help me out.
You have a few issues. First of all, it is better to move the Input which adds more FormGroups to your FormArray outside of the <div formArrayName="manager_formArray">- element. I created a new FormControl this.mgrNameInput = new FormControl(''); for this reason (see StackBlitz for more details).
You also need to add the message to the new entry when you press the Add-button, calling the addPM()-method:
addPM(){ // removed the argument, using the controller inside the method instead.
this.manager_formArray.push(this.formBuilder.group({MgrName:this.mgrNameInput.value}));
this.mgrNameInput.reset(); // reset the input field.
}
I also added the remove-method when removing an entry.
removeMgr(index: number){
this.manager_formArray.removeAt(index);
}
Please check the StackBlitz for the complete example

How to get .json objects by id in Angular 2?

I'm trying to get objects by id from a JSON file after selecting an item from my drop-down list. So far I have managed to retrieve all the objects from JSON but not by ID.
Typescript
showInfo() {
var selected = this.diadikasies.filter(x=> x.content);
console.log (selected);
}
HTML
<div *ngIf="diadikasies && diadikasies.length > 0" class="form-group">
<label for="diadikasies">
<i class="fas fa-clipboard-list" aria-hidden="true"></i> Please Select: </label>
<select #proc (change)="showInfo()">
<option [ngValue]="undefined" disabled selected>Επιλέξτε Διαδικασία</option>
<option *ngFor="let diad of diadikasies" [value]="diad.id">{{ diad.title.rendered }}</option>
</select>
</div>
Thanks in advance.
You can do with find() method like below
TS file
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
description;
opts = [{
id: 1,
name: 'Test 1',
description: 'This is Test 1 description'
}, {
id: 2,
name: 'Test 2',
description: 'This is Test 2 description'
}];
showInfo(value) {
let selectedOpt = this.opts.find(opt => opt.id == value);
this.description = selectedOpt.description;
}
}
In the template file
<div *ngIf="opts && opts.length > 0" class="form-group">
<label for="opts">
<i class="fas fa-clipboard-list" aria-hidden="true"></i> Please Select: </label>
<select #proc (change)="showInfo(proc.value)">
<option [ngValue]="undefined" disabled selected>Επιλέξτε Διαδικασία</option>
<option *ngFor="let diad of opts" [value]="diad.id">{{ diad.name }}</option>
</select>
</div>
<div>{{ description }}</div>
You can find the working example from here Stackblitz
You can use ngModel and ngModelChange together to get the selected value
<select #proc [(ngModel)]="selectedObj" (ngModelChange)="showInfo()">
and inside showInfo method,
var selected = this.diadikasies.filter(x=> x.id == this.selectedObj.id);