There is a data model with a nested object :
export interface Model {
someField1:string;
someField2:string;
someObject: Object;
}
export interface Object {
someField1: string;
someField2: string;
someField3: string;
}
there is a form for this model :
formBuilder.group({
someField1: null,
someField2: null,
someObject: formBuilder.group({
someField1: null,
someField2: null,
someField3: null
})
there is an angular component for this object:
<mat-form-field>
<mat-select placeholder="Some Object" formGroupName="someObject">
<mat-option *ngFor="let object of someObjectes" [value]="object">
{{ object.someField1 }}
</mat-option>
</mat-select>
</mat-form-field>
I accept a list of objects from the server and let me select one that the user needs. It is necessary that the choice is tied to the form and if the received model already has data about the object, then they should be displayed as a pre-selected item.
Angular Material mat-select offers a compareWith API to which you can pass a mapping function to set the default value.
Here is a working Stackblitz example
Related
Based on what it is selected on this field, I want to fill with data the Tasks field below.
<mat-form-field>
<mat-label>Position</mat-label>
<mat-select [(ngModel)]="position" (ngModelChange)="change(position)" multiple formControlName="position">
<mat-option *ngFor="let position of positionArray" [value]="position">
{{position}}
</mat-option>
</mat-select>
</mat-form-field>
This is the Tasks field:
<mat-form-field>
<mat-label>Tasks</mat-label>
<mat-select formControlName="tasks">
<mat-option *ngFor="let res of tasksItems" [value]="res">
{{res}}
</mat-option>
</mat-select>
</mat-form-field>
Typescript code is as below:
private tasksItems= [];
change(position) {
if (position == "IT") {
this.tasksItems= ["one", "two"];
}
else if (position == "Design") {
this.tasksItems= ["three", "four"];
}
else {
this.tasksItems= [];
}
Edit: This is the positionArray in ts. The values are stored in database and the change(position) method works fine. The problem is the field Tasks doesn't get the value that is stored and I am assuming it has something to do with [(ngModel)].
positionArray: string[] = ['IT', 'Design'];
Data is stored to database. But the problem is [(ngModel)] doesn't read the data (when I want to edit the field). Can someone explain to me why and how do I fix it?
You shouldn't need to use ngModel if you are already using formControlName.
I can't see the whole code but I assume you have something like [formGroup]="form" on the form element which would bind to the FormGroup in your ts file.
As long as your form.position form control is updated, it should reflect in your template.
If you are using reactive form controls try removing [(ngModel)]="position" and make sure the form control is set correctly in your ts file.
Edit
Also, try changing your template variable names. position seems to be used for different variables.
try:
<mat-option *ngFor="let pos of positionArray" [value]="pos">
{{pos}}
</mat-option>
I'm using angular material and observables to create an async list with mat-paginator and mat-option (instead of using a table, client requirements). I want to paginate this list in HTML but with the observable stream, not using a subscribe and an assignment to an auxiliary array paginated.
Example:
<mat-option *ngFor="let elem of opMvAsync | async">
<!-- Data printed here -->
</mat-option>
<mat-paginator *ngIf="(opMvAsync | async)?.length > 5" [length]="(opMvAsync | async)?.length" [hidePageSize]="true" [pageSize]="5" (page)="onPageChange($event)"></mat-paginator>
And the TS:
//TS
opMvAsync : Observable<Array<Items>>;
ngOnInit() {
this.opMvAsync = this.service.getItems();
}
I have another example in my app, very similar, but using an auxiliary array :
<!-- HTML-->
<mat-option *ngFor="let elem of lstPaginated">
<!-- Data printed here -->
</mat-option>
<mat-paginator *ngIf="lstOri.length > 5" [length]="lstOri.length" [hidePageSize]="true" [pageSize]="5" (page)="onPageChange($event)"></mat-paginator>
// TS
lstOri: Array<Items>;
lstPaginated: Array<Items>;
ngOnInit() {
this.service.getItems().subscribe(r=> {
this.lstOri= r;
this.lstPaginated= this.lstOri.slice(0, 5);
});
}
onPageChange(event: PageEvent) {
this.lstPaginated= this.lstOri.slice(event.pageIndex * event.pageSize, event.pageIndex * event.pageSize + event.pageSize);
}
This works fine, but it is quite laborious to have to handle two arrays constantly.
Is there any way to work directly paging the observable? Thanks for your advance.
Edit: What I need is to figure it out the way in which I can paginate the observable that I'm rendering in HTML. My OnPageChange should handle which elements of the observable I display, but I don't know how to do it.
You can solve this problem by following below pattern.
In your service, instead of having a method getItems(), create a variable items$. And set its value to the get/post method call.
In your component, set opMvAsync equal to service.items$.
Now in your template, use it with async pipe.
Below is the sample code:
my.service.ts
#Injectable({ providedIn: 'root', })
export class MyService {
...
items$ = this.http.get('some/url/').pipe(...);
...
}
app.component.ts
export class AppComponent {
...
public opMvAsync$ = this.service.items$.pipe(...);
...
}
app.component.html
<div *ngIf="opMvAsync$ | async as opMvAsync">
<mat-option *ngFor="let elem of opMvAsync | async">
<!-- Data printed here -->
</mat-option>
</div>
This way you can avoid the subscribe/unsubcribe work, as async keyword will take care of it.
I have a data object obj with multiple parameters:
export interface obj {
param1: number;
param2: number;
param3: number;
}
I would like to dynamically add mat-grid-tiles with mat-form-fields displaying their value.
I do this by creating an array of the parameter names:
params = ['param1', 'param2', 'param3']
Then, in my HTML code, I use my mat-grid-tiles with *ngFor on the parameter names and then assign the reference to my obj in [ngModel]:
<mat-grid-tile [colspan]=1 *ngFor="let param of params">
<mat-form-field>
<input matInput [ngModel]="coilData[params]" readonly>
</mat-form-field>
</mat-grid-tile>
It works! However, because my obj is initially null until an API call is made, it seems to throw hundreds of null errors like so:
ERROR TypeError: Cannot read property 'param1' of null
I realized that I can just use the Elvis Operator: ?, however I can not seem to figure out how to use this on [ngModel] when I am dynamically assigning parameter names to it like this: [ngModel]="obj[param]" instead of the usual [ngModel]="obj?.param1.
Is it possible to use the Elvis Operator when dynamically applying parameter names to ngModel?
Ended up taking Heretic Monkey's suggestion and used obj ? obj[param] : null instead which ended up working for me.
Try
< ng-container *ngIf="coilData" >
<mat-grid-tile [colspan]=1 *ngFor="let param of params">
<mat-form-field>
<input matInput [ngModel]="coilData[params]" readonly>
</mat-form-field>
</mat-grid-tile>
< ng-container />
I am creating a dynamic multiple dropdown in angualar 8. I am receiving list of list from backend and I am running through the list of list to generate multiple dynamic dropdown. Now I need to send selected option values from all the dropdown to backend again in Angular 8. I am unable to send all the value.
I am getting list in this format from which I am looping through to generate dynamic drop-down
cat_dropdown = [['A','B','C'],['1','2','3']]
Based on above list my html code generate two drop-downs one with options A,B,C and another with 1,2,3.
My HTML Code:
<form (ngSubmit)="segmentSelection()" #ff="ngForm">
<div id="userSelection" ngModelGroup="userData" #userData="ngModelGroup">
<div *ngFor="let first of cat_dropdown">
<mat-form-field>
<mat-label>Choose Segment Key</mat-label>
<mat-select id="selectme" name="segmentKey">
<mat-option *ngFor="let segment of first" [value]="segment">
{{segment}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</form>
My component.ts code:
import { NgForm } from '#angular/forms';
export class myComponent implements OnInit {
#ViewChild("ff", { static: true }) segmentData: NgForm;
plotselection = {
segmentKey: []
}
segmentSelection(){
this.plotselection.segmentKey = this.segmentData.value.userData.segmentKey;
fetch("http://localhost:5000/data-decomposition", {
method: "POST",
headers: {
"Content-Type": "application/json"
},body: JSON.stringify({
segmentKey: this.segmentData.value.userData.segmentKey,
})
}).then(res => res.json()).then(myjson => {
console.log(myjson)
})
}
Now in my .ts component I have a dictionary name "plotselection" where I have key name 'segmentKey' which I defined as empty list.
As I have 2 dropdown in frontend so thought that i will receive two values from frontend and I will send those as a list to backend. When I select option from both the dropdowns for example from 1st dropdown I choose 'B' and from 2nd '3' and submit my selection, then when I console log the response I could only see the value '3' in my list not 'B' and '3' together. How can I have both the value to the list.
Thank you, and looking for your suggestion...
For my easiness I have used Select control instead of mat-select control.
In the form I have given specific name by appending the index to control while looping.
Html
<div *ngFor="let first of cat_dropdown; let num = index">
<div>
<Label>Choose Segment Key</Label>
<Select id="selectme{{num}}" [value]="segment" name="segmentKey{{num}}" (change)="ChangeCall($event.target)">
<option *ngFor="let segment of first" >
{{segment}}
</option>
</Select>
</div>
</div>
So that there are two controls with respective selected values.
There is also alternate solution is use to Change event on the Select Control.
I am trying to display a single object from an array based on a property value.
My list of transactionitems has an accountId property, but I would like to display the account name instead. All accounts are loaded in the accounts$ array. I just can't figure out how to properly use my getAccountById function
Here is the component class
export class NewtransactionComponent implements OnInit {
transaction$: Transaction;
tempItem$: TransactionItem;
accounts$: Array<Account>;
constructor(private data: DataService) { }
ngOnInit() {
this.transaction$ = new Transaction();
this.data.getAccounts().subscribe(data => this.accounts$ = Object.assign(new Array<Account>(), data));
this.tempItem$ = new TransactionItem();
this.transaction$.TransactionDate = new Date();
}
addItem(){
this.transaction$.TransactionItems.push(this.tempItem$);
this.tempItem$ = new TransactionItem();
}
getAccountById(id):Account{
return this.accounts$.find(x => x.id === id);
};
and here is the html view that gives the error "Cannot read property 'name' of undefined"
<div class="items-container">
<mat-form-field>
<input matInput placeholder="Amount" [(ngModel)]="tempItem$.Amount">
</mat-form-field>
<mat-form-field *ngIf="accounts$">
<mat-select placeholder="Account" [(ngModel)]="tempItem$.AccountId">
<mat-option *ngFor="let account of accounts$" [value]="account.id">{{account.name}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Credit/Debit" [(ngModel)]="tempItem$.CreditDebit">
<mat-option value="Credit">Credit</mat-option>
<mat-option value="Debit">Debit</mat-option>
</mat-select>
</mat-form-field>
<button mat-mini-fab color="primary" (click)="addItem()">Add</button>
</div>
<table *ngIf="transaction$.TransactionItems.length">
<tr>
<th>Amount</th>
<th>Account</th>
<th>Credit/Debit</th>
</tr>
<tr *ngFor="let item of transaction$.TransactionItems">
<th>{{item.Amount | currency}}</th>
<th>{{getAccountById(item.AccoundId).name}}</th>
<th>{{item.CreditDebit}}</th>
</tr>
</table>
these are the account and transactionitem data models for reference
export class Account {
Id: string;
Name: string;
Category: string;
SubCategory: string;
}
export class TransactionItem{
Id: number;
TransactionId:number;
Accountid: string;
Amount: number;
CreditDebit: string;
}
I assume that the error is here: {{account.name}}?
This is most likely a timing issue. The page will attempt to display before the data is retrieved from the subscription.
One way to resolve the issue is to use an *ngIf
<div class="items-container" *ngIf="account">
That way the page won't try to display until the data is retrieved.
Another option is to use the safe navigation operator:
{{account?.name}}
The question mark tells Angular not to attempt to read the name property if the account is null or undefined.
EDIT:
If there error is here: {{getAccountById(item.AccountId).name}}, then it is telling you that getAccountById(item.AccountId) is undefined or null. Is it possible that one of your transactions has no account?
And looking at your code more closely, JavaScript/TypeScript is case sensitive. So if you declare the id using:
Id: string;
(Upper case I)
You can't then access it using:
return this.accounts$.find(x => x.id === id);
(Lower case)