hi am developing a rent calculator app in angular which take three inputs: rent, amount of rent increase per year; and number of years to calculate rent for. and give the result which is working fine. but i am having trouble to show that data in table as am new to angular i dont know the way to do that.
Html file
<div class="container">
<h1>Rent Calculator</h1>
<input type="number" placeholder="Enter your total rent" [(ngModel)]="rent" />
<input type="number" placeholder="Rent increase per Year" [(ngModel)]="increase">
<input type="number" placeholder="Number of Total Years" [(ngModel)]="years">
<button type="button" (click)="calculate()"> Calculate </button>
<br>
<table id="users">
<tr>
<th *ngFor="let column of headers">
{{column}}
</th>
</tr>
<tr *ngFor="let row of total">
<td *ngFor="let column of headers">
{{row[column]}}
</td>
</tr>
</table>
<!-- <h4 *ngFor="let r of total;let i=index">
Year {{i+1}} = {{r}} Rs
</h4> -->
//it works with above commented code
</div>
ts file
export class TableComponent implements OnInit {
headers: any = ['years', 'baseRent', 'New_rent'];
ngOnInit(): void {
}
constructor() { }
increase: any;
years: any;
rent: any;
total: any[] = []; //declare an array
calculate() {
// debugger;
this.total = [];
let previousRent = this.rent;
this.total.push(previousRent);
for (let i = 1; i < this.years; i++) {
const CurrentRent = previousRent * (1 + this.increase / 100);
previousRent = CurrentRent;
this.total.push(Math.round((CurrentRent + Number.EPSILON) * 100) / 100);
}
}
}
The first thing you need to learn is to avoid using any as your type. Angular uses TypeScript so we can leverage the type system.
I created 2 types: RentHeader and RentResult to represent the real world objects we need.
RentHeader will have a text for displaying on the table, and the key where we will use it to get the correct property from the RentResult.
RentResult have 3 properties which represent all the values you need: years, baseRent and newRent.
We use _rentResults array to store the object in the calculation function.
example.component.ts
//... import stuff here.
type RentHeader = {
readonly text: string;
readonly key: string;
};
type RentResult = {
readonly years: number;
readonly baseRent: number;
readonly newRent: number;
};
#Component({
...
})
export class ExampleComponent implements Oninit {
private _rentResults!: RentResult[];
public get rentResults() {
return this._rentResults;
}
public readonly headers: RentHeader[] = [
{text: 'Years', key: 'years'},
{text: 'Base Rent', key: 'baseRent'},
{text: 'New Rent', key: 'newRent'}
];
...
public calculate() {
// do your calculation here and replace the 0 with your calculation.
const result = {
years: 0,
baseRent: 0,
newRent: 0
};
this._rentResults.push(result);
}
}
example.component.html
...
<table>
<tr>
<th *ngFor="let header of headers">
{{header.text}}
</th>
</tr>
<tr *ngFor="let row of rentResults">
<td *ngFor="let header of headers">
{{row[header.key]}}
</td>
</tr>
</table>
...
Related
As you can see in this stack blitz example min and max validation is firing
https://stackblitz.com/edit/angular-mat-form-field-icrmfw
But in the below stack blitz, I have made an array of the same controls , but the validation is not firing
https://stackblitz.com/edit/angular-mat-form-field-hmmm69
<form class="form" [formGroup]="configWeightage">
<table class="DSA_custom-table col-md-6">
<thead>
<tr>
<th scope="col">Category</th>
<th scope="col">Weightage</th>
</tr>
</thead>
<tbody>
<ng-container>
<tr
*ngFor="let order of configWeightage.get('weightageFormArray').controls; let i = index"
formArrayName="weightageFormArray"
>
<td>{{j_s_weightage[i].name}}</td>
<td>
<div class="example-container">
<mat-form-field>
<input
matInput
type="number"
class="example-right-align"
formControlName="i"
id="weightage"
required
/>
<mat-error
*ngIf="configWeightage.get('weightageFormArray').hasError('min')"
>Please enter greater than 0</mat-error
>
<mat-error
*ngIf="configWeightage.get('weightageFormArray').hasError('max')"
>Please enter less than 100</mat-error
>
</mat-form-field>
</div>
</td>
<td>%</td>
</tr>
</ng-container>
<tr>
<td>Total Weightage</td>
<td class="text-center"><label>{{weightage}}</label></td>
</tr>
<tr>
<td></td>
<span class="DSA_wb_caption text-primary text-right"
>Sum of weightage should be 100.</span
>
</tr>
</tbody>
</table>
</form>
This is my class file
import { Component } from '#angular/core';
import {
FormBuilder,
FormGroup,
Validators,
FormControl,
FormArray,
} from '#angular/forms';
/** #title Form field with prefix & suffix */
#Component({
selector: 'form-field-prefix-suffix-example',
templateUrl: 'form-field-prefix-suffix-example.html',
styleUrls: ['form-field-prefix-suffix-example.css'],
})
export class FormFieldPrefixSuffixExample {
configWeightage: FormGroup;
formBuilder: FormBuilder;
hide = true;
services: FormArray;
weightageValueArray = [];
j_s_weightage = [];
attrWeightage: any = { name: '' };
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.configWeightage = this.fb.group({
weightageFormArray: new FormArray([], [Validators.required, Validators.min(0), Validators.max(100)])
});
this.attrWeightage.name = "flexiblity";
this.j_s_weightage.push(this.attrWeightage);
this.attrWeightage = { name: '' };
this.attrWeightage.name = "flexiblity2";
this.j_s_weightage.push(this.attrWeightage);
this.weightageValueArray.push(60);
this.weightageValueArray.push(40);
// console.log(this.configWeightage)
this.services = this.configWeightage.get('weightageFormArray') as FormArray;
let dbSize = this.services.length;
for (; 0 < dbSize; dbSize--) {
this.services.removeAt(dbSize - 1);
}
this.j_s_weightage.map((o, i) => {
const control = new FormControl(0);
(this.configWeightage.controls.weightageFormArray as FormArray).push(control);
});
}
}
Okay found your issue:
formControlName="i" in inspect element formcontrolname was i, and not a number. use angular string interpolation [formControlName]="i" or formControlName="{{i}}
here you are checking whole array, not individual controle. it still works though if any of inputs has error. but will show error for both inputs.
<mat-error *ngIf="configWeightage.get('weightageFormArray').hasError('min')">Please enter greater than 0</mat-error>
<mat-error *ngIf="configWeightage.get('weightageFormArray').hasError('max')">Please enter less than 100</mat-error>```
<!--Change above to -->
<mat-error *ngIf="configWeightage.get('weightageFormArray').controls[i].hasError('min')">Please enter greater than 0</mat-error>
<mat-error *ngIf="configWeightage.get('weightageFormArray').controls[i].hasError('max')">Please enter less than 100</mat-error>
in ts file: instead of giving validators to formarray, give it to individual controller:
// change this
this.configWeightage = this.fb.group({
weightageFormArray: new FormArray([], [Validators.required, Validators.min(0), Validators.max(100)])
});
// to this
this.configWeightage = this.fb.group({
weightageFormArray: new FormArray([])
});
// and this
this.j_s_weightage.map((o, i) => {
const control = new FormControl(0);
(this.configWeightage.controls.weightageFormArray as FormArray).push(control);
});
// to this
this.j_s_weightage.map((o, i) => {
const control = new FormControl(0,[Validators.required, Validators.min(0), Validators.max(100)]);
(this.configWeightage.controls.weightageFormArray as FormArray).push(control);
});
I have a reactive angular form. The form is a simple scoreboard. I would like to be able to add the rounds up together to make up a score. So when a round is finished and the score for that round is entered, it will sum up all the total rounds into that players score.
This is what I have so far:
form.component.html
<section [formGroup]="board">
<table class="table table-bordered" formArrayName="scoreboard">
<tr>
<th colspan="2">Scoreboard:</th>
<th width="150px">
<button type="button" (click)="addPlayer()" class="btn btn-primary">
Add Additional Players
</button>
</th>
</tr>
<tr
*ngFor="let quantity of scoreboard().controls; let i = index"
[formGroupName]="i"
>
<td>
Name :
<input type="text" formControlName="name" class="form-control" />
</td>
<td>
Round1
<input type="text" formControlName="round1" />
Round2
<input type="text" formControlName="round2" />
</td>
<td>
Score:
<input type="text" formControlName="score" class="form-control" />
</td>
</tr>
</table>
{{ this.board.value | json }}
</section>
form.component.ts
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, FormArray } from '#angular/forms';
#Component({
selector: 'app-basic-form',
templateUrl: './basic-form.component.html',
styleUrls: ['./basic-form.component.css']
})
export class BasicFormComponent {
board: any;
constructor (private fb: FormBuilder) {
this.board = this.fb.group({
scoreboard: this.fb.array([this.game(), this.game()])
});
}
scoreboard() : FormArray {
return this.board.get("scoreboard") as FormArray
}
game(): FormGroup {
return this.fb.group({
name: '',
score: '',
round1: [''],
round2: ['']
})
}
addPlayer() {
this.scoreboard().push(this.game());
}
onSubmitForm () {
console.log(this.board.value);
}
}
Really just starting to learn Angular and wanted to try something on my own. If you could be detailed or show me somewhere I can learn additional information about the solution that would be great!
You can listen to your group's controls value changes, and update the score accordingly. Your form array is made of list of form groups (the return value of game function), these groups have controls which holds the values for their respective index, so when you create a form group, you can listen to round1 and round2 changes, and update score accordingly. For example, what I did below is using combineLatest function to merge both value changes observables (round1 + round2) and then update the score accordingly. So now every new form group pushed to your form array will have its own value change listener and will update the score.
game(): FormGroup {
const gameGroup = this.fb.group({
name: '',
score: '',
round1: [''],
round2: ['']
});
combineLatest(gameGroup.get('round1').valueChanges,gameGroup.get('round2').valueChanges)
.subscribe(([r1,r2]) => gameGroup.get('score').setValue(Number(r1) + Number(r2)));
return gameGroup;
}
html code:
<tr *ngFor = "let row of food;">
<td *ngFor ="let col of headers;">
{{row[col]}}
</td>
</tr>
angular component code:
export class FirstComponent implements OnInit {
public header;
food=[{Project_title: 'Avocado',
Benefit: true,
Completion_date:'01/10/2022',
Project_description:160,
Project_cost:'some long text some long text some long text',
Accomplishments:'something',
help_needed:'Yes',
Project_leader:'XYZ',
Project_Manager:'ABC',
Savings:'Some very long text Some very long text Some very long text Some very long
text Some very long text Some very long text',
complete_status:'Completed'
}]
constructor() { }
ngOnInit(): void {
this.headers=Object.keys(this.food[0]); \\this is used to assign all the keys in an array
});
}
}
I'm trying to extract each object from the array from food and print the values of array based on their corresponding key value.
Change your code to this:
in first.component.html
<table>
<tr>
<th *ngFor="let column of headers">
{{ column }}
</th>
</tr>
<tr *ngFor="let row of rows">
<td *ngFor="let column of headers">
{{ row[column] }}
</td>
</tr>
</table>
in first.component.ts
export class FirstComponent implements OnInit {
headers = [];
rows = [
{
Project_title: 'Avocado',
Benefit: true,
Completion_date: '01/10/2022',
Project_description: 160,
Project_cost: 'some long text some long text some long text',
Accomplishments: 'something',
Help_needed: 'Yes',
Project_leader: 'XYZ',
Project_Manager: 'ABC',
Savings:
'Some very long text Some very long text Some very long text Some very long text Some very long text Some very long text',
complete_status: 'Completed',
},
];
constructor() {}
ngOnInit() {
this.headers = Object.keys(this.rows[0]);
}
Here is a working example: https://stackblitz.com/edit/angular-ivy-zmucpt?file=src/app/first.component.ts
I am a new student of typescript/angular2+ and I am developing a website to practice my studies.
I already made the view and now I am creating the update page but I have some problems to finish.
My update page has 2 selects and 1 table with 1 or more checkboxes.
In select I'm using (valueChanged) to get the select id
in Table I'm using o [(ngModel)] to get the select all
In summary I do this:
get the select id 1
get id of 1 or more products by selecting the checkbox
get the id of select 2
When I select the checkbox it is returning this array but I only need the id that is in the array to be able to send it to the backend, but it also has to be an array because my backend is configured to accept a list. I'm using DTO (Data Transfer Object).
[{"id": 2225, "name": "Joy Price", "isSelected": true}, {"id": 2226, "name": "Ronnie Jordan", "isSelected": true}]
In summary
I'm needing get the checkedCategoryList ids and create a new array to send to the idsProducts that is in the model.
What is the best way to do this?
at the moment I am not able to progress in this problem
update.component.html
<app-vo-modal keyTitle="Update" keySubtitle="" >
<form [formGroup]="form">
<div class="form-group">
<label class="required">Choose product</label>
<app-vo-select [itens]="productItens" (valueChanged)="showValues($event)" formControlName="product"></app-vo-select>
</div>
</form>
<div>
<tbody>
<table class="table table-striped text-center">
<thead>
<tr>
<th><input type="checkbox" [(ngModel)]="isMasterSel" name="name" value="h1" (change)="checkUncheckAll()"/>
</th>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr class="text-center" *ngFor="let prod of showTable; index as i">
<td>
<input type="checkbox" [(ngModel)]="prod.isSelected" name="name" value="{{prod.id}}" (change)="isAllSelected()"/>
</td>
<td>{{prod.id}}</td>
<td>{{prod.name}}</td>
</tr>
</tbody>
</table>
</div>
<form [formGroup]="form">
<div class="form-group">
<label class="required">Choose product the substitute</label>
<app-vo-select [itens]="productItens" formControlName="product"></app-vo-select>
</div>
</form>
</app-vo-modal>
update.component.ts
export class UpdateComponent implements OnInit {
products: UpdateModelComponent[] = [];
showTable: UpdateModelComponent[] = [];
productItens: SelectOption[];
form: FormGroup;
isMasterSel: boolean;
categoryList: any;
checkedCategoryList: any;
#Output()
valueChanged: EventEmitter<any> = new EventEmitter<any>();
#Output()
dataSave = new EventEmitter<any[]>();
constructor(
private service: UpdateService,
private formBuilder: FormBuilder
) {
this.form = this.formBuilder.group({
product: []
});
showValues(id: number) {
this.service.childproduct(id).
subscribe((element: EntityBase<UpdateModelComponent>) => {
this.showTable = element.content;
});
}
ngOnInit() {
this.service.parentProduct().
subscribe((element: EntityBase<UpdateModelComponent>) => {
this.products = element.content;
console.log(this.products);
this.productItens = this.products.map(t => ({ id: t.id.toString(), text: t.nome }));
});
}
onModalConfirm() {
const a = new UpdateModelComponent();
this.service.save(a).subscribe(r => {
});
}
checkUncheckAll() {
for (let i = 0; i < this.showTable.length; i++) {
this.showTable[i].isSelected = this.isMasterSel;
}
this.getCheckedItemList();
}
isAllSelected() {
this.isMasterSel = this.showTable.every(function(item: any) {
return item.isSelected === true;
});
this.getCheckedItemList();
}
getCheckedItemList() {
this.checkedCategoryList = [];
for (let i = 0; i < this.showTable.length; i++) {
if (this.showTable[i].isSelected) {
this.checkedCategoryList.push(this.showTable[i]);
}
}
this.checkedCategoryList = JSON.stringify(this.checkedCategoryList);
console.log(this.checkedCategoryList);
> result : array/object of selected products
> result : [{"id":2225,"name":"Joy Price", "isSelected":true}, {"id":2226,"name":"Ronnie Jordan", "isSelected":true}]
}
}
update.model.ts
export class UpdateModelComponent {
public id: number;
public name: string;
public components: UpdateModelComponent[] = [];
public isSelected = false;
public idsProducts: [];
public currentProduct: number;
public newProduct: number;
}
update.service.ts
export class UpdateService {
PATH = '/server/api/product';
constructor(protected httpClient: HttpClient) { }
childproduct(page?: Paging): Observable<EntityBase<UpdateModelComponent>> {
if (!page) {
page = new Paging();
}
return this.httpClient.get<EntityBase<UpdateModelComponent>>(`${this.PATH}/${paginacao.toString()}`);
}
parentProduct(id: number): Observable<EntityBase<UpdateModelComponent>> {
return this.httpClient.get<EntityBase<UpdateModelComponent>>(`${this.PATH}/v2/${id}`);
}
save(components: UpdateModelComponent): Observable<any> {
return this.httpClient.put<any>(`${this.PATH}`, components);
}
}
update in 9-9-2021
you can use Set
example:-
component ts
uniqueIdsUsingSet: Set<number> = new Set();
prods = [
{ id: 2225, name: 'Joy Price' },
{ id: 2226, name: 'Jordan Ronnie ' },
{ id: 2227, name: 'Price Jordan' },
{ id: 2228, name: 'Ronnie Joy' },
];
uniqueArryUsingSet(item: HTMLInputElement): void {
if(item.checked) {
this.uniqueIdsUsingSet.add(+item.id);
} else {
this.uniqueIdsUsingSet.delete(+item.id);
}
}
component HTML
<ng-container *ngFor="let prod of prods">
<div>
<label [for]="prod.id">
{{prod?.name}}
</label>
<input
[id]="prod.id"
type="checkbox"
[value]="prod.id"
#item
(change)="uniqueArryUsingSet(item)"
/>
</div>
</ng-container>
old answer
in Component ts file
uniqueIds: any[] = [];
prods = [
{ id: 2225, name: 'Joy Price' },
{ id: 2226, name: 'Jordan Ronnie ' },
{ id: 2227, name: 'Price Jordan' },
{ id: 2228, name: 'Ronnie Joy' },
];
makeUniqueArrayItems(item: HTMLInputElement): void {
const itemIndex = this.uniqueIds.indexOf(item.id);
!item.checked
? this.uniqueIds.splice(itemIndex, 1)
: this.uniqueIds.push(item.id);
this.uniqueIds = [...new Set(this.uniqueIds)];
}
in component HTML
<ng-container *ngFor="let prod of prods">
<div>
<label [for]="prod.id">
{{prod?.name}}
</label>
<input
[id]="prod.id"
type="checkbox"
[value]="prod.id"
#c
(change)="makeUniqueArrayItems(c)"
/>
</div>
</ng-container>
<div *ngIf="uniqueIds?.length">
<h3>
values
</h3>
{{uniqueIds}}
</div>
example on stackblitz https://stackblitz.com/edit/angular-uniqu-select-arr?file=src/app/app.component.html
I'm trying to filter a default value if no result is found on my query.
I tried using ng-template, but I simply don't manage to do it.
Rather than writing, here are images to better understand :
This is my successful filter : it correctly shows my datas once I filter them in the search box.
However if I try to enter an invalid data as done below :
It simply returns me an empty html table.
Which is not what I'd like to achieve. I'd like it instead to show a message saying : no data found.
How can I do it please?
And here is my source code :
Component :
import {Component, OnInit} from '#angular/core';
import {IProduct} from './product';
#Component({
selector: 'pm-products',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
///////////////////////////////////// PROPERTIES //////////////////////////////////////
// String Interpolation
pageTitle = 'Product List';
// Property binding
imageWidth = 50;
imageMargin = 2;
// Event binding
showImage = false;
// Two-way binding
// listFilter = 'cart';
// Filter products
private _listFilter: string;
// Filter Products Array
filteredProducts: IProduct[];
///////////////////////////////////// CONSTRUCTOR //////////////////////////////////////
constructor() {
this.filteredProducts = this.products;
this.listFilter = 'cart';
}
/////////////////////////////////// GETTERS/SETTERS ///////////////////////////////////
get listFilter(): string {
return this._listFilter;
}
set listFilter(value: string) {
this._listFilter = value;
/***
* If there is a list of filtered value, show the list of the filtered values => this.performFilter(this.listFilter)
* Else, (if there is no filtered) return the whole set of products
*/
this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products;
}
/////////////////////////////////////// METHODS ///////////////////////////////////////
// Get Products
products: IProduct[] = [
{
productId: 2,
productName: 'Garden Cart',
productCode: 'GDN-0023',
releaseDate: 'March 18, 2019',
description: '15 gallon capacity rolling garden cart',
price: 32.99,
starRating: 4.2,
imageUrl: 'assets/images/garden_cart.png'
},
{
productId: 5,
productName: 'Hammer',
productCode: 'TBX-0048',
releaseDate: 'May 21, 2019',
description: 'Curved claw steel hammer',
price: 8.9,
starRating: 4.8,
imageUrl: 'assets/images/hammer.png'
},
];
performFilter(filterBy: string): IProduct[] {
/**
* filterBy result => to lower case. => case insensitive comparison.
* Return a new array of the filtered productS by the product name,
* by checking if that product name given is an index of the an element in the product array.
*/
filterBy = filterBy.toLowerCase(); // 1.
return this.products.filter((product: IProduct) => product.productName.toLowerCase().indexOf(filterBy) !== - 1); // 2.
}
toggleImage = (): void => {
this.showImage = !this.showImage;
}
////////////////////////////////// LIFECYCLE HOOKS ///////////////////////////////////
ngOnInit(): void {
console.log('hello');
}
}
HTML
<div class="card">
<div class="card-header">{{pageTitle}}</div>
<!-- CARD -->
<div class="card-body">
<div class="row">
<div class="col-md-2"> Filter by:</div>
<div class="col-md-4">
<input [(ngModel)]="listFilter" type="text"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h4>Filtered by: {{listFilter}} </h4>
</div>
</div>
<!-- ./CARD -->
<!-- TABLE -->
<div class="table-responsive">
<table *ngIf="products && products.length" class="table">
<thead>
<tr>
<th>
<button (click)="toggleImage()" class="btn btn-primary">{{showImage ? "Hide" : "Show"}} image</button>
</th>
<th>Product</th>
<th>Code</th>
<th>Available</th>
<th>Price</th>
<th>5 Star Rating</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of filteredProducts">
<td><img *ngIf="showImage" [src]="product.imageUrl" [title]="product.productName"
[style.width.px]="imageWidth" [style.margin.px]="imageMargin" alt=""></td>
<td>{{product.productName}}</td>
<td>{{product.productCode | lowercase | convertToSpaces: '-'}}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency: 'EUR':'symbol':'2.2-2'}}</td>
<td>{{product.starRating}}</td>
</tr>
</tbody>
</table>
</div>
<!-- ./TABLE -->
</div>
</div>
Please take care of yourself.
Maybe just include an *ngIf="!filteredProducts.length" wherever you'd like to show your message.
ex.
<tr *ngFor="let product of filteredProducts">
//Your table stuff
</tr>
<tr *ngIf="!filteredProducts.length">
<td colspan="6">No data found</td>
</tr>
You have to add tr contains string no data found after ngfor tr
<tr *ngFor="let product of filteredProducts">
<td><img *ngIf="showImage" [src]="product.imageUrl" [title]="product.productName"
[style.width.px]="imageWidth" [style.margin.px]="imageMargin" alt=""></td>
<td>{{product.productName}}</td>
<td>{{product.productCode | lowercase | convertToSpaces: '-'}}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency: 'EUR':'symbol':'2.2-2'}}</td>
<td>{{product.starRating}}</td>
</tr>
<tr *ngIf="filteredProducts.length === 0 " >
<td colspan="6" >Your message here </td>
</tr>
You can simply add a row that appears only when the list is empty and the search bar has any value. like this:
<tr *ngFor="let product of filteredProducts">...</tr>
<tr col-span ="6" *ngIf="!filteredProducts.length && listFilter.length"> uh oh,
could not find what you searched for</tr>