Angular 2 - how to clean array in form - arrays

I need help.
I create app in Angular2(4) where I have Form with 2 select boxes and 1 checkbox. And I want to achieve dependence this boxes. For example I have this array:
dataJSON = [
{
productName: 'Product 1',
variants: [
{
variantName: 'Variant1',
variantType: [
'Type1', 'Type2', 'Type3'
]
},
{
variantName: 'Variant2',
variantType: [
'Type1', 'Type2', 'Type3'
]
},
{
variantName: 'Variant3',
variantType: [
'Type1', 'Type2', 'Type3'
]
},
]
},
{
productName: 'Product 2',
variants: [
{
variantName: 'Variant1',
variantType: [
'Type1', 'Type2', 'Type3', 'Type4'
]
},
{
variantName: 'Variant2',
variantType: [
'Type1', 'Type2', 'Type3'
]
}
]
},
{
productName: 'Product 3',
variants: [
{
variantName: 'Variant1',
variantType: [
'Type1', 'Type2', 'Type3'
]
},
{
variantName: 'Variant2',
variantType: [
'Type1', 'Type2', 'Type3'
]
},
{
variantName: 'Variant3',
variantType: [
'Type1', 'Type2', 'Type3'
]
},
{
variantName: 'Variant4',
variantType: [
'Type1', 'Type2', 'Type3', 'Type4'
]
}
]
}
];
So when I in first select box choose 'Product 1' then in second select box I will have options: 'Variant1', 'Variant2', 'Variant3'. Then when I choose 'Variant1' I will have in checkboxes options: 'Type1', 'Type2', 'Type3'.
When I choose everithing I want save what and base on that make request to API for data.
What I have now.
<form [formGroup]="productForm" (ngSubmit)="submitForm()">
<div class="form-group row">
<div class="col-md-4 col-sm-4 col-lg-4">
<label>Product</label>
<select formControlName="productName" (change)="productChanged()" class="form-control">
<option >Pick a Product...</option>
<option *ngFor="let l of dataJSON">{{l.productName}}</option>
</select>
</div>
<label>Variant</label>
<div class="col-md-4 col-sm-4 col-lg-4">
<select formControlName="variants" (change)="variantsChanged()" class="form-control">
<ng-template ngFor let-variant [ngForOf]="(variantAfterChangeEvent)">
<option>Pick a variant...</option>
<option *ngFor="let v of variant.variants">{{v.variantName}}</option>
</ng-template>
</select>
</div>
<div class="col-md-4 col-sm-4 col-lg-4">
<label>Type</label>
<ng-template ngFor let-type [ngForOf]="(typeAfterChangeEvent)">
<div *ngFor="let t of type[0].variantType">
<input type="checkbox" class="minimal" (change)="onChange(t, $event.target.checked)"> {{t}}
</div>
</ng-template>
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
variantAfterChangeEvent: any[];
typeAfterChangeEvent: any[];
productForm: any;
constructor(private fb: FormBuilder) {
this.productForm = fb.group({
productName: [],
variants: [],
type: this.fb.array([])
});
}
productChanged() {
const productName = this.productForm.get('productName').value;
this.variantAfterChangeEvent = this.dataJSON.filter(s = > s.productName);
}
variantsChanged() {
const variants = this.productForm.get('variants').value;
this.typeAfterChangeEvent = this.variantAfterChangeEvent
.filter((element) =>
element.variants.some((subElement) => subElement.variantName === variants))
.map(element => {
const newElt = Object.assign({}, element ); // copies element
return newElt.variants.filter(subElement => subElement.variantName === variants);
});
}
onChange(type: string, isChecked: boolean) {
const typeFromArray = <FormArray>this.productForm.controls.type;
if (isChecked) {
typeFromArray.push(new FormControl(type));
} else {
const index = typeFromArray.controls.findIndex(x => x.value === type)
typeFromArray.removeAt(index);
}
console.log('onChange() ' + JSON.stringify(this.productForm.value));
}
submitForm() {
console.log('submitForm ' + JSON.stringify(this.productForm.value));
}
But now when I choose Product -> Variant -> Type and than before Submit I want change Product and choose something else different Product -> Variant -> Type and then Submit I get what I choose as last but in 'type' I have also what I choose first time. So how can I clean 'type' array in productForm when I change somenting in Form = Product or Variant.
And also how can I disable Submit Form Button when not all items(boxes) are selected?
EDIT:
Plnkr

It would be better if you could create a plunker.
Based on what I understood, you can get ngModel from each control and clear other control's value on change.
To disable the form until all values are selected, add required property in each control and add [disabled]="!productForm.valid" in submit button

Related

change the value of component with function

I am kinda new to reactjs I am using the react-dropdown library https://github.com/fraserxu/react-dropdown and I am trying to make a dropdown menu so my user can switch between 2 languages. However, I am not sure how to properly update the new value for language.
Here's my code:
const Navbar = () => {
const languages = [
{
code: 'fr',
name: 'Français',
country_code: 'fr',
key : 1
},
{
code: 'en',
name: 'English',
country_code: 'gb',
key: 2
},
]
const defaultLanguages = languages[0].name;
const changeLanguage = (e) => {
console.log(e)
}
return (
<div className={color ? 'header header-bg' : 'header'}>
<ul>
<li>
<DropDown options={languages} value={defaultLanguages} onChange={(e) => changeLanguage} />
</li>
</ul>
</div>
)
}
export default Navbar
as you can see I want to switch between french and english but I am not sure how to pass the value to the dropdown component.
You need to use the same attributes in your options (languages) passed to the Dropdown component. You can see the examples of both flag options and object options on the official repo:
//Options
//Flat Array options
const options = [
'one', 'two', 'three'
];
//Object Array options
const options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two', className: 'myOptionClassName' },
{
type: 'group', name: 'group1', items: [
{ value: 'three', label: 'Three', className: 'myOptionClassName' },
{ value: 'four', label: 'Four' }
]
},
{
type: 'group', name: 'group2', items: [
{ value: 'five', label: 'Five' },
{ value: 'six', label: 'Six' }
]
}
];
Below code worked on my side:
import Dropdown from 'react-dropdown';
const Navbar = () => {
const languages = [
{
value: 'fr',
label: 'Français',
},
{
value: 'en',
label: 'English',
country_code: 'gb',
},
];
const defaultLanguages = languages[0].label;
const changeLanguage = (e) => {
console.log(e);
};
return (
<div className={'header'}>
<ul>
<li>
<Dropdown
options={languages}
value={defaultLanguages}
onChange={changeLanguage}
/>
</li>
</ul>
</div>
);
};
export default Navbar;
enter image description here
Create local state for tracking currently selected language. Also move out languages array outside of component. Here is the code:
const languages = [
{
code: 'fr',
name: 'Français',
country_code: 'fr',
key : 1
},
{
code: 'en',
name: 'English',
country_code: 'gb',
key: 2
},
]
const Navbar = () => {
const [selectedLanguage, setSelectedLanguage] = useState(languages[0].name);
const changeLanguage = (option) => {
setSelectedLanguage(option.name)
}
return (
<div className={color ? 'header header-bg' : 'header'}>
<ul>
<li>
<DropDown options={languages} value={selectedLanguage} onChange={changeLanguage} />
</li>
</ul>
</div>
)
}
export default Navbar

update react state of nested object with array

I want to insert a new object inside the options which is an array property inside the select1 object every time the user clicks on add button, how can I achieve this target, pls reply to this query.
I want to build a simple application where a user enters the options in the select box and can apply conditions based upon the selected option.
const [select1, setSelect1] = useState({
id: uuid(),
type: 'select',
properties: {
label: 'Select1',
options: [
{
id: uuid(),
label: 'text1',
value: {
labelTxt: 'text1',
childOptions: [],
},
},
{
id: uuid(),
label: 'text2',
value: {
labelTxt: 'text2',
childOptions: [],
},
},
],
},
parentId: null,
});
function addOptionsVal() {
setSelect1((prevState) => {
const options = [...prevState.properties.options];
options.splice(1, 0, {
id: uuid(),
label: optionVal,
value: {
labelTxt: optionVal,
childOptions: [],
},
});
console.log(options);
return { ...prevState, options };
});
}
return (
<div>
<select name="" id="">
<option value="">--select--</option>
{select1.properties.options.map((option) => {
return <option>{option.label}</option>;
})}
</select>
</div>
<input
type="text"
value={optionVal}
onChange={(e) => handleValueChange(e)}
/>
<button onClick={addOptionsVal}>Add options</button>
</div>
<div>
<input
type="checkbox"
value="male"
checked={checked}
onChange={toggleCondition}
id="condition"
/>
<label htmlFor="condition">Apply condition</label>
</div>
)
This is the entire code file in case you need
Try this:
function addOptionsVal() {
setSelect1((prevState) => {
return {
...prevState,
properties: {
...prevState.properties,
options: [
...prevState.properties.options,
{
id: uuid(),
label: optionVal,
value: {
labelTxt: optionVal,
childOptions: [],
},
},
],
},
};
});
}
stackblitz
Also, take a look at this question Correct modification of state arrays in React.js

How to clear a select field when another select field changes with react-select

So I have 2 select fields. Then first select field is for categories; The other one is for subcategories. When the first field changes, the second field changes the list of options to choose, but the selected value of the second select field is still there. So I was wondering if there's a way to deselect the second value, when the first value changes.
Code:
import { useEffect, useState } from 'react';
// import categoryOptions from './categoryOptions';
import subcategoryOptions from './subcategoryOptions.json';
import Select from 'react-select';
const Info = ({
register,
errors,
product,
setValue,
getValues,
watchAllFields,
formStep,
unregister,
}) => {
const categories = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
const [selectedCategory, setSelectedCategory] = useState(null);
const [selectedSubcategory, setSelectedSubcategory] = useState(null);
const [subcategoryArray, setSubcategoryArray] = useState();
const [isChanged, setIsChanged] = useState(false);
useEffect(() => {
setSelectedSubcategory(null);
if (selectedCategory) {
const foundSubcategory = subcategories.filter(
(item) => item.category === selectedCategory.value
);
if (foundSubcategory) {
console.log(foundSubcategory);
setSubcategoryArray(foundSubcategory);
}
}
setIsChanged(true);
}, [selectedCategory]);
const subcategories = [
{ value: '', label: '⠀' },
{ value: 'eee', label: 'Chocolate', category: 'chocolate' },
{ value: 'e', label: 'zre', category: 'chocolate' },
{ value: 'es', label: 'Chooo', category: 'chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
return (
<section className='px-10'>
<div className='flex flex-col'>
<label htmlFor='title' className='pb-5 text-2xl text-white'>
Title
</label>
<input
type='text'
name='title'
className='text-white bg-indigo-900 border-indigo-900 input focus:bg-indigo-500'
placeholder='Try something creative (Required)'
maxLength={30}
{...register('title', {
required: {
value: true,
message: 'Title is required!',
},
})}
/>
{errors.title && (
<p className='mt-2 text-sm text-yellow-500'>{errors.title.message}</p>
)}
<h1 className='pt-10 pb-5 text-2xl text-white'>Gig Requierments</h1>
<textarea
type='text'
name='Requirements'
className='h-56 text-white bg-indigo-900 border-indigo-900 input focus:bg-indigo-500'
{...register('requirements')}
></textarea>
<h1 className='pt-10 pb-5 text-2xl text-white'>Category</h1>
<Select
defaultValue={selectedCategory}
onChange={setSelectedCategory}
options={categories}
/>
<Select
isClearable
defaultValue={selectedSubcategory}
onChange={setSelectedSubcategory}
options={subcategoryArray}
/>
</div>
</section>
);
};
export default Info;
Any help would be appreciated. Thanks.
Just pass a custom handler to the first Select that resets the second Select's value, if the currently selected subcategory is not in the new category:
const isSubcategory = (subcategory, category) => { /* returns true if subcategory is a subcategory of category */ }
const handleCategoryChange = useCallback(value => {
if (!isSubcategory(selectedSubcategory, value)) {
setSelectedSubcategory(null);
}
setSelectedCategory(value);
}, [selectedSubcategory])
<Select
isClearable
defaultValue={selectedCategory}
onChange={handleCategoryChange}
options={subcategoryArray}
/>
If the selected subcategory is never a subcategory of another category you can even skip the check.
The effect is not required. It all depends on local state changes.

Why ngFor with filter pipe cause infinite loop?

i have this situation.
CheckboxesComponent
<section
*ngFor="let group of model.groups | myfilter:term; let index = index">
<div>
<mat-checkbox
[checked]="group.allChecked"
[ngModel]="group.allChecked"
color="primary">
{{ group.name }}
</mat-checkbox>
</div>
<div>
<ul>
<li *ngFor="let checkbox of groups.checkboxes;">
<mat-checkbox
[checked]="checkbox.check"
[(ngModel)]="checkbox.check"
color="primary">
{{ checkbox.displayName }}
</mat-checkbox>
</li>
</ul>
</div>
</section>
In a second component i have
<mat-form-field
appearance="outline">
<mat-label>
Search by
</mat-label>
<input
matInput
type="text"
[(ngModel)]="filter">
<mat-icon matPrefix fontIcon="icon-search"></mat-icon>
</mat-form-field>
<app-checkbox-group
[datasource]="claims"
[term]="filter"
>
</app-checkbox>
and this pipe
#Pipe({
name: 'roleFilter',
pure: false
})
export class myfilter implements PipeTransform {
transform(groups: [], term: string): [] {
if (!term) {
return groups;
}
return groups
.map(obj => {
return Object.assign({}, obj, {
checkboxes: obj.checkboxes.filter(el => el.displayName.includes(term))
})
})
.filter(obj => obj.checkboxes.length > 0)
}
}
and groups is a dataset like this
[
{
name: 'Group 1',
allChecked: false,
isIndeterminate: false,
checkboxes: [
{
check: true,
displayName: 'first',
name: 'first',
id: '1',
isMandatory: false
},
{
check: false,
displayName: 'second',
name: 'second',
id: '2',
isMandatory: false
},
{
check: false,
displayName: 'third',
name: 'third',
id: '3',
isMandatory: false
},
{
check: false,
displayName: 'fourth',
name: 'fourth',
id: '4',
isMandatory: false
},
{
check: false,
displayName: 'fifth',
name: 'fifth',
id: '5',
isMandatory: false
},
{
check: false,
displayName: 'sixth',
name: 'sixth',
id: '6',
isMandatory: false
},
{
check: false,
displayName: 'seventh',
name: 'seventh',
id: '7',
isMandatory: false
},
{
check: false,
displayName: 'eighth',
name: 'eighth',
id: '8',
isMandatory: false
},
]
}
]
When i start typing on the input filter, to reduce the dataset, if i start typing with a letter that not match with any displayName all groups are hidden and it's work like expected.
The problem appears when i start typing with a letter that match with some of the displayName property of the checkboxes.
I don't understand why but all page freeze and the map function is called infinite times.
The problem seem to be in
Object.assign in the map method. Because if i change this line with
obj.checkboxes = checkboxes: obj.checkboxes.filter(el => el.displayName.includes(term)) it works but replace the original checkboxes with the filtered one.
In accordion to #Andrei comment
try to add trackByIndex(idx) {return idx;} method and pass it to both ngFor insatnces like this *ngFor="let checkbox of groups.checkboxes; trackBy: trackByIndex"
The solution was using the trackBy pipe on both *ngFor directives.

Issues formly with Angular Material modal

I would like to show the forms in a modal using the Angle Material, but simply the controller is ignored.
Outside the modal, it operates normally, only the modal giving this problem ..
What did I doing wrong?
Any idea doque do?
angular.module('adminApp.forms', ['formly', 'formlyBootstrap', 'adminApp.services'], function config(formlyConfigProvider) {
formlyConfigProvider.setType({
name : 'repeatSection',
templateUrl : 'repeatSection.html',
controller : function($scope) {
$scope.formOptions = {formState: $scope.formState};
$scope.addNew = addNew;
$scope.copyFields = copyFields;
function copyFields(fields) {
return angular.copy(fields);
}
function addNew() {
$scope.model[$scope.options.key] = $scope.model[$scope.options.key] || [];
var repeatsection = $scope.model[$scope.options.key];
var lastSection = repeatsection[repeatsection.length - 1];
var newsection = {};
// if (lastSection) {
// newsection = angular.copy(lastSection);
// }
repeatsection.push(newsection);
}
}
});
})
.controller('FormsController', function($scope, $mdDialog){
$scope.addField = function(ev){
$mdDialog.show({
parent : angular.element(document.body),
controller : FormsBuilder,
ariaLabel : 'Adicionar Campo',
targetEvent : ev,
clickOutsideToClose : true,
templateUrl : 'partials/tpl/fields.html',
})
function FormsBuilder($scope, FIELDS) {
console.log('start!')
var vm = this;
// funcation assignment
vm.onSubmit = onSubmit;
init();
//vm.originalFields = angular.copy(vm.fields);
// function definition
function onSubmit() {
console.log('submit!');
return
$scope.field = new FIELDS(vm.model);
$scope.field.$save(function(){
$scope.showToast('Field Group ....Criado!');
});
}
function init() {
console.log('init!')
vm.model = {
fields: []
};
vm.fields = [
{
"key": "fieldGroup-name",
"type": "input",
"templateOptions": {
"placeholder": "Nome do Grupo",
"label": "One add-on on the left (icon)"
}
},
{
type: 'repeatSection',
key: 'fields',
templateOptions: {
btnText:'Adicionar novo campo',
fields: [
{
className: 'row',
fieldGroup: [
{
className: 'col-xs-4',
type: 'input',
key: 'fieldName',
templateOptions: {
label: 'Nome do campo:'
}
},
{
type: 'input',
key: 'fieldSlug',
className: 'col-xs-4',
templateOptions: {
label: 'Slug do campo:'
}
}
]
},
{
"type": "select",
"key": "Fieldtype",
"templateOptions": {
"label": "Field Type",
"required": true,
"options": [
{
"name": "Text Field",
"value": "input"
},
{
"name": "TextArea Field",
"value": "textarea"
},
{
"name": "Radio Buttons",
"value": "radio"
},
{
"name": "Checkbox",
"value": "checkbox"
}
]
}
},
{
type: 'checkbox',
model: 'formState',
key: 'selfExecuting',
templateOptions: {
label: 'Are you executing this trade?'
}
},
{
hideExpression: '!formState.selfExecuting',
fieldGroup: [
{
type: 'input',
key: 'relationshipName',
templateOptions: {
label: 'Name:'
}
},
{
type: 'select',
key: 'complianceApprover',
templateOptions: {
label: 'Compliance Approver:',
options: [
{
name: 'approver 1',
value:'some one 1'
},
{
name: 'approver 2',
value:'some one 2'
}
]
}
},
{
type: 'textarea',
key: 'requestorComment',
templateOptions: {
label: 'Requestor Comment',
rows: 4
}
}
]
}
]
}
}
];
}
}
};
});
Modal template:
<md-dialog aria-label="Adicionar Campo">
<md-dialog-content>
<h1>Nome do Grupo: {{vm.exampleTitle}}</h1>
<form ng-submit="vm.onSubmit()" novalidate>
<formly-form model="vm.model" fields="vm.fields" form="vm.form">
<md-button type="submit" class="md-raised md-primary">Salvar</md-button>
</formly-form>
</form>
</md-dialog-content>
</md-dialog>
<script type="text/ng-template" id="repeatSection.html">
<div>
<div class="{{hideRepeat}}">
<div class="repeatsection" ng-repeat="element in model[options.key]" ng-init="fields = copyFields(to.fields)">
<formly-form fields="fields" model="element" bind-name="'formly_ng_repeat' + index + $parent.$index">
</formly-form>
<div style="margin-bottom:20px;">
<md-button type="submit" class="md-raised md-primary" ng-click="model[options.key].splice($index, 1)">Remover campo</md-button>
</div>
<hr>
</div>
<md-button type="button" class="md-raised md-primary" ng-click="addNew()" >{{to.btnText}}</md-button>
</div>
</script>
RESOLVED:
$mdDialog.show({
parent : angular.element(document.body),
controller : FormsBuilder,
controllerAs : 'vm',
ariaLabel : 'Adicionar Campo',
targetEvent : ev,
clickOutsideToClose : true,
templateUrl : 'partials/tpl/fields.html',
})

Resources