I have created a form which consist of JSON array and according to that, I am generating Validation,formControlName and generating output through formGroup.
this.ELEMENT_DATA_UPDATE = [
{
first_name : 'abc',
last_name : 'xyz',
phone : 8888888888
}
];
Here, I am using Angular material so converted this key value pair to another array consists of
{"key" : "first_name" , "value" : "abc" , "name" : "First name :"}
This is when the input JSON array is fixed. But my project consists of data manipulation on a large scale in which input JSON array contents will change many times. There is no problem in generating UI modules, but when I try to apply Validation and forms modules to this dynamically generated contents, whole flow collapse ,
this is my code
var jsonArray : any = [{}];
export class UpdateContactFieldDialog {
matcher = new MyErrorStateMatcher();
updateForm: FormGroup;
formString : string = null;
ELEMENT_DATA_UPDATE : any[] = [];
keyArray : any[] = [];
myJobFunctionControl = new FormControl();
optionsJobFunction: string[] = ['Operations', 'Production', 'Manufacturing'];
myTitleControl = new FormControl();
optionsTitle: string[] = ['HR', 'Developer', 'Tester', 'MD', 'CEO', 'Director'];
constructor(private formBuilder: FormBuilder,private dialogRef: MatDialogRef<AddContactFieldDialog> ) {
}
ngOnInit() {
//dumy randomly geneated fields
this.ELEMENT_DATA_UPDATE = [
{
first_name : 'abc',
last_name : 'xyz',
job_function : 'Production',
title : 'Developer',
email : 'abc#abx.com',
phone : 7945612304
}
];
for (let obj of this.ELEMENT_DATA_UPDATE) {
console.log("object length:", this.ELEMENT_DATA_UPDATE.length);
for (let key in obj) {
this.keyArray.push({'key' : key , 'value' : obj[key] , 'name': (key.charAt(0).toUpperCase() + key.substr(1).toLowerCase()).replace(/_/g, ' ')});
}
break;
}
console.log(this.keyArray);
console.log(this.ELEMENT_DATA_UPDATE[0]);
for(let formModule of this.keyArray){
var keyData = formModule.key;
if(this.formString != null && keyData == 'email' || keyData.includes('mail')){
this.formString = this.formString +''+keyData+':[this.ELEMENT_DATA_UPDATE[0].'+keyData +',[Validators.required,Validators.email]], ';
}
else
if(this.formString != null && keyData == 'phone' || keyData.includes('number') || keyData.includes('no') || keyData.includes('num') ){
this.formString = this.formString +''+keyData+':[this.ELEMENT_DATA_UPDATE[0].'+keyData +',[Validators.required,Validators.minLength(10),Validators.maxLength(10),Validators.pattern("[0-9]*")]], ';
}
else
if(this.formString == null && keyData != 'email' && keyData != 'phone'){
this.formString = ''+keyData+':[this.ELEMENT_DATA_UPDATE[0].'+keyData +',Validators.required], ';
}
else{
this.formString = this.formString + ''+keyData+':[this.ELEMENT_DATA_UPDATE[0].'+keyData +',Validators.required], ';
}
}
console.log(this.formString);
jsonArray = (this.formString);
this.updateForm = this.formBuilder.group(jsonArray);
}
// convenience getter for easy access to form fields
get f() { return this.updateForm.controls; }
submitContact() {
this.submitted = true;
// stop here if form is invalid
if (this.updateForm.valid) {
// alert('SUCCESS!! :-)\n\n' + JSON.stringify(this.updateForm.value))
console.log(this.updateForm.value);
this.dialogRef.close();
}
else{
return;
}
}
}
My code is generating following code as String
first_name: ['', Validators.required],
last_name: ['', Validators.required],
job_function: ['', [Validators.required]],
title: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]],
phone : ['',[Validators.required, Validators.minLength(10), Validators.maxLength(10), Validators.pattern('[0-9 ]*')]]
I want this to be used inside of formGroup so i can dynamically generate formControls , assign them validation and values.
updateForm = this.formBuilder.group(jsonArray);
Complementary my comment, NOTE:you need add the validators (I can't see them in your code)
this.updateForm=new FormGroup({}) //<--create the formGroup
for(let formModule of this.keyArray){
this.updateForm.addcontrol(formModule.key,new FormControl(formModule.Value))
}
Moreover, if our objects in keyArray was like
{ "key" : "key_name" ,
"value" : "value" ,
"name" : "Key name" ,
validators:[Validators.required, Validators.email]
}
We can improve our loop
for(let formModule of this.keyArray){
this.updateForm.addcontrol(formModule.key,
new FormControl(formModule.Value,formModule.validators))
}
Well, if difficult that our object becomes from a service like I showed, but it's possible our services serve us object like:
{ "key" : "key_name" ,
"value" : "value" ,
"name" : "Key name" ,
validators:[{type:"required"},{type="min",args:3}]
}
Before make the loop we can use
this.keyArray.forEach(x=>
{
if (x.validators)
{
conts validators=[];
validators.forEach(v=>{
switch (v.type)
{
case "required":
validators.push(Validators.required);
break;
case "min":
validators.push(Validators.min(v.arg);
break;
...
}
})
x.validators=validators;
}
}
About to show the errors we must take account that our controls in a form is
updateForm.get(keyArray.key)
So, e.g.
<div class="col-lg-6" *ngFor="let keyArray of keyArray">
<mat-form-field>
<input type="text" [formControlName]="keyArray.key" matInput
[placeholder]="keyArray.name"
[ngClass]="{ 'is-invalid': submitted && updateForm.get(keyArray.key).errors }"
autocomplete="off" />
<mat-error *ngIf="submitted && updateForm.get(keyArray.key).hasError('required')">
{{keyArray.name}} is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
Related
I have input where I user can search/type a data and I'm wondering how I can make the user ONLY able to search what was already provided from the backend and forbid them from creating new data.
so in my backend I've "Chart" and "Map" words and I figuring out a way to make the user able to search only this. If I user type other than this and press enter, nothing will happen.
Right now, if the user type other text than this two and press enter, it create a new data and push it to the backend.
I don't want to hard code like this (input == "Chart" || input == "Map") since we will be adding more data in the backend later.
super <= data type like "Chart and Map"
<div>
<input matInput #input [formControl]="tagCtrl" [matAutocomplete]="auto" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" (matChipInputTokenEnd)="add($event,null)">
</div>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let tag of filteredSuperTags | async" [value]="tag">
{{tag}}
</mat-option>
</mat-autocomplete>
tagCtrl = new FormControl();
superTags: Tag[] = [];
filteredSuperTags: Observable<String[]>;
allsuperTags: Array<Tag> = [];
allSuperTagNames: Array<String> = new Array<String>();
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
if (event1 == null) {
const input = event.input;
const value = event.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{this.superTags.push({ tag: value.trim(), type: TagType.super }); } }
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
else {
const input = event1.option;
const value = event1.option.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{this.superTags.push({ tag: value.trim(), type: TagType.super }); } }
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
}
any recommendation or help will be really appreciated.
Your backend was adding the option no matter what because you were calling the service before verifying if the value existed. If its a form, its super weird to call the backend everytime you select something in a typeahead. In my opinion it should be done once when everything is filled properly or on some kind of submit event.
I just moved the service call inside the verification and removed a if that was only used to assign the input and the value but was duplicating about 10 lines. Now you have an if assigning the value and then followed by the content of the previous if.
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value;
if (event1 === null) {
input = event.input;
value = event.value;
else {
input = event1.option;
value = event1.option.value;
}
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{
this.superTags.push({ tag: value.trim(), type: TagType.super });
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
}
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
I want to introduce a search option in my mat-select DropDown.I went through a lot of similar working options, but those are not working for me because of the object array I am passing to the Dropdown. any help is appreciated. thank you.
My Code
HTML File
<mat-form-field>
<mat-select (selectionChange)="getSubtier($event.value)">
<input (keyup)="onKey($event.target.value)">
<mat-option>None</mat-option>
<mat-option *ngFor="let state of selectedStates" [value]="state">{{state.name}}</mat-option>
</mat-select>
</mat-form-field>
TS File
states: string[] = [
{
toptier_agency_id: 15,
create_date: 1517428376464,
update_date: 1560547998012,
toptier_code: "013",
abbreviation: "DOC",
name: "Department of Commerce",
website: "https://www.commerce.gov/",
mapped_org_name: "COMMERCE, DEPARTMENT OF",
display_yn: "Y"
},
{
toptier_agency_id: 16,
create_date: 1517428376787,
update_date: 1560547999157,
toptier_code: "014",
abbreviation: "DOI",
name: "Department of the Interior",
website: "https://www.doi.gov/",
mapped_org_name: "INTERIOR, DEPARTMENT OF THE",
display_yn: "Y"
}];
selectedValue: string;
selectedStates = [];
ngOnInit() {
this.selectedStates = this.states;
}
onKey(value) {
this.selectedStates = this.search(value);
}
getSubtier(value) {
console.log(value);
}
search(value: string) {
// this.selectedStates=[]
// let filter = value.toLowerCase();
// this.selectedStates = this.selectedStates['name'].filter((unit) => unit.label.indexOf(value) > -1);
}
StackBliz Demo
Modify search functuon. Use filter and includes
Try like this:
onKey(value) {
this.selectedStates = this.search(value);
}
search(value: string) {
let filter = this.states.filter(item =>
item.name.toLowerCase().includes(value.toLowerCase())
);
return [...filter];
}
Working Demo
Try the following:
introduce filter with an isVisible property
search(value: string) {
return this.selectedStates.map(k => ({...k, isVisible:(k.name.toLowerCase().indexOf(value.toLowerCase()) > -1)}))
}
add a getter for the filtered state
get filteredState() {
return this.selectedStates.filter(k=> k.isVisible === undefined || k.isVisible)
}
then in your html replace selectedStates with filteredState
<mat-option *ngFor="let state of filteredState" [value]="state">{{state.name}}</mat-option>
TS Note: that you should replace states: string[] with states: any[] or with the object you are using
I want to serialize circular reference to JSON
This is the part generating the JSON array and it causes a circular reference because it creates a child inside an element and I want to display the result.
const mappedData = this.state.treeData
.filter(data => data.title === "Category")
.map(categ => {
const { id, title, children, subtitle, type } = categ;
function getChildren(children) {
return children
? children.map(child => {
if (child.title === "Item" || child.title === "Group") {
const data = {
id: child.id,
name: child.subtitle,
type: child.type,
children: getChildren(child.children),
child_id: child.children
? child.children.map(child => child.id)
: []
};
if (data.children.length === 0) delete data.children;
if (data.child_id.length === 0) delete data.child_id;
return data;
} else {
return {
id: child.id,
name: child.subtitle,
type: child.type
};
}
})
: [];
}
const data = {
id: id,
name: subtitle,
type: type,
children: getChildren(children),
child_id: children ? children.map(child => child.id) : []
};
if (data.children.length === 0) delete data.children;
if (data.child_id.length === 0) delete data.child_id;
return data;
});
The HTML part that calls the stringify method
<div className="json">
<p> {JSON.stringify(mappedData)}</p>
</div>
I found a Replacer that it works but the JSON result is too long for what I need
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
JSON.stringify(circularReference, getCircularReplacer());
And here's the result :
[{"id":"7a69fc68","name":{"type":"input","key":null,"ref":null,"props":{"className":"inputSubs","type":"text","placeholder":"Category Name"},"_owner":{"tag":1,"key":null,"stateNode":{"props":{},"context":{},"refs":{},"updater":{},"notificationAlert":{"current":{"props":{},"refs":{"notifications":{"__reactInternalInstance$6qqi1p3qi9b":{"tag":5,"key":null,"elementType":"div","type":"div","return":{"tag":1,"key":null,"return":{"tag":5,"key":null,"elementType":"div","type" .. etc
Any Help ?
I have one json file which contains multiple objects inside another object. I want to fetch data but not using key of that value. I want to iterate there key and values and want to print them dynamically in angular 6.
{
"name" : "abc",
"tags" : "def",
"updated-by" : "ijk",
"property" : {
"description" : "abcd",
"type" : "string"
},
"sources" : {
"input" : {
"type" : "lmn",
"properties" : {
"key" : "opq"
}
}
}
}
Can we iterate objects like we iterates array. If anyone can help?
I would suggest referring this StackOverflow question,
As far as I know, *ngFor can be used not only for arrays but also for Objects.
Hope the above link helps.
Also for the keys whose values contain objects, you could check if the corresponding value of the key is an object.
For instance,
if( (typeof A === "object") && (A !== null) )
where A is the corresponding value of the key. If A is indeed an object, use *ngFor again to iterate over the object.
I haven't tested the following code but I hope you get an overview of what I am trying to say,
#Component({
selector: 'app-myview',
template:
`<div *ngFor="let key of objectKeys(items)">{{key + ' : ' + items[key]}}
<div *ngIf="checkFunction(items[key])">
<div *ngFor="let key2 of objectKeys(items[key])">
{{key2 + ' :' + items[key][key2]}}
</div>
</div>
</div>`
})
export class MyComponent {
objectKeys = Object.keys;
items = { keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3' };
constructor(){}
checkFunction(obj){
if( (typeof obj === "object") && (obj !== null) )
{
return true;
}
else{
return false;
}
}
}
var temp = {
"name" : "abc",
"tags" : "def",
"updated-by" : "ijk",
"property" : {
"description" : "abcd",
"type" : "string"
},
"sources" : {
"input" : {
"type" : "lmn",
"properties" : {
"key" : "opq"
}
}
}
};
flatten the object
var flattenObject = function(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
};
var flat = flattenObject(temp)
Iterate over object just like array
Object.entries(flat).forEach(entry => {
console.log(entry[0] + " => " + entry[1])
})
I have 3 properties included JSON object array 'notes'.
$scope.notes = [
{
'type':'txt',
'name': 'JohnHenry',
'text':'Greeting',
}
];
My input field is
`<input type="text" placeholder="Text here..." ng-model="note.input" ng-list="," ng-enter="addnote()">`
I will type in below text into input textfield.
"txt-Glen-Negotiate Price, num-Phil-0939876, met-DrWalh-1505"
type property is to display icon. I wish to get as below JSON
$scope.notes = [
{
'type':'txt',
'name': 'JohnHenry',
'text':'Greeting',
},{
'type':'txt',
'name': 'Glen',
'text':'negotiate price',
},{
'type':'num',
'name': 'Phil',
'text':'0939876',
},{
'type':'met',
'name': 'DrWalh',
'text':'1505',
}
];
How to convert input ng-list text to JSON objects.
Write your own directive
app.directive('jsonConvert', function(){
return {
require: 'ngModel',
link: function (scope, elm, attrs, ngModel){
scope.$watch(
function(){
return ngModel.$modelValue;
}, function(newValue, oldValue){
var value = ngModel.$modelValue
if (value instanceof Array) return;
var valueArr = value ? value.split(',') : value;
if (!valueArr) return;
for (var i = 0; i < valueArr.length; i++){
if (valueArr[i]){
var splitItem = valueArr[i].split("-");
}
valueArr[i] = {
type: splitItem[0] ? splitItem[0] : '',
name: splitItem[1] ? splitItem[1] : '',
text: splitItem[2] ? splitItem[2] : ''
}
}
var result = valueArr;
ngModel.$setViewValue(result);
}, true);
}
}
})
Although the way you have defined what you want may not be scalable or best practise since it maps 0 to type, 1 to name and 2 to text
Example - http://plnkr.co/edit/vtcpiYsTYKq3H2QTupyS?p=preview