Getting abnormal result from angular model class function - javascript

I have a simple Invoicing & Inventory management application where we display the list of invoices under a particular customer which you can see in the img below. Also, when we click on View/Edit button the bootstrap modal pop-up is displayed with the selected invoice details. The problem I notice here is, the result of Grand Total function is wrong which I being a novice in angular unable to figure out.
I also read about the impact of function calls in angular template here https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496 because I'm calling the functions in my template code below. I tired the pipe soultion shown in the article but that didn't help me.
Looking for someone who can help me figure out the Grand Total issue. Any help is really appreciated. Kindly look at the images & code below for the reference.
1. Invoice lists under customer(Raj Kapoor)
2. Invoice Bootstrap Modal Pop-up (On click of View/Edit Button)
Note: Here, assume that we've edited invoice no 4
3. Template code(admin-invoices.component.html)
Note: I'm providing only the absolute essentials part of code to keep it simple to understand. Focus only on 2 things: 1. View/Edit button function call 2. Invoice amount calculations section
<div class="container">
<div class="row g-0 mb-4">
<!-- Customer Name & Total Balance code goes here -->
</div>
<table class="table table-hover border" datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger">
<thead class="thead-dark">
<tr>
<!-- Table heading goes here like Date, Invoice No.,
Amount, Balance Due, Operation, Action etc.
-->
</tr>
</thead>
<tbody>
<tr *ngFor="let invoice of customerInvoices">
<td class="text-center">{{invoice.invoiceDate | date:'dd/MM/yyyy'}}</td>
<td class="text-center">{{invoice.invoiceNumber}} </td>
<td class="text-center">{{invoice.invoiceTotal | currency:'INR':true}}</td>
<td class="text-center">{{invoice.invoiceTotal - invoice.paymentTotal | currency:'INR':true}}</td>
<td class="text-center">
<button type="button" class="btn btn-success" (click)="setInvoice(invoice)" data-bs-toggle="modal" data-bs-target="#invoiceDetailsModal">
View/Edit Details
</button>
</td>
</tr>
</tbody>
</table>
<!-- View/Edit Details: Bootstrap Modal Starts -->
<div class="modal fade" id="invoiceDetailsModal" tabindex="-1" aria-labelledby="invoiceDetailsLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<!-- Shop Details header goes here. -->
</div>
<div class="modal-body pt-0">
<div class="row">
<!-- To Customer, Date and Invoice No goes here-->
</div>
<!-- Items List Section -->
<div class="row">
<div class="items-table">
<div class="row header">
<!-- Table Heading: Sr. No, Item Details,
Qty, Rate, Amt goes here.. -->
</div>
<ng-container *ngFor="let invItem of invoice.invoiceItem; let i=index;">
<!-- Table data(list of all items) goes here-->
</ng-container>
<!-- Invoice amount calculations section -->
<div class="row">
<div class="col-4">
<!-- sub total row -->
<div class="row" style="border: 1px solid green;">
<div class="col-6 text-end">Sub Total:</div>
<div class="col text-center">{{invoiceSubTotal() | currency:'INR':true}}</div>
</div>
<!-- discount row -->
<div class="row">
<div class="col-6 ps-0 pe-0 text-end">
Discount:
<select class="form-select p-0" style="display: inline; width:60px;" [(ngModel)]="invoice.discountLabel" name="discountLabel" id="discountLabel" aria-label="Select discount option">
<option value="%">%</option>
<option value="Rs">Rs</option>
</select>
</div>
<div class="col text-center">
<input [(ngModel)]="invoice.discountTotal" name="discountTotal" type="number" style="border: 1px solid #ddd; width:100px">
</div>
</div>
<!-- freight row -->
<div class="row">
<div class="col-6 text-end">Freight(Rs.):</div>
<div class="col text-center"><input [(ngModel)]="invoice.freight" name="freight" type="number" style="border: 1px solid #ddd; width:100px"></div>
</div>
<!-- grand total row -->
<div class="row">
<div class="col-6 text-end">Grand Total:</div>
<div class="col text-center">{{calculateGrandTotal() | currency: 'INR':true}}</div>
</div>
<!-- Received row -->
<div class="row">
<div class="col-6 text-end">Received:</div>
<div class="col text-center">
<input [(ngModel)]="invoice.paymentTotal" name="paymentTotal" type="number" style="border: 1px solid #ddd; width:100px">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" (click)="clearData()">Close</button>
<button type="button" class="btn btn-outline-secondary" (click)="updateInvoice()">
Update & Print
</button>
</div>
</div>
</div>
</div>
<!-- View/Edit Details: Bootstrap Modal Ends -->
4. Component Class(admin-invoices.component.ts)
#Component({
selector: 'app-admin-invoices',
templateUrl: './admin-invoices.component.html',
styleUrls: ['./admin-invoices.component.css']
})
export class AdminInvoicesComponent {
customerInvoices: Invoice[] = []; // to get list of all invoices under this customer
invoice: Invoice = new Invoice();
customerId: number;
customer: Customer = new Customer(); // To avoid initial undefined value of customer field
totalBalance: number = 0.0; // total balace of all invoices under this customer
itemsList: Item[] = [];
constructor(private route: ActivatedRoute,
private customerService: CustomerService,
private invoiceService: InvoiceService,
private itemService: ItemService) {
this.customerId = +(this.route.snapshot.paramMap.get('customerId')); // converting string to number using '+'
this.setCustomer();
this.setItems();
this.invoiceService.getCustomerInvoices(this.customerId)
.subscribe((invoices: Invoice[]) => {
this.customerInvoices = invoices.map(invoice => new Invoice(invoice));
this.calculateTotalBalance(); // get the total balace of all invoices under this customer
});
}
setCustomer() {
this.customerService.get(this.customerId)
.subscribe((customer: Customer) => this.customer = customer);
}
setItems() {
this.itemService.getAll()
.subscribe((items: Item[]) => this.itemsList = items);
}
calculateTotalBalance() {
this.customerInvoices.forEach(invoice => {
this.totalBalance = this.totalBalance + (invoice.invoiceTotal - invoice.paymentTotal);
});
}
setInvoice(invoice: Invoice) {
this.invoice = invoice;
}
addItem() {
this.invoice.addItem();
}
removeItem(item) {
this.invoice.removeItem(item);
}
invoiceSubTotal() {
return this.invoice.invoiceSubTotal();
}
calculateGrandTotal() {
return this.invoice._invoiceTotal;
}
updateInvoice() {
// update code goes here
}
}
5. Invoice Model class(invoice.ts)
export class Invoice {
invoiceId?: number;
invoiceNumber: number;
discountLabel: string = '';
discountTotal: number = 0.0;
freight: number = 0.0;
invoiceTotal: number = 0.0;
paymentTotal: number = 0.0;
invoiceDate: string;
narration: string = '';
customer: Customer;
invoiceItem: InvoiceItem[] = [];
description: string;
constructor(customer?: Customer, invoiceNumber?: number);
constructor(invoice: Invoice);
constructor(customerOrInvoice?: Customer | Invoice, invoiceNumber?: number){
if(!invoiceNumber) {
// Existing invoices
Object.assign(this, customerOrInvoice); // copy everything in current invoice object
} else {
// New invoice creation
this.invoiceDate = new Date().toISOString().substring(0,10);
this.customer = customerOrInvoice as Customer;
this.invoiceNumber = invoiceNumber;
this.invoiceItem.push({itemId: 0, quantity: 0, rate: 0}); // default invoice_item
}
}
addItem() {
this.invoiceItem.push({itemId: 0, quantity: 0, rate: 0}); // default invoice_item
}
invoiceSubTotal(): number {
let subTotal = 0.00;
this.invoiceItem.forEach(item => {
subTotal += (item.quantity * item.rate);
});
return subTotal;
}
get totalDiscount(): number {
if(this.discountLabel === '%') return ((this.discountTotal * this.invoiceSubTotal())/100);
return this.discountTotal; // Considering discountLable to either 'Rs' or not selected
}
get _invoiceTotal(): number {
this.invoiceTotal = (this.invoiceSubTotal() - this.totalDiscount) + this.freight;
return this.invoiceTotal;
}
}

First of all, I would like to thank #GRD or grdtechlab#gmail.com for collaborating with me over an email to help me with this problem. This was basically two way efforts to find out the root cause. But again credit goes to GRD.
Root Cause: So, the backend response object was the main issue and not the UI code. Because, when we query sequelize database to get the Decimal type data, then it converts the decimal values to string values. And, in the resultant object, we end up with the string type values instead of 'number' type(in javascript). This leads to abnormal results in our computation functions like _invoiceTotal( ) getter function above.
Read more here https://github.com/sequelize/sequelize/issues/8019
Solution: As mentioned in the above github issue, I changed the sequelize configuration to dialectOptions : {decimalNumbers: true} and this converted all the decimal values to number in the response object.
See the response object images below to understand this better.
1. Incorrect Response object (string values instead of number type)
2. Correct Response object (after changing sequelize configuration)

Related

Understanding how to use pagination in Bootstrap-vue

I have written the following code:
<div>
<div id="app" class="container">
<div class="grid">
<article v-for="tool in tools">
<div class="title">
<h3>{{capitalizeFirstLetter(tool.name)}}</h3>
</div>
<div class="description">
{{tool.description}}
</div>
<br>
<div>
<toggle-button :value=tool.status color="#82C7EB" :width=100 :height=30 :sync="true" :labels="{checked: 'Following', unchecked: 'Unfollowing'}" #change="onChange(tool)"/>
</div>
</article>
</div>
</div>
</div>
Recently I started using Bootstrap-Vue. I'm trying to figure out how to add pagination on the bottom.
I'm not sure how aria-controls works. My goal is to have 9 blocks of tools on each page. How should I add the pagination so I could move to the next 9 blocks of tools?
Since it wasn't clear if you needed client-side pagination or serverside i made an example of both in the snippet.
For simplicity I've made it 3 per page, but you could change it to 9.
The first one gets the initial page on load, and then calls the API every time the page changes by using a watcher, that calls a method with the new page which then retrieves that place and replaces our old data with the new.
The second one loads all the data on page load, and instead slices the data array based on the per_page property, so that only the items for that page is shown.
For this I've used a computed property which automatically updates based on the properties used inside it.
Both paginations have aria-controls defined with the id of the container of our page elements. This is used to tell screen readers what elements the pagination changes.
The classes row, col-*, border, mx-auto, h2 and text-center is classes used for styling and layout and isn't part of the actual solution, so you can freely change or remove them.
new Vue({
el: '#app',
computed: {
pagination2CurrentItems() {
const startIndex = (this.pagination2.current_page - 1) * this.pagination2.per_page;
const endIndex = startIndex + this.pagination2.per_page;
return this.pagination2.items.slice(startIndex, endIndex)
}
},
created() {
this.getPagination1Data()
this.getPagination2Data()
},
filters: {
capitalizeFirstLetter(value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
},
data() {
return {
pagination1: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
},
pagination2: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
}
}
},
methods: {
getPagination1Data(page = 1) {
fetch(`https://reqres.in/api/unknown?page=${page}&per_page=3`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination1.total_rows = data.total;
this.pagination1.items = data.data;
});
},
getPagination2Data() {
/*
This endpoint only has 12 items total,
so this will get all in one call
*/
fetch(`https://reqres.in/api/unknown?per_page=12`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination2.total_rows = data.total;
this.pagination2.items = data.data;
});
}
},
watch: {
'pagination1.current_page'(newPage) {
this.getPagination1Data(newPage)
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<div id="app" class="container">
<div id="tools_list_1" class="row">
<div class="col-12 h2 text-center">
Server pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination1.items">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination1.current_page"
:per-page="pagination1.per_page"
:total-rows="pagination1.total_rows"
aria-controls="tools_list_1"
>
</b-pagination>
</div>
</div>
<div id="tools_list_2" class="row">
<div class="col-12 h2 text-center">
Client pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination2CurrentItems">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination2.current_page"
:per-page="pagination2.per_page"
:total-rows="pagination2.total_rows"
aria-controls="tools_list_2"
>
</b-pagination>
</div>
</div>
</div>
If you are interested in server side pagination with url changes of query params (using vue-router in history mode) and you need browser history (back/forward) to work properly, you can check my answer to a similar question. I think it can be adapted to not use b-table.
Place pagination number in url Vuejs

How to edit passed data from parent to child component in Angular 4

I have parent component with all teams and child component for displaying each of team in box using *ngFor and #Input. All child components are displayed fine but when I want to change each of them opening modal I always get the data from first object in array of teams.
team.component.html
<app-team-box *ngFor="let team of teams" [team]="team"></app-team-box>
team-box.component.html
<div class="ibox">
<div class="ibox-title">
<h5>{{this.team.name}}</h5>
<a class="btn btn-xs" (click)="openEditTeamModal(this.team)">Edit</a>
</div>
</div>
<div class="modal fade" id="edit_team_modal">
<div class="modal-dialog">
<div class="modal-content">
<form role="form" (ngSubmit)="editTeam()" novalidate>
<div class="modal-body">
<div class="row" *ngIf="this.team">
<label>Team name:</label>
<input type="text" id="name" name="name" [(ngModel)]="this.team.name" #name="ngModel">
</div>
</div>
<div class="modal-footer">
<button type="submit">Save</button>
</div>
</form>
</div>
</div>
team-box.component.ts
export class TeamBoxComponent implements OnInit {
#Input() team;
constructor(private teamService: TeamService) {}
openEditTeamModal(selectedTeam): void {
this.team = selectedTeam;
$("#edit_team_modal").modal();
$(".modal").appendTo("html");
}
editTeam(): void {
this.teamService.update(this.team).subscribe((team: Response) => {
$("#edit_team_modal").modal("hide");
});
}
}
The problem is when I change first one everything is fine, modal is populated with name and after changing it's get saved. But when I click on edit button for example second team, in modal I get the data for first team. Why this.team is always referencing to first object in array of teams?

Conditionally tick the checkbox in modal dialog in Angular

I have a bootstrap modal dialog which contains some checkboxes. Each checkbox represents a category from the database and I need to tick the checkbox only if the user is interested on that category.
following is my html for the modal
<div *ngIf="_user" id="categoryModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-body" id="categoryDiv">
<div class="row">
<div *ngIf="_tenderCategories">
<span *ngFor="let category of _tenderCategories; let i = index;">
<div class="col-md-4">
<div class="checkbox">
<label>
<input id={{category.id}} type="checkbox" class="category-checkbox" [checked]="isUserInterested(category)" (change)="onCategoryCheckBoxClick(category)">
<p class="tender-category" style="cursor: pointer;">{{category.name}}</p>
</label>
</div>
</div>
</span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Submit</button>
</div>
</div>
</div>
</div>
As you can see, in order to tick the checkbox, I call the function isUserInterested(category).
The problem I face is, when the page reloads only, the function I mentioned gets called. The modal above appears when I click a link on my html page; but the function does not get called in that case. By the time I click the above mentioned link, the component has all the data required for the function to execute.
isUserInterested(category: Category) {
if (this._user.interestedCategories.indexOf(category) > -1) {
// if the _user.interestedCategories list contaians the category, that category should be shown as marked
console.log("Category "+category.id+" interested");
return true;
} else {
return false;
}
}
Why I am not getting the expected behavior? How can I get this implemented in the way I expect?
You should be using ngModel to achieve this as below,
<input id={{category.id}} type="checkbox" class="category-checkbox"
[ngModel]="isUserInterested(category)"
(ngModelChange)="onCategoryCheckBoxClick(category)">
Update 1 : Inside Modal dialog
The child model should contain the users object as below,
#Input() users?:any[];
Method will be as
isUserInterested(c){
if(this.category.interestedCat.indexOf(c) > -1){
console.log(c)
return true;
}
else {
return false
}
}
Updated LIVE DEMO

Push $scope with ng-click AngularJS

I want the user to press the $scope.getJobList button to get the value of the $scope.getIdJobs variable. I am trying to solve this with the push functionality but it is not working.
Am I doing something wrong?
jsfidle
Example
$scope.getIdJobs = function(getIdJobs) {
$scope.getIdJobs = $filter('filter')( $scope.products, {id:getIdJobs})[0];
$scope.getJobList = function(currObj){
$scope.workflows[0].Steps.push($scope.getIdJobs); // It will add a new step ($scope.newStep) in first workflow's Steps
console.log($scope.workflows);
//$scope.workflows.push($scope.getIdJobs); // line that changed
$scope.workflows = job_detail.addJob(currObj)
console.log($scope.workflows);
};
//console.log("Estou dentro do getidJobs " + getIdJobs);
}
html
<article dir-paginate="product in filteredJobs | filter:searchTerm | orderBy: sorting.order:sorting.direction | itemsPerPage:5" id="{{product.id}}" class="productlist product col-sm-12">
<div class="inner-content">
<div class="clearfix">
<div class="col-md-8 col-sm-8 col-xs-8">
<h4 class="productname" name="{{product.Categoria}}"><a data-ng-href="#/job/{{product.id}}" ng-click="getIdJobs(product.id)">{{product.Title}}</a></h4>
<!--<h4 class="productname" name="{{product.Categoria}}"><a data-ng-href="#/job/{{filteredJobs.indexOf(product)}}">{{product.Title}}</a></h4>-->
<h4 style="color: gray;"><a data-ng-href="#/empresa">{{product.Empresa}}</a></h4>
</div>
</div>
</div>
</article>

Angularjs sorting using orderBy is not working for me

I am trying to sort my home list based on ratings, pricing and total counts. Unfortunately not working for me, I am new in angularjs.
Here I have my html code. Actually it is sorting the things but not in order.
->When user clicks on price btn he should get home with highest price first then lower , lowest etc.(5000,4000,3000) if again he clicks then order must be (3000,4000,5000). Similar things for total counts and rating. I have also created plunker to edit your suggestions. please edit and provide me the working link. Here is demoForEdit.
//html code
<body ng-controller="MainCtrl as ctrl">
<div class="col-md-offset-2">
<button ng-click="orderByPopularity='total_counts'; reverseSort = !reverseSort" class="btn btn-sm btn-info">sort on total counts </button>
<button ng-click="orderByPopularity='price'; reverseSort = !reverseSort" class="btn btn-sm btn-danger">sort on Price</button>
<button ng-click="orderByPopularity='avg'; reverseSort = !reverseSort" class="btn btn-sm btn-success">sort on ratings</button>
</div>
<br>
<div ng-repeat="home in homes | orderBy:orderByPopularity:reverseSort">
<div class="panel panel-primary ">
<div class="panel-heading">
Home name: {{home.home_name}}
</div>
<div class="panel-body">
<div class="panel panel-body">
<table>
<p>total counts:{{home.total_counts}}</p>
<p>city:{{home.city}}</p>
<p>Price:{{home.price}}</p>
<div ng-repeat="rate in rating">
<!-- looping inside ng-repeat rating is seprate data but has home id-->
<div ng-if="rate.home_id==home.id">
Home raintgs:{{rate.avg}}
</div>
</div>
</div>
</div>
</div>
</div>
//My app.js code
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.homes=[{"id":"48","home_name":"Swastik","city":"Banglore","zipcode":"888888","total_counts":"1","price":"5000"},
{"id":"49","home_name":"Khushboo","city":"Chennai","zipcode":"456786","total_counts":"17","price":"3333"},
{"id":"50","home_name":"Raj","city":"Hydrabad","zipcode":"123455","total_counts":"2","price":"20000"}];
$scope.orderByPopularity ='total_counts';
$scope.reverseSort = true;
$scope.rating=[{"home_id":"48","avg":"3.5"},
{"home_id":"49","avg":"2"},
{"home_id":"50","avg":"4"}
];
});
It does sort, but your total_counts values are strings, so it orders them lexicographically. If you want to order by number, which I assume you do, you need to remove the quotes and have their values as the number themselves, i.e.: "total_counts":17 etc..
EDIT: Modified so you could keep your values as strings (inside the custom sorting function).
As for avg rating ordering, you can't do that since your ordering is on $scope.homes, and this array's objects don't have the avg property, that is a property for your other array - $scope.ratings.
To sort by that property, you'll have to create your own custom function.
$scope.myValueFunction = function(o) {
if ($scope.orderByPopularity == "avg") {
var obj;
for (var i = 0; i < $scope.rating.length; i++) {
if ($scope.rating[i].home_id == o.id) {
obj = $scope.rating[i]
}
}
return obj.avg;
}
else {
return parseInt(o[$scope.orderByPopularity]);
}
};
And in the HTML:
<div ng-repeat="home in homes | orderBy:myValueFunction:reverseSort">
Look at this Plunker as for a demonstration.

Categories

Resources