Angular How to display a single object from an array in html - html

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)

Related

Angular get value from dynamically generated mat select

I'm using Angular material to make a reactive form that look like this:
Form
With this code I make a dynamically generated mat select and an input, but I don't know how to get the value from all of these.
HTML:
<div *ngFor="let number of [].constructor(cantConsumibles)">
<mat-select placeholder="Selecciona un consumible" class="form-control"
formControlName="consumibles">
<mat-option *ngFor="let consumible of consumibles" [value]="consumible">
{{consumible.CodConsumible}}
</mat-option>
</mat-select>
<input matInput type="number" formControlName="consumibles" placeholder="Cantidad">
</div>
<div align="end">
<button mat-button (click)="agregarConsumible(true)"><mat-icon>add</mat-icon></button>
<button mat-button (click)="agregarConsumible(false)"><mat-icon>remove</mat-icon></button>
</div>
TS:
cantConsumibles: number = 0;
agregarConsumible(flag: boolean): void {
(flag) ? this.cantConsumibles++ : this.cantConsumibles--;
}
Any ideas? Thanks in advance
The select API documents say that we could register a change with #Output() selectionChange: EventEmitter<C> https://material.angular.io/components/select/api
So let's add that method on our select to know, what was selected.
<mat-select placeholder="Favorite food" (selectionChange)="onFoodSelection($event)">
<mat-option *ngFor="let food of foods" [value]="food.value">
{{ food.viewValue }}
</mat-option>
</mat-select>
We then console.log the event value but also save the event value to a variable for later use.
#Component({
selector: 'material-app',
templateUrl: 'app.component.html'
})
export class AppComponent {
output: any;
foods = [
{value: 'product10', viewValue: 'Steak'},
{value: 'product20', viewValue: 'Pizza'},
{value: 'product30', viewValue: 'Tacos'},
{value: 'product40', viewValue: 'Lasagne'},
];
onFoodSelection(event:any) {
this.output = event.value;
console.log(event.value)
}
}
Follow same principal on other elemens (select, input, checkbox etc.) getting the event on change in template and doing something with in in typescript code part.
Here is a small example for you: https://stackblitz.com/edit/material-select-change-event-binding-tta13p?file=app%2Fapp.component.ts

Paginate Observable in HTML with Mat-paginator and 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.

angular mat-select set default value in form

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

Angular 6 mat-form-field with mat-select not selecting any values

I am new to Angular 6, and I am working on a project where I am using material design with reactive form which uses form control. So the problem I am facing is that all other fields are working fine except mat-select, where it properly display the drop-down content but when i select it, the selected values are not reflected in form control.
And mat-select data are coming from back-end
Here is my html code:
<mat-form-field class="example-full-width">
<mat-select placeholder="Source City" formControlName="sourceCityControl" required [errorStateMatcher]="matcher">
<mat-option *ngFor="let city of cities" [value]="city">
{{city.name}}
</mat-option>
</mat-select>
<mat-error>
Source City <strong> Required </strong>
</mat-error>
</mat-form-field>
<br> <br>
<mat-form-field class="example-full-width">
<mat-select placeholder="Destination City" formControlName="destinationCityControl" required [errorStateMatcher]="matcher">
<mat-option *ngFor="let city of cities" [value]="city">
{{city.name}}
</mat-option>
</mat-select>
<mat-error>
Destination City <strong> Required </strong>
</mat-error>
</mat-form-field>
<!-- other form elements -->
<!-- to check form elements -->
{{profileForm.value | json}}
And here is my TypeScript file code:
public cities = [];
minDate = new Date();
nextdate = this.minDate.getUTCDate();
nextmonth = this.minDate.getUTCMonth();
nextyear = this.minDate.getFullYear();
maxDate = new Date(this.nextyear, this.nextmonth + 1, this.nextdate);
host: Oldoffer;
offerModel: any;
matcher = new MyErrorStateMatcher();
constructor(private fb: FormBuilder, private _offerService:
OfferRideService, private router: Router, private editService: EditService)
{ }
test1: string;
test2: Date;
test3: Date;
profileForm: FormGroup;
ngOnInit() {
console.log(this.nextdate);
this._offerService.getCities().subscribe(data => this.cities = data);
this.editService.getHost().subscribe((data) => {
this.host = data;
this.test1 = this.host.date.toString();
this.test2 = new Date(this.test1.slice(0, 19) + 'Z');
this.test3 = new Date(this.test2.getFullYear(), this.test2.getUTCMonth(),
this.test2.getUTCDate())
this.profileForm = new FormGroup({
sourceCityControl: new FormControl('', Validators.required),
destinationCityControl: new FormControl('', Validators.required),
dateControl: new FormControl(this.test3, Validators.required),
seatsControl: new FormControl(this.host.noOfSeats, [Validators.required,
Validators.min(1), Validators.max(4)]),
amountControl: new FormControl(this.host.amount, [Validators.required,
Validators.min(0), Validators.max(2000)]),
acControl: new FormControl(this.host.preference.ac),
musicControl: new FormControl(this.host.preference.music),
smokingControl: new FormControl(this.host.preference.smoking),
petsControl: new FormControl(this.host.preference.pets)
});
})
}//other code omitted
Any help and suggestions??
Thanks ...
I replicated your problem to it's simplest form and here's what you're missing: you might be missing a <form> element from your other formContorls so make sure you always keep an eye on on the console. It's gonna be your best friend if you're new to angular. Also when building FormGroups, look at the documentation for it because that's not a good way to do it.
Just follow the example I build for you and it should solve your problems: https://stackblitz.com/edit/angular-nbihkv

Angular 2 enum with ngClass

Hello I am trying to use a conditional class with an enum. I have used enums in html before with ngSwitchCase and have the same error that I'm getting now. When I would add a property called that enum and assign it to that enum it would work.
working example:
<ng-container *ngFor="let column of columns" [ngSwitch]="column.dataType">
<td *ngSwitchCase="DataType.Text">{{getPropertyValue(row,column.propertyName)}}</td>
<td *ngSwitchCase="DataType.Date">date</td>
<td *ngSwitchCase="DataType.Number">number</td>
<td *ngSwitchDefault>default</td>
</ng-container>
ts
private DataType = DataType;
not working:
<span *ngClass="(column.sortType === SortType.Ascending)? 'privilege-grid-sortasc': (column.sortType === SortType.Descending)?'privilege-grid-sortdesc':'privilege-grid-sortnone'"></span>
I also have tried [ngClass] ="{'class-name': var === enum,...}"
ts
private SortType = SortType;
error message:
Cannot read property 'Ascending' of undefined
I found a really good tutorial here : https://coryrylan.com/blog/angular-tips-template-binding-with-static-types
in summary the syntaxe is [ngClass] ="{'class-name': var === enum}":
#Component({
selector: 'app-message',
template: `
<div class="message" [ngClass]="{
'message--error': messageTypes.Error === type,
'message--success': messageTypes.Success === type,
'message--warning': messageTypes.Warning === type
}">
<ng-content></ng-content>
</div>
`
})
export class MessageComponent implements OnInit {
#Input() type = MessageTypes.Default;
private messageTypes = MessageTypes; //very important to declare here
constructor() { }
ngOnInit() { }
}
I think your problem must lie somewhere else. I recreated your scenario using the [ngClass] binding with an enum and it works fine for me:
[ngClass] ="{'class-name': var === enum,...}"
Is your template in the second case on a separate .html file and not in the first case? I've had problems where a private variable on my component file can't be read by the template file.