I am trying to make a specific form field in my overall form dynamic so that x amount of objects can be added to the array of that field.
However, every time the page inits I get a
Error: Cannot find control with path: 'media -> '
This is my form.ts
this.cardForm = new FormGroup({
'title': new FormControl(cardTitle),
media: this._fb.array([
this.initMedia(),
]),
'links': new FormGroup({
'news': new FormControl(news),
}),
initMedia() {
return this._fb.group({
type: new FormControl(),
raw: new FormControl(),
primary: new FormControl(),
thumbs: this._fb.group({
default: new FormControl()
})
})
}
addMedia(){
const control = <FormArray>this.cardForm.controls['media'];
control.push(this._fb.control(['']));
}
removeMedia(i: number){
const control = <FormArray>this.cardForm.controls['media'];
control.removeAt(i);
}
this is my form.html:
<div class="row">
<div class="col-xs-12">
<form [formGroup]="cardForm" (ngSubmit)="onSubmit(cardForm.value)">
<div class="row">
<div class="col-xs-12">
<button
type="submit"
class="btn btn-success">
Update Card</button>
<button
type="button"
class="btn btn-danger"
(click)="onCancel()">
Cancel</button>
</div>
</div>
<div formArrayName="media">
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<div *ngFor= "let media of cardForm.controls.media.controls; let i=index">
<span>Media {{i + 1}}</span>
<span *ngIf="cardForm.controls.media.controls.length > 1" (click)="removeMedia(i)"></span>
</div>
<div [formGroupName]="i">
<div>
<label>Url</label>
<md-input-container class="mdcontainer">
<input mdInput placeholder="Media Url" type="text" formControlName="raw">
</md-input-container>
</div>
</div>
</div>
</div>
</div>
</div>
And the media[] looks like this:
media: [
{
raw:'string',
primary: boolean,
type: 'string',
thumb: {
default: 'string'
{
}
]
What am I missing/doing wrong here for that error to come up?
Any help/tips/suggestions would be much appreciated.
[formGroupName]="i" should be inside of *ngFor. In this case i variable will have non undefined value
<div *ngFor="let media of cardForm.controls.media.controls; let i=index">
<span>Media {{i + 1}}</span>
<span *ngIf="cardForm.controls.media.controls.length > 1" (click)="removeMedia(i)"></span>
<div [formGroupName]="i">
<div>
<label>Url</label>
<md-input-container class="mdcontainer">
<input mdInput placeholder="Media Url" type="text" formControlName="raw">
</md-input-container>
</div>
</div>
</div>
Plunker Example
You could try the get method instead. I think it is a bit more user-friendly:
cardForm.get('media')
Check it out in the docs here: https://angular.io/guide/reactive-forms#inspect-formcontrol-properties
Related
What I am trying to do:
I am building a form repeater, Now I have the plugin working and able to add fields etc, The main form is for setting up a survey with the following logic:
OUTER REPEATER:
Question: input type text \ **Question Type: **input type select
INNER REPEATER:
Choice (can add multiple choices)
On Question Type users can choose a question type and have the option to choose a Single / multiple choice input: at this point I want the nested inner field to be visible (Choice)
So if the choice type is a rating, or a text answer, do not show the nested Choices repeater field only show it when the choice is a single or multiple choice answer.
CODE:
HTML
<!--begin::Repeater-->
<div id="survey">
<div class = "card card-flush shadow-sm">
<div class = "card-header">
<h3 class = "card-title">Survey Questions</h3>
</div>
<div class = "card-body py-2">
<!--begin::Form group-->
<div class="form-group">
<div data-repeater-list="survey">
<div data-repeater-item>
<div class="form-group row">
<div class="col-md-5">
<label class="form-label">Question:</label>
<input type="text" class="form-control form-control-solid mb-5" placeholder="Enter question..." name = "survey[questions][name]"/>
</div>
<div class="col-md-5">
<label class="form-label">Answer Type:</label>
<select class = 'form-select form-select-solid' placeholder = "Choose a question type..." id = "questionType" name = "survey[questions][type]">
<option disabled selected>Choose a question type</option>
<option value = "STANDARD">Open-Ended</option>
<option value = "BOOLEAN">Yes/No Question</option>
<option value = "RATING">Rating</option>
<option value = "SLIDER">Slider</option>
<option value = "SINGLE" id = "additionalSurveyOptions">Single Choice</option>
<option value = "MULTIPLE" id = "additionalSurveyOptions">Multiple Choice</option>
<option value = "NUMBER">Number Input</option>
</select>
</div>
<div class="col-md-2">
<a href="javascript:;" data-repeater-delete class="btn btn-light-danger mt-3 mt-md-8 w-100">
<i class="la la-trash-o"></i>Delete
</a>
</div>
</div>
<div class="inner-repeater mb-5">
<div class = "row">
<div class = "col-7 offset-5">
<div data-repeater-list="choices">
<div data-repeater-item>
<label class="form-label">Choice:</label>
<div class="input-group pb-1">
<input type="text" class="form-control form-control-solid" placeholder="Enter choice here..." name = "choice"/>
<button class="border border-secondary btn btn-icon btn-light-danger" data-repeater-delete type="button">
<i class="la la-trash-o fs-3"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class = "row">
<div class = "col-7 offset-5">
<button class="btn btn-light-primary mt-3 mt-md-8 w-100" data-repeater-create type="button">
<i class="la la-plus"></i> Add a Choice
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!--end::Form group-->
</div>
<div class = "card-footer">
<a href="javascript:;" data-repeater-create class="btn btn-light-primary w-100">
<i class="la la-plus"></i>Add a Question
</a>
</div>
</div>
</div>
<!--end::Repeater-->
JAVASCRIPT
<!-- Tools :: Repeater (Begin) -->
<script>
$('#survey').repeater({
repeaters: [{
selector: '.inner-repeater',
show: function () {
$(this).slideDown();
},
hide: function (deleteElement) {
$(this).slideUp(deleteElement);
}
}],
show: function () {
$(this).slideDown();
},
hide: function (deleteElement) {
$(this).slideUp(deleteElement);
}
});
</script>
<!-- Tools :: Repeater (End) -->
I'm not 100% sure where to start
So I guess to summarise:
How do I hide / show the inner repeater based on the selected option in the outer-repeater
Thanks
I have tried wrapping the repeaters in an if statement (if this.value === "SINGLE") ...
repeaters: [{
selector: '.inner-repeater',
show: function () {
$(this).slideDown();
},
hide: function (deleteElement) {
$(this).slideUp(deleteElement);
}
}],
No luck
I tried setting the display visibility to hidden on the main div itself, then when the input value is SINGLE / MULTIPLE display the inner block... this worked (but it showed the option for every repeated form input) - I just want it to show on the element with the option selected
How to validate duplicate OwnerId in formArray. I am trying install this #rxweb/reactive-form-validators but it not working. This my demo code Stackblitz
HTML:
<div class="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 d-flex" [formGroupName]="i"
*ngFor="let driver of nameDriverControl.controls; let i=index">
<label>{{i+1}}.Name</label>
<input class="form-control" type="text" id="name" name="name{{i}}" formControlName="name" ><br/>
<label>{{i+1}}.Owner Id</label>
<input class="form-control" type="text" id="ownerId" name="ownerId{{i}}" inputmode="numeric" dashFormat formControlName="ownerId" maxLength="14">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<div class="form-group mb-0" *ngIf="i !== 0">
<button class="btn btn-danger" (click)="removeDriver(i)">
Delete
</button>
</div>
<div class="form-group mb-2">
<button *ngIf="i >= 0 && i < 1" type="button" [hidden]="nameDriver.length > 3" (click)="addDriver()" class="btn btn-primary">
Add
</button>
</div>
</div>
</div>
Component
createDriver () {
return new FormGroup({
name: new FormControl(null, Validators.required),
ownerId: new FormControl(null, Validators.required)
})
}
You can use unique validator of last version of #rxweb/reactive-form-validators (2.1.3).
After installation you need to import #rxweb like this:
import { RxwebValidators } from '#rxweb/reactive-form-validators';
Then in your createDriver methods add unique validator for ownerId like this:
createDriver () {
return new FormGroup({
name: new FormControl(null, Validators.required),
ownerId: new FormControl("", RxwebValidators.unique(
{ message: 'You must enter a unique OwnerId' }
))
})
}
And in your HTML template you can add error message like this:
<small class="form-text text-danger" *ngIf="driver.controls.ownerId.errors">{{driver.controls.ownerId.errors.unique.message}}<br/></small>
Here is working sample that I've created for you: StackBlitzLink
And the result:
See this for more information about unique validtion.
Is it possible to add dynamic text in the ng-model name?
data js
self.Map = [{ Id: 0, Name: 'Map1' }, { Id: 1, Name: 'Map2' }, { Id: 2, Name: 'Map3' }]
html
<div ng-repeat="option in mainCtrl.Map">
<div style="text-align:left;" class="col-md-6">
{{option.Name}} Map
</div>
<div style="text-align:right;" class="col-md-6">
<input type="text" id="Is{{option.Name}}" name="Is{{option.Name}}" ng-model="mainCtrl.Is{{option.Name}}"/>
</div>
</div>
desired output
ng-model="mainCtrl.IsMap1"
Refer to https://www.w3schools.com/js/js_objects.asp, you can access object in these 2 way:
objectName.propertyName
objectName["propertyName"]
You may try following way such that angularJs can bind the value you want:
<input type="text" id="Is{{option.Name}}" name="Is{{option.Name}}" ng-model="mainCtrl['Is'+option.Name]"/
Have you tried to add dummy field to your HTML input tag like
<div ng-repeat="option in mainCtrl.Map">
<div style="text-align:left;" class="col-md-6">
{{option.Name}} Map
</div>
<div style="text-align:right;" class="col-md-6">
<input type="text" id="Is{{option.Name}}" name="Is{{option.Name}}" ng-model="mainCtrl[this.getAttribute('dummyfield')]" dummyfield="{{option.Name}}Role" />
</div>
</div>
I am working in Angular project and I am creating form using Reactive approach. I want to create repeater control in child component. Please find the code below.
MainComponent.html
<div [formGroup]="myForm">
<input type="text" class="form-control" placeholder="Name"
formControlName="name" />
<div formArrayName="measurements">
<div *ngFor="let ctrl of myForm.get('measurements').controls; let i=index">
<div [formGroupName]="i">
<measurement-edit [measurementForm]="myForm.controls.measurements.controls[i]">
</measurement-edit>
</div>
</div>
</div>
<div formArrayName="thresholds">
<div *ngFor="let ctrl of myForm.get('thresholds').controls; let i = index">
<div [formGroupName]="i">
<threshold-edit
[thresholdForm]="myForm.controls.thresholds.controls[i]"
[valueType]="myForm.controls.valueType.value">
</threshold-edit>
</div>
</div>
</div>
</div>
ThresholdEditComponent.html
<div [formGroup]="thresholdForm">
<input type="checkbox"
[value]="isThresholdRequired"
formControlName="isThresholdRequired">
<!-- Here I want to use repeater controls and these controls should be accessed from parent (main) commponent -->
<div formArrayName="redThresholdItems">
<div *ngFor="let itemRow of thresholdForm.get('redThresholdItems').controls; let j = index;">
<div [formGroupName]="j">
<input type="text"
class="form-control txt-thr-val-width"
formControlName="thresholdValue1" />
</div>
</div>
</div>
</div>
MainComponent.ts
ngOnInit(): void {
this.createForm();
}
createMetricForm(): void {
this.myForm = this.fb.group({
name: ['', Validators.required],
easurements: this.fb.array([
this.initMeasurements()
]),
thresholds: this.fb.array([
this.initThresholds()
])
});}
initMeasurements(): FormGroup {
return this.fb.group({
isDerivedMetric: [false],
baselineMetricId: [''],
metricCalculation: [''],
metricSelection: ['']
});}
initThresholds(): FormGroup {
return this.fb.group({
isThresholdRequired: [false],
redThresholdItems: this.fb.array([
this.initThresholdRatings()
])
});}
initThresholdRatings(): FormGroup {
return this.fb.group({
thresholdValue1Operator: ['-1'],
thresholdValue1: [''],
compoundOperator: ['-1'],
thresholdValue2Operator: ['-1'],
thresholdValue2: ['']
});}
Please help me to create repeater controls in child component using the above code.
I am trying to compare values coming from database to the value I am entering on my angular 2 model driven form. I want to display a div when the values are not equal. I am trying the logic below but I am unable to make it work. Help will be appreciated.
View
<form [formGroup]="reguserform" #f="ngForm" (ngSubmit)="register()">
<fieldset>
<div class="form-group">
<label for="Username">Username</label>
<input class="form-control"
[(ngModel)]="user.Username"
type="text" id="Username"
formControlName="Username" />
</div>
<div class="alert alert-danger"
*ngIf="reguserform.controls.Username.touched && reguserform.controls.Username.errors">
<div *ngIf="reguserform.controls.Username.errors.required"
class="alert alert-danger">
Please enter a valid Username...
</div>
<div *ngFor="let r of rusers">
<div *ngIf="r.Username == user.Username" class="alert alert-danger">Username is taken</div>
</div>
</div>
</fieldset>
Component
getUsers() {
this.authenticationService.getRegUser().subscribe(
res => this.rusers = res
);
}
Everything is fine from the db end the object is being logged in console too but at the time of comparison no div is shown.
Your idea works in general, but that *ngIf="reguserform.controls.Username.touched && reguserform.controls.Username.errors" isn't true!
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<form [formGroup]="reguserform" #f="ngForm" (ngSubmit)="register()">
<fieldset>
<div class="form-group">
<label for="Username">Username</label>
<input class="form-control"
[(ngModel)]="user.Username"
type="text" id="Username"
formControlName="Username" />
</div>
<div class="alert alert-danger">
<div *ngFor="let r of rusers">
<div *ngIf="r.Username == user.Username" class="alert alert-danger">Username is taken</div>
</div>
</div>
</fieldset>
</form>
</div>
`,
})
export class App {
public reguserform: FormGroup; // our model driven form
user = {};
rusers = [
{ Username: 'mxii' },
{ Username: 'test' },
{ Username: 'peter' }
];
constructor(private _fb: FormBuilder) {
this.name = 'Angular2'
}
ngOnInit() {
this.reguserform = this._fb.group({
Username: ['', [<any>Validators.required, <any>Validators.minLength(1)]]
});
}
}
See my live-demo: https://plnkr.co/edit/SnHfoAL2dnuwKkrFYGzE?p=preview
When you write res => this.rusers = res I guess you put the http response in your variable. It's not an iterable object. Maybe you need to write this :
getUsers() {
this.rusers = this.authenticationService.getRegUser().map(
res => res.json()
);
}