Extending Angular 2 dynamic forms to hide fields for certain values if conditions are met - forms

I was following the example given on Dynamic Forms from the Angular 2 documentation (https://angular.io/docs/ts/latest/cookbook/dynamic-form.html). In this example you are shown how to create forms dynamically, based on metadata that describes the business object model.
This roughly looks as follows:
I have a YesNoQuestion class that describes how my questions are formatted.
import { QuestionBase } from './question-base';
export class YesNoQuestion extends QuestionBase<string> {
controlType = 'radio';
options: {key: string, value: string}[] = [];
constructor(options: {} = {}) {
super(options);
this.options = [
{key: 'true', value: 'Yes'},
{key: 'false', value: 'No'}
];
}
}
Each form is generated in my dynamic-form.component:
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let question of questions" class="form-group">
<df-question [question]="question" [form]="form"></df-question>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>
The dynamic-form calls the dynamic-form-question for each single question dynamic-form-question.component:
<div [formGroup]="form">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<div class="form-group" *ngSwitchCase="'radio'">
<div *ngFor="let opt of question.options">
<input type="radio" [formControlName]="question.key" [value]="opt.key" /> {{opt.value}}
</div>
</div>
... Some extra form types ...
</div>
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>
Now I can generate a form by simply inserting a questions object like the following:
let questions: QuestionBase<any>[] = [
new YesNoQuestion({
key: 'a',
label: 'Label A',
}),
new YesNoQuestion({
key: 'b',
label: 'Label B',
}),
new YesNoQuestion({
key: 'c',
label: 'Label C',
}),
new TextareaQuestion({
key: 'd',
label: 'Label D',
}),
Now I am looking for a way to let these question be dependent of each other. I for example only want to see question A, and if yes is answered for question A I want to show question B. If no is answered I want to show question C.
Would something like that be implementable in my current approach? I haven't found a good tactic yet, I tried to add an attribute hide but didn't know how to reference other field values.

Related

Data binding checkboxes in svelte

in a svelte/sapper form I want to send data to a user model
<input type="text" bind:value={user.label}>
..works as expected. But how to send user.offer_ids
let offer_ids = [];
{#each offers as offer}
<label>
<input type=checkbox bind:group={offer_ids} value="{offer.id}">{offer.label}
</label>
{/each}
I can't find any svelte sample how to do this job, which I think is basic in forms.. Thanks for help
May be it was unclear..I want to send data to Json:
<form>
<input type="text" bind:value={user.label}> //gives data to 'user'
{#each offers as offer}
<label>
<input type=checkbox bind:group={offer_ids} value="{offer.id}">{offer.label}
</label>
{/each}
//how to get all selected ids to 'user'
</form>
Hope this is more clear now.
With checkboxes, we bind to checked instead of value:
<script>
let name = 'world';
let flag = false
</script>
<style>
.highlighted {
color: red
}
</style>
<h1 class:highlighted={flag}>Hello {name}!</h1>
<label for="checkbox">Highlight header</label>
<input id="checkbox" type='checkbox' bind:checked={flag}/>
Edit
The offer model should have a selected or checked state. Then you can bind the checkbox to that:
<script>
let name = 'world';
let offers = [
{id: 1, name: 'One', checked: false},
{id: 2, name: 'Two', checked: false},
{id: 3, name: 'Three', checked: false},
]
</script>
{#each offers as offer, index (offer.id)}
<label for={'checkbox'+offer.id}>{offer.name}</label>
<input id={'checkbox'+offer.id} bind:checked={offer.checked} type='checkbox'/>
{/each}
<div>
{offers.filter(offer => offer.checked).map(offer => offer.name)}
</div>
Here, we display three checkboxes and the text below prints the names of the selected offers on the screen.
REPL: https://svelte.dev/repl/461fea432dc54ed0a744afc4e05bf34b?version=3.22.3
so my solution now:
let offer_ids = [];
<form>
{#each offers as offer, i (offer.id)}
<label>
<input type=checkbox bind:group={offer_ids} value="{offer.id}">{offer.label}
</label><br/>
{/each}
</form>
enter code here
and in the function...
async function publish() {
user.offer_ids = offer_ids
const response = await (api.post('users', {user}));

angular autocomplete with filter in form array in reactive forms

I have a problem, how to create one filtered autocomplete list and use it in different form array controls.
My form model:
this.prodForm = this.fb.group({
date: ['', Validators.required],
priority: ['', Validators.required],
products: this.fb.array([])
});
var product = this.fb.group({
name: '',
isTrial: '',
})
Autocomplete list:
this.filteredMachineProducts = this.productCtrl.valueChanges.
startWith(null).
map(name => this.filterMachineProducts(name));
filterMachineProducts(val: string) {
return val ? this.machineProducts.filter((s) => s.name.match(new RegExp(val, 'gi'))) : this.machineProducts;
}
And front:
<div formArrayName="products">
<div *ngFor="let product of products.controls; let p=index" [formGroupName]="p">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
Produkt #{{p + 1}}
<mat-panel-description>
<button mat-raised-button color="primary" type="button" (click)="removeProduct(p)">UsuĊ„</button>
</mat-panel-description>
</mat-panel-title>
</mat-expansion-panel-header>
<div fxLayout="row" fxLayoutWrap="wrap">
<div fxFlex.gt-sm="25" fxFlex.gt-xs="50" fxFlex="100">
<mat-form-field>
<input matInput placeholder="Asortyment" [matAutocomplete]="productAutoComplete" [formControl]="productCtrl">
</mat-form-field>
<mat-autocomplete #productAutoComplete="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let machineProduct of filteredMachineProducts | async" [value]="machineProduct" (onSelectionChange)="getProductMachine(machineProduct, p)">
<span>{{ machineProduct.name }}</span>
</mat-option>
</mat-autocomplete>
</div>
At this moment I use separate formControl but it should be formControlName="name".
Should I create filtered list for each autocomplete in loop?
Yes, You need to create a filtered list for each auto-complete.
I have answered a question of this sort here.

Angular 2 - Handling multiple checkboxes checked state

I'm building a registration form, and have to handle multiple checkboxes generated through *ngFor structural directive.
Template:
<div class="form-group">
<label class="col-lg-3 control-label">Response Type</label>
<div class="col-lg-7">
<div class="checkbox" *ngFor="let type of client.responseTypes; let i = index">
<label>
<input type="checkbox" name="responseTypes" [(ngModel)]="client.responseTypes[i].checked" /> {{type.value}}
</label>
</div>
</div>
</div>
TS model object:
client.responseTypes = [
{ value: 'type1', checked: false},
{ value: 'type2', checked: true },
{ value: 'type3', checked: true }
];
The actual values behind the scene checked or unchecked are stored correctly. But, the checkboxes do not show the correct value.
If one of the three objects.checked == false, all checkboxes will show unchecked
Only if all three objects.checked == true, all three will be checked in the form.
I tried fiddling with [value]="client.responseTypes[i].checked" and [checked]="client.responseTypes[i].checked" on the input element, but to no success.
Any pointers in the right direction are massively appreciated.
Regards, Chris
the syntax is ok, well, you can do too
<input type="checkbox" name="responseTypes" [(ngModel)]="type.checked">
the problem is in other place (when you defined the client -I think you write some like this.client.responseTypes=...-), you can write
{{client |json }}
to check it
Alright after trying to wrap my head around why my local application wasn't showing the result I wanted, I started to copy and paste my form-group out of the <form></form> element it was in.
Ta-daa, it works now.
FYI: Placing *ngFor checkboxes inside a <form></form> element results in unwanted behaviour.
In angular every model bind with name , means you need to give unique name to each Form elements`
For Example:
<div class="form-group"><label class="col-lg-3 control-label">Response Type</label>
<div class="col-lg-7">
<div class="checkbox" *ngFor="let type of client.responseTypes; let i = index">
<label>
<input type="checkbox" name="type.id" [(ngModel)]="client.responseTypes[i].checked" /> {{type.value}}
</label>
</div>
</div>
</div>
Object :
client.responseTypes = [{id: '1', value: 'type1', checked: false},
{id: '2', value: 'type2', checked: true },
{id: '3', value: 'type3', checked: true }];
And now Check :) Thank you

Why checkboxes don't work inside a form?

I have an objects array
pets = [{key: 'dog', isChecked: true}, {key: 'hamster', isChecked: false}, {key: 'cat', isChecked: false}];
and display it like checkboxes
<div *ngFor='let pet of pets'>
<input type='checkbox'
name='pets'
value='{{pet}}'
[(ngModel)]='pet.isChecked'
(change)='check()'
/>
{{pet.key}} - {{pet.isChecked}}
</div>
but as soon as I start putting it inside a form
<form>
<div *ngFor='let pet of pets'>
<input type='checkbox'
name='pets'
value='{{pet}}'
[(ngModel)]='pet.isChecked'
(change)='check()'
/>
{{pet.key}} - {{pet.isChecked}}
</div>
</form>
It stops displaying correctly.
How can I make this work with a form?
Plunkr link
and
Plunkr link with a form
HTML :
<form>
<div *ngFor='let pet of pets'>
<input type='checkbox'
name='pets'
id="pets{{getRandom()}}"
[ngModel]="pet.isChecked"
value='{{pet}}'
(click)="$event.stopPropagation(); check();"/>
{{pet.key}} - {{pet.isChecked}}
</div>
</form>
TS :
function getRandom() {
return Math.random() * 1000;
}

Angular2 log values after dynamically adding form fields

I am trying to console log the values of each object in the choices array. I am currently able to log the objects in the choices array but all of the values are empty. I am seeing timeZonePicker: "", startTimeInput: "", endTimeInput: "" for each object. I am able to add and remove from the choices array and log the key but I cannot log the value. I have tried a lot of different things but still unsuccessful.
<div class="container">
<div class="row">
<div class="col-md-9">
<div *ngFor="let choice of choices; trackBy=customTrackBy" class="form-inline">
<select [ngModel]="choice.timeZonePicker" class="form-control" id="timeZonePicker">
<option *ngFor="let timeZone of timeZones" [selected]="timeZone.chosenTimeZone == '(GMT) Western Europe Time, London, Lisbon, Casablanca, Greenwich'">{{ timeZone.chosenTimeZone }}</option>
</select>
<input [ngModel]="choice.startTimeInput" type="time" class="form-control" id="startTimeInput">
<input [ngModel]="choice.endTimeInput" type="time" class="form-control" id="endTimeInput">
</div> <!-- end form field div -->
<div class="container">
<button (click)="onSubmit()" class="btn btn-primary">Submit</button>
</div>
<div class="container">
<button class="pull-left btn btn-success" (click)="addNewChoice()">Add Field</button>
<button class="pull-left btn btn-danger" (click)="removeChoice()">Remove Field</button>
</div>
</div> <!-- end col-md-9 -->
</div> <!-- end row -->
</div> <!-- end container -->
Below is the component.
export class TimeZonesComponent {
constructor(){}
timeZones = [
{ val: -12, chosenTimeZone: '(GMT -12:00) Eniwetok, Kwajalein'},
{ val: -11, chosenTimeZone: '(GMT -11:00) Midway Island, Samoa'},....];
choices = [
{
timeZonePicker: '',
startTimeInput: '',
endTimeInput: ''
},
{
timeZonePicker: '',
startTimeInput: '',
endTimeInput: ''
}];
addNewChoice(){
var dataObj = {
timeZonePicker: '',
startTimeInput: '',
endTimeInput: ''
};
this.choices.push(dataObj);
}
removeChoice(){
var lastItem = this.choices.length - 1;
this.choices.splice(lastItem);
console.log(this.choices);
}
onSubmit(){
console.log(this.choices);
}
customTrackBy(index: number, obj: any){
return index;
}
}
I really appreciate any help.
I found out my error. I needed to use trackBy (which I wasn't initially) and [(ngModel]). I was only using one way binding but I needed two way. If anyone would like to see the code for learning, just comment and I will happily share it.