Angular Default value dropdown using an Object - arrays

I am using Angular Material (latest stable version) for my project and I am not able to set the default value in the dropdown by using an Object. My code:
TS-FILE:
selectedObject: any;
selectedObjectList: any = [{ key: 1, value: 'One' }, { key: 2, value: 'Two' }, { key: 3, value: 'Three' }];
changeMatObject(value: any) {
console.log('MatObject value: ', value);
}
ngOnInit(): void {
this.selectedObject = { key: 1, value: 'One'};
}
HTML-FILE:
<mat-form-field>
<mat-select [(value)]="selectedObject">
<mat-option *ngFor="let selectedObject of selectedObjectList; let i = index" value="{{selectedObject.key}}"
(click)="changeMatObject(selectedObject.value)">
{{selectedObject.value}}
</mat-option>
</mat-select>
</mat-form-field>
Apparently it is possible to set the default value when the value in the HTML [(value)] is set to a string like this example:
TS-FILE:
selectedString: any;
selectedStringList: any = ['One', 'Two', 'Three'];
changeMatString(value: any) {
console.log('MatString value: ', value);
}
ngOnInit(): void {
this.selectedObject = 'One';
}
HTML-FILE:
<mat-form-field>
<mat-select [(value)]="selectedString">
<mat-option *ngFor="let selectedString of selectedStringList; let i = index" value="{{selectedString}}"
(click)="changeMatString(selectedString)">
{{selectedString}}
</mat-option>
</mat-select>
</mat-form-field>
This is a small piece of code but with larger lists/objects it can be difficult. Can someone explain what the difference is creating a dropdownlist with array filled with strings and an array filled with objects and why it isnt possible to set the default value in object dropdown?

Did you try to use [(ngModel)] in place of [(value)] ?
<mat-form-field>
<mat-select [(ngModel)]="selectedString">
<mat-option *ngFor="let string of selectedStringList; let i = index" value="{{string}}"
(click)="changeMatString(string)">
{{string}}
</mat-option>
</mat-select>
</mat-form-field>

You made a few typos (and re-used selectedObject in your template) and you need a way to compare objects:
HTML
<mat-form-field>
<mat-select [(value)]="selectedObject" [compareWith]="compareObjects">
<mat-option *ngFor="let selectableObject of selectedObjectList; let i = index" [value]="selectableObject"
(click)="changeMatObject(selectableObject.value)">
{{selectableObject.value}}
</mat-option>
</mat-select>
</mat-form-field>
(Note the difference between selectedObject and selectableObject, the [value] binding and the compareObjects function)
TypeScript
public compareObjects = function (option, value): boolean {
return option.key === value.key;
}

Related

How to build a dynamic object in Angular

Hello I have the following problem, I want to form the following payload structure that is in the example of the first code paragraph, I was advancing well until I came across the part of the form that is dynamic and no matter how hard I try it has not worked out, I would appreciate it really much your help.
The problem is that I cannot build the structure as the first paragraph of code, especially in the dataCollege field, since it does not save the array dynamically.
I tried to put the FormControl in the html where I render the dynamic inputs but it gives me an error
This is the structure of the payload that I was able to assemble the first 3, but the last one is where I have a problem, which is the field datosCole, which is dynamic, depending on the GradosCole field, which can be initial, has 2 values, primary has 3 values and secondary has 4 values
{ name: Sara,
age: 14,
gradeCollege: Secondary,
dataCollege: {
course: "Maths",
schedule: "Afternoon",
section: "A",
teacher: "Mateo",
}
}
This is my HTML sheet of how I build my form.
<div>
<form [formGroup]="form">
<!-- Name -->
<mat-form-field>
<input matInput type="text" placeholder="Name" [formControl]="name">
</mat-form-field>
<!-- Age -->
<mat-form-field>
<input matInput type="text" placeholder="Age" [formControl]="age">
</mat-form-field>
<!-- Grade -->
<mat-form-field>
<mat-label>Grade</mat-label>
<mat-select [formControl]="gradeCollege">
<mat-option *ngFor="let item of grades" [value]="item" (click)="getData(item)">
{{item}}
</mat-option>
</mat-select>
</mat-form-field>
<!-- dynamic data -->
<div *ngFor="let item of data; index as i" >
<mat-form-field>
<input matInput [type]="item.type" [placeholder]="item.label" [formControl]="dataCollege" >
</mat-form-field>
</div>
</form>
<div>
<button type="button" (click)="save()">Save</button>
</div>
</div>
This is my TS file
import { Component, OnInit } from '#angular/core'; import { FormGroup, FormBuilder, Validators, AbstractControl } from '#angular/forms';
#Component({ selector: 'app-datos', templateUrl: './datos.component.html', styleUrls: ['./datos.component.scss'] }) export class DatosComponent implements OnInit {
public grades: any[] = ['initial','primary','secondary'];
public data: any[] = []
// Form
public form: FormGroup;
public name: AbstractControl;
public age: AbstractControl;
public gradeCollege: AbstractControl;
public dataCollege: AbstractControl;
constructor( protected fb: FormBuilder, ) {
}
ngOnInit() {
this.form = this.fb.group({
name: ['', Validators.compose([Validators.required])],
age: [''],
gradeCollege: ['', Validators.compose([Validators.required])],
dataCollege: ['']
});
this.name= this.form.get('name');
this.age = this.form.get('age');
this.gradeCollege = this.form.get('gradeCollege');
this.dataCollege = this.form.get('dataCollege'); }
getData (typeGrade: any) {
console.log(typeGrade);
if(typeGrade === 'initial'){
this.data = null
this.data = [
{type: 'text', label: 'course'},
{type: 'text', label: 'schedule'},
]
}
if(typeGrade === 'primary'){
this.data = null
this.data = [
{type: 'text', label: 'course'},
{type: 'text', label: 'schedule'},
{type: 'text', label: 'section'}
]
}
if(typeGrade === 'secondary'){
this.data= null
this.data = [
{type: 'text', label: 'course'},
{type: 'text', label: 'schedule'},
{type: 'text', label: 'section'},
{type: 'text', label: 'teacher'}
]
}
}
submit() {
const payload = {
name: this.name.value,
age: this.age.value,
gradeCollege: this.gradeCollege.value,
dataCollege: this.dataCollege.value,
};
console.log(payload)
}}
this is the error
ERROR TypeError: ctx.save is not a function
at ActionDialogComponent_Template_button_click_13_listener (template.html:34:36)
at executeListenerWithErrorHandling (core.js:21860:1)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:21902:1)
at HTMLButtonElement. (platform-browser.js:976:1)
at ZoneDelegate.invokeTask (zone-evergreen.js:399:1)
at Object.onInvokeTask (core.js:41686:1)
at ZoneDelegate.invokeTask (zone-evergreen.js:398:1)
at Zone.runTask (zone-evergreen.js:167:1)
at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:480:1)
at invokeTask (zone-evergreen.js:1621:1)
defaultErrorLogger # core.js:6241
handleError # core.js:6294
handleError # core.js:13881
executeListenerWithErrorHandling # core.js:21863
wrapListenerIn_markDirtyAndPreventDefault # core.js:21902
(anonymous) # platform-browser.js:976
invokeTask # zone-evergreen.js:399
onInvokeTask # core.js:41686
invokeTask # zone-evergreen.js:398
runTask # zone-evergreen.js:167
invokeTask # zone-evergreen.js:480
invokeTask # zone-evergreen.js:1621
globalZoneAwareCallback # zone-evergreen.js:1647
I imagine that you want that the "dataCollege" is an Array, not an object.
Some like, e.g.
{ name: Sara,
age: 14,
gradeCollege: Secondary,
dataCollege:[{
course: "Maths",
schedule: "Afternoon",
section: "A",
teacher: "Mateo",
}
]
}
or if you has two elements
{ name: Sara,
age: 14,
gradeCollege: Secondary,
dataCollege:[{
course: "Maths",
schedule: "Afternoon",
section: "A",
teacher: "Mateo",
},
{
course: "Language",
schedule: "Afternoon",
section: "C",
teacher: "Alex",
}
]
}
Well you has an array of objects, so you should use an FormArray of FormGroups
A FormArray of FormGroup as mannage always in the same way
1.-Declare in a .ts a getter of your formArray
get dataCollege()
{
return this.form.get('dataCollege') as FormArray
}
2.- in .html
<form [formGroup]="form">
...inputs thats belog to the main form in the way,e.g...
..see that you use formControlName="control-name"
..the control-name between quotes but the "formControlName" as is..
<input formControlName="name">
....
..a div with formArrayName..
<div formArrayName="dataCollege">
..a ngFor to iterate over the formArray.controls..
..you use the "getter" defined in the fisrt step..
..you add [formGroupName]="i"..
<div *ngFor="let group of dataCollege.controls;let i=index"
[formGroupName]="i"
..like in the main group you use formControlName for the...
..controls of the FormArray...,e.g.
<input formControlName="course">
</div>
</div>
And forget all your getters to get the control

Angular Use Function in Container Context

I want to set up a checkbox structure and I want to handle it dynamically. I have an object response returned from my service. However, when I use this function in context, I get a string return and I cannot use ngFor.
form.demo.component.ts
getElementChoicesByNKey(nkey:string):choiceModel[]{
var element = this.findInComponentsByNKey(nkey);
var res = element.Choices;
return res;
}
this function gives the correct return.
form.demo.component.html
...
<ng-container *ngIf="item.Type == 'Choice'">
<ng-container *ngTemplateOutlet="choiceTheme; context:{ Id: item.Id, Label: item.Label, Choices: getElementChoicesByNKey(item.Id) }"></ng-container>
</ng-container>
...
<ng-template #choiceTheme let-Id="Id" let-Label="Label" let-Choices="Choices">
<nz-form-item>
<nz-form-label nzFor="Id">{{Label}}</nz-form-label>
<nz-form-control [formControlName]="Id" nzErrorTip="{{ 'form.requiredText' | translate }}">
<p>{{Choices.length}}</p>
<div *ngFor="let item of Choices">
<p>test</p>
</div>
</nz-form-control>
</nz-form-item>
</ng-template>
...
here Choices appears as string and won't let me use ngFor. I am getting an error as below.
Cannot find a differ supporting object '[
{ label: 'Apple', value: 'Apple', checked: true },
{ label: 'Pear', value: 'Pear', checked: false },
{ label: 'Orange', value: 'Orange', checked: false } ]' of type 'string'. NgFor only supports binding to Iterables such as Arrays.
What is the point I missed? Thank you for your help.
Use
*ngFor="let item of getChoices(Choices)" in template
and in component.ts
getChoices(choiceStr) {
return JSON.parse(choiceStr);
}
Since you are getting Choices as a string, parse the string to an array inorder for ngFor to work

how to do ngFor inside ngFor dynamically in angular 8?

Hi what i trying to achieve is ngFor with dynamic value inside ngFor, is this possible? i try using ngModel inside it too and it didn't work out. Here is what i do :
inside my home.component.ts :
import { Component, OnInit } from '#angular/core';
import {CdkDragDrop, moveItemInArray} from '#angular/cdk/drag-drop';
export interface Condition {
value: string;
viewValue: string;
}
export interface ListProduk {
value: string;
viewValue: string;
}
export interface DragBox {
value: string;
viewValue: string;
}
export interface ListModel {
value: string;
viewValue: string;
single_item: string;
}
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
conditions: Condition[] = [
{ value: 'if', viewValue: 'IF' },
{ value: 'else', viewValue: 'ELSE' },
{ value: 'then', viewValue: 'THEN' },
{ value: 'if else', viewValue: 'IF ELSE' },
{ value: 'or', viewValue: 'OR' },
{ value: 'and', viewValue: 'AND' }
];
listProduks: ListProduk[] = [
{ value: 'mcm-508', viewValue: 'MCM-508' },
{ value: 'bl-100 pl', viewValue: 'BL-100 PL' },
{ value: 'bl-150 bl', viewValue: 'BL-150 BR' },
{ value: 'bl-302gs', viewValue: 'BL-302GS' },
{ value: 'bl-52gl', viewValue: 'BL-52GL' }
];
listModels: ListModel[] = [
{ value: 'conditions', viewValue: 'Condition', single_item:'condition' },
{ value: 'listProduks', viewValue: 'List Produk', single_item:'listProduk' },
]
constructor() { }
ngOnInit() {
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.listModels, event.previousIndex, event.currentIndex);
}
}
and then here is my home.component.html :
<p>home works!</p>
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let listModel of listModels" cdkDrag>
<mat-form-field>
<mat-label>Pick {{listModel.value}} :</mat-label>
<mat-select>
<mat-option *ngFor="let {{listModel.single_item}} of {{listModel.value}}" [value]="{{listModel.single_item}}.value">
test
</mat-option>
</mat-select>
</mat-form-field>
<div>
<i class="material-icons">
arrow_right_alt
</i>
</div>
</div>
</div>
i try to do loop the mat-select dynamically, since i want it loop an array that have different name, i need value in listModel array to print to *ngFor inside mat-select. Which is this line :
<mat-option *ngFor="let {{listModel.single_item}} of {{listModel.value}}" [value]="{{listModel.single_item}}.value">
test
</mat-option>
how to do this properly?
UPDATED QUESTION After update my code with Ahmed comment, which is my Html is looked like this :
<p>home works!</p>
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let listModel of listModels" cdkDrag>
<mat-form-field>
<mat-label>Pick {{listModel.value}} :</mat-label>
<mat-select>
<mat-option *ngFor="let a of listModel.value" [value]="a.value">
{{a.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
<div>
<i class="material-icons">
arrow_right_alt
</i>
</div>
</div>
</div>
and this give me an error like this :
ERROR Error: Cannot find a differ supporting object 'conditions' of
type 'string'. NgFor only supports binding to Iterables such as
Arrays.
what did i missed?
You can display this using a function, which would return the correct array. We are calling this in the template. BE CAREFUL, I would never recommend calling a function in template, if it is just all possible. This can seriously hurt performance in an app. But if you don't have much content on this page, it is pretty safe to use. So I would suggest the following:
<div *ngFor="let value of getList(listModel.value)">
and the function would return the correct array:
getList(value) {
return this[value]
}
You could also make a slight change to the model and pass an optional parameter with the array with the correct array to the object itself. You can do this in OnInit:
ngOnInit() {
this.listModels.forEach(x => {
x.customArray = this[x.value]
})
}
and use it like normal iteration in *ngFor:
<div *ngFor="let value of listModel.customArray">
Here's a STACKBLITZ with both options

Filter array of objects in Angular4 without pipe

I have an array of links that each element is an object that contain few strings - a link, description and category. I have different components that display the links, and I want in each component to display only the links of its category.
So I want to filter the array by the category.
I have a mock-up array with all the links.
I try to filter the array of objects without a pipe. The reason why: https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
Apparently the Angular team suggests to do the filtering in the component level and not using a pipe:
"The Angular team and many experienced Angular developers strongly recommend moving filtering and sorting logic into the component itself."
So here's my component:
#Component({
selector: 'startups',
templateUrl: './startups.component.html'
})
export class StartupsComponent implements OnInit {
constructor(private appLinksService: DigitalCoinHubService) { }
title = 'Startups';
links: DCHlinks[]; // create a new array in type of DCHlinks to get the data
startupsLinks: DCHlinks [] = []; // will build the startsups links only
getLinks(): void {
this.links = this.appLinksService.getLinks(); // gets the array with the data from the service
for (let i in this.links)
{
if (this.links[i].dchCategory == 'startups' )
{
this.startupsLinks[i].push(this.links[i]);
}
}
}
ngOnInit() {
this.getLinks();
}
}
So first I get the big array from the service:
this.links = this.appLinksService.getLinks();
And then I try to build a new array that will contain only the relevant links. The filter is by the category. But then when I try to build the new array by push the elements which their category matches - it gives me error:
Property 'push' does not exist on type 'DCHlinks'.
DCHlinks is the object - this is the class:
export class DCHlinks {
dchLink: string;
dchLinkTitle: string;
dchLinkDescription: string;
dchCategory: string;
}
Any idea how to do this simple filter? (and w/o pipe - see above reason why..)
Thanks!
You need to intialize the array as you did for startupsLinks
links: DCHlinks[] = [];
Or you can simply use array.filter to get the relavant data
this.startupsLinks = this.links.filter(t=>t.dchCategory == 'startups');
I have the same issue but I resolve in different way. I have the Array of object
and want to use filtering using of html select option .which I given the data that filter the array of object.
`$`
heroes = [{
name: "shubh",
franchise: "eng"
},
{
name: "Ironman",
franchise: "Marvel"
},
{
name: "Batman",
franchise: "DC"
},
{
name: "Batman",
franchise: "DC"
},
{
name: "Batman",
franchise: "DC"
},
{
name: "satman",
franchise: "mc"
},
{
name: "monmam",
franchise: "DC"
},
{
name: "loolman",
franchise: "DC"
},
{
name: "Thor",
franchise: "Marvel"
},
{
name: "monmam",
franchise: "DC"
},
{
name: "monmam",
franchise: "DC"
},
{
name: "monmam",
franchise: "DC"
},
{
name: "Thor",
franchise: "Marvel"
},
{
name: "Superman",
franchise: "DC"
},
{
name: "Superman",
franchise: "DC"
},
{
name: "Superman",
franchise: "DC"
},
{
name: "Superman",
franchise: "DC"
},
];
//this is the most imp part in the filter section .I face lot of problem this is not working if this line not write .The filter method works old one time.
newarr = this.heroes;
//So I create new array which store the old array value every time. and we replace the value in the filter function.
filter(heroes: string) {
console.log(heroes);
this.heroes = this.newarr; // replace the value and store old value
let heroesnew = this.heroes.filter((data => data.name == heroes));
this.heroes = heroesnew;
console.log(this.heroes);
}
<!––"#sel" is the template variable which gives the data of value property in option field ––>
<select #sel class="custom-select custom-select-sm" (change)="filter(sel.value)">
<option>All</option>
<option value="Batman">Batman</option>
<option value="Superman">Superman</option>
<option value="satman">satman</option>
<option value="monmam">monmam</option>
<option value="Thor">thor</option>
</select>
<!–– this is the table that I want to filter––>
<div>
<table class="table table-hover ">
<thead class="thead-dark">
<tr>
<th>#</th>
<th>name</th>
<th>franchise</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let incidence of heroes">
<td>{{ incidence.name}}</td>
<td> {{ incidence.franchise }}</td>
</tr>
</tbody>
</table>
</div>
$
use the code for angular 2+,to 8 It working fine ..

convert column value to a map value in ng-repeat

Nervous to ask this question.. HATE getting downvoted.. but it is what it is, I've searched and can't find the solution.
What I ended up doing is adding a loop that goes through my searchResults and reassigns the value for the column after the service returns inside the success block (PSEUDO CODE HERE, I can't copy and paste my actual code, there is an airgap):
var myNumberMap = {
1: "Number ONE!!",
2: "Number TWO!!",
3: "Number THREE!!!"
}
$scope.getSearchResults = function() {
$q.all({
resultSet : searchService.getSearchResults()
}).then(function(resultData) {
searchResults = resultData.resultSet;
for(var i = 0; i < searchResults.length; i++) {
searchResults[i].number = myNumberMap[searchResults[i].number];
}
}
}
I was really hoping there was some slick way I could just assign the data result value inside the grid config to be the value in the map?
Something like:
$scope.myCoolGridConfig = NgGridConfig.getConfig(
NgGridConfig.getDefaultConfig(), {
data: 'searchModel.searchResults.list',
columnDefs: [
field: 'number',
displayName: 'Number',
value: myNumberMap[searchModel.searchResults.list.number]
]
}
)
There are a few methods that you could take here:
Create a custom filter that you apply to your ng-repeat to transform the values based on your map.
Store your value map in your angular controller and bind the mapped value to the DOM.
// Controller
$scope.myMap = {
1 : "String One",
2 : "String Two",
3 : "String Three"
}
// something.html
<div ng-repeat='num in numList'>
{{myMap[num]}}
</div>
If I interpenetrated the question correctly your looking for something along these lines.
myMap = {
1 : "String One",
2 : "String Two",
3 : "String Three"
};
If the col number is 1 display String One instead of one in the table
Use myMap and look for the prop of col in it to pull the string value
<table>
<tr ng-repeat="col in tempCols">
<td>{{col}}</td>
<td>{{myMap[col]}}</td>
</tr>
</table>
If you need to do it towards an object that has no defining index such as the object below.
$scope.objectData = [{
name: "test1",
},
{
name: "test1",
},
{
name: "test1",
},
{
name: "test1",
},
{
name: "test1",
},
]
You can track it by $index + 1
<table>
<tr>
<td> Column Converted</td>
<td> Object name value</td>
<tr ng-repeat="col in objectData">
<td>{{myMap[$index + 1]}}</td>
<td>{{col.name}}</td>
</tr>
</table>
Heres a plunker for a better visual

Resources