var myApp = angular.module('RealEstateApp', []);
Date.prototype.yyyymmdd = function() {
var yyyy = this.getFullYear().toString();
var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
var dd = this.getDate().toString();
return yyyy + '-' + (mm[1]?mm:"0"+mm[0]) + '-' + (dd[1]?dd:"0"+dd[0]);
};
myApp.directive("percent", function($filter){
var p = function(viewValue){
var m = viewValue.match(/^(\d+)\/(\d+)/);
if (m != null)
return $filter('number')(parseInt(m[1])/parseInt(m[2]), 4);
return $filter('number')(parseFloat(viewValue)/100, 4);
};
var f = function(modelValue){
return $filter('number')(parseFloat(modelValue)*100, 2);
};
return {
require: 'ngModel',
link: function(scope, ele, attr, ctrl){
ctrl.$parsers.unshift(p);
ctrl.$formatters.unshift(f);
}
};
});
myApp.controller('RealEstateController',['$scope', function($scope) {
// ------- MODELS --------
// Month number, amortization date, interest, principal, principal balance
$scope.Amortizations = [];
$scope.TotalContractPrice = 4000000;
$scope.DownPaymentPercent = .2;
$scope.DownPayment = 0;
$scope.InstallmentPrincipal = 0;
$scope.TotalInterest = 0;
$scope.TotalPrincipal = 0;
$scope.TotalAmortization = 0;
$scope.NumberOfYearsToPay = 5;
$scope.NumberOfMonthsToPay = 0;
$scope.PaymentModeInMonths = 1; // monthly
$scope.InterestRatePerAnnum = 0.21;
$scope.PeriodicInterestRate = 0;
$scope.AmortizationFactor = 0;
$scope.AmortizationAmount = 0;
$scope.AmortizationStartDate = new Date('Feb 28, 2013');
$scope.AmortizationStartDateText = $scope.AmortizationStartDate.yyyymmdd();
// determine payment terms based on budget
$scope.DeterminePaymentTerms = false;
$scope.Budget = 0;
$scope.AdvancedPayments = [
{ MonthNumber: null, Payment: null }
];
// ------- CONTROLLER's ACTIONS -----------
$scope.ComputeYears = function() {
$scope.NumberOfYearsToPay = $scope.NumberOfMonthsToPay / 12;
}
$scope.ComputeFromTotalContractPrice = function() {
$scope.ComputeDownPayment();
$scope.ComputeInstallmentPrincipal();
$scope.ComputeAmortizationFactor();
}
$scope.ComputeFromYears = function() {
$scope.NumberOfMonthsToPay = $scope.NumberOfYearsToPay * 12;
$scope.ComputeAmortizationFactor();
};
$scope.ComputeFromMonths = function() {
$scope.ComputeYears();
$scope.ComputeAmortizationFactor();
};
$scope.ComputeFromPaymentModeInMonths = function() {
$scope.ComputeAmortizationFactor();
};
$scope.ComputeDownPaymentPercent = function() {
$scope.DownPaymentPercent = $scope.DownPayment / $scope.TotalContractPrice;
};
$scope.ComputeInstallmentPrincipal = function() {
$scope.InstallmentPrincipal = $scope.TotalContractPrice - $scope.DownPayment;
};
$scope.ComputeFromDownPayment = function() {
$scope.ComputeDownPaymentPercent();
$scope.ComputeInstallmentPrincipal();
if ($scope.DeterminePaymentTerms) {
$scope.ComputeFromAmortizationAmount();
}
else {
$scope.ComputeAmortizationFactor();
}
};
$scope.ComputeDownPayment = function() {
$scope.DownPayment = $scope.DownPaymentPercent * $scope.TotalContractPrice;
}
$scope.ComputeFromDownPaymentPercent = function() {
$scope.ComputeDownPayment();
$scope.ComputeInstallmentPrincipal();
$scope.ComputeAmortizationFactor();
};
$scope.ComputeFromInstallmentPrincipal = function() {
$scope.DownPayment = $scope.TotalContractPrice - $scope.InstallmentPrincipal;
$scope.ComputeDownPaymentPercent();
$scope.ComputeAmortizationFactor();
};
$scope.ComputeAmortizationFactor = function() {
$scope.AmortizationStartDate = new Date($scope.AmortizationStartDateText);
var pir = $scope.PeriodicInterestRate = $scope.InterestRatePerAnnum / (12 / $scope.PaymentModeInMonths);
if ($scope.PaymentModeInMonths == 0) {
$scope.AmortizationFactor = 0;
return;
}
var compoundTerms = $scope.NumberOfMonthsToPay / $scope.PaymentModeInMonths;
var amortizationFactor = 0;
if (compoundTerms == 0) {
$scope.AmortizationFactor = 0;
return;
}
if ($scope.NumberOfMonthsToPay > 0) {
if ($scope.InterestRatePerAnnum > 0) {
$scope.AmortizationFactor = pir / (1 - (1 / Math.pow( 1 + pir, compoundTerms ) ) );
}
// 0% interest
else {
$scope.AmortizationFactor = 1 / compoundTerms;
}
}
else {
// nothing, this is handled by compoundTerms == 0
}
$scope.AmortizationFactor = $scope.round( $scope.AmortizationFactor, 10);
if ($scope.DeterminePaymentTerms) {
// AmortizationAmount is based on Budget
}
else {
$scope.AmortizationAmount = $scope.AmortizationFactor * $scope.InstallmentPrincipal;
}
$scope.CreateAmortizationSchedule();
};
$scope.ComputeFromInterestRatePerAnnum = function() {
$scope.ComputeAmortizationFactor();
};
$scope.FirstInterest = function() {
return Math.ceil($scope.PeriodicInterestRate * $scope.InstallmentPrincipal);
};
$scope.IsBudgetLessThanTheFirstInterest = function() {
return $scope.Budget <= $scope.FirstInterest();
};
$scope.ComputeFromAmortizationAmount = function() {
// http://oakroadsystems.com/math/loan.htm#LoanNumber
// N = −log(1−iA/P) / log(1+i)
var interest = $scope.FirstInterest();
if ($scope.Budget <= interest) {
return;
}
var n = (-Math.log(1 - $scope.PeriodicInterestRate * $scope.InstallmentPrincipal / $scope.Budget))
/ Math.log(1 + $scope.PeriodicInterestRate);
if (!isNaN(n) && n > 0) {
$scope.AmortizationAmount = $scope.Budget;
$scope.NumberOfMonthsToPay = Math.ceil(n);
$scope.ComputeYears();
$scope.CreateAmortizationSchedule();
}
};
$scope.EvaluatePaymentTerms = function() {
// based on budget
if ($scope.DeterminePaymentTerms == true) {
$scope.Budget = 0;
$scope.ComputeFromAmortizationAmount();
}
else {
$scope.ComputeFromInstallmentPrincipal();
}
};
$scope.AddAdvancedPayment = function() {
$scope.AdvancedPayments.push({MonthNumber: null, Payment: null});
};
$scope.RemoveAdvancedPayment = function(index) {
$scope.AdvancedPayments.splice(index,1);
$scope.CreateAmortizationSchedule();
};
$scope.CreateAmortizationSchedule = function() {
var amort = $scope.Amortizations = [];
var pir = $scope.PeriodicInterestRate;
var ma = $scope.AmortizationAmount;
var prevBalance = $scope.InstallmentPrincipal;
amort.push({
MonthNumber : 0,
AmortizationDate : null,
Interest : null,
Principal : null,
Amortization : null,
AdvancedPayment : null,
PrincipalBalance : prevBalance});
var i = 0;
while (Math.floor(prevBalance) > 0) {
var interest = pir * prevBalance;
var principal = ma - interest;
if (principal > prevBalance) {
principal = prevBalance;
}
var amortization = interest + principal;
var advancedPayment = 0;
var advancedPaymentFilter = $scope.AdvancedPayments.filter(function(pay) {
return pay.MonthNumber-1 == i;
});
if (advancedPaymentFilter.length == 1) {
advancedPayment = advancedPaymentFilter[0].Payment;
}
prevBalance = prevBalance - principal - advancedPayment;
var startDate = $scope.AmortizationStartDate;
var nthMonthFromStartDate = new Date(new Date(startDate).setMonth(startDate.getMonth()+i));
i += parseInt($scope.PaymentModeInMonths);
amort.push({
MonthNumber : i,
AmortizationDate : nthMonthFromStartDate,
Interest : interest,
Principal : principal,
Amortization : amortization,
AdvancedPayment : advancedPayment,
PrincipalBalance : prevBalance});
}
$scope.TotalInterest =
amort
.map(function(a) { return a.Interest; })
.reduce(function(prev,current) { return prev + current; });
$scope.TotalPrincipal =
amort
.map(function(a) { return a.Principal; })
.reduce(function(prev,current) { return prev + current; });
$scope.TotalAmortization =
amort
.map(function(a) { return a.Amortization })
.reduce(function(prev,current) { return prev + current; });
};
$scope.round = function(number,X) {
// rounds number to X decimal places, defaults to 2
X = (!X ? 2 : X);
return Math.round(number*Math.pow(10,X))/Math.pow(10,X);
}
$scope.ComputeFromDownPaymentPercent();
$scope.ComputeFromYears();
$scope.ComputeAmortizationFactor();
}]);
<body ng-controller="RealEstateController" ng-app="RealEstateApp">
<div style='float: left' class='input'>
<fieldset>
<legend>Selling</legend>
<div><label for='TotalContractPrice'>Total Contract Price</label></div>
<div>
<input
id='TotalContractPrice'
ng-model='TotalContractPrice'
ng-change='ComputeFromTotalContractPrice()'
ng-disabled='DeterminePaymentTerms'/>
</div>
<div><label for='DownPaymentPercent'>Downpayment %</label></div>
<div><input
id='DownpaymentPercent' ng-model='DownPaymentPercent' ng-change='ComputeFromDownPaymentPercent()'
ng-disabled='DeterminePaymentTerms'
percent/></div>
<div><label for='DownPayment'>Downpayment</label></div>
<div><input id='DownPayment' ng-model='DownPayment' ng-change='ComputeFromDownPayment()' ng-disabled='DeterminePaymentTerms' type='number'/></div>
<div><label for='InstallmentPrincipal'>Installment Principal</label></div>
<div><input id='InstallmentPrincipal' ng-model='InstallmentPrincipal' ng-change='ComputeFromInstallmentPrincipal()' ng-disabled='DeterminePaymentTerms'/></div>
</fieldset>
<fieldset>
<legend>Payment Terms</legend>
<div><label for='NumberOfYearsToPay'>Number of years to pay</label></div>
<div><input ng-disabled='DeterminePaymentTerms' id='NumberOfYearsToPay' ng-model='NumberOfYearsToPay' ng-change='ComputeFromYears()' type='number'/></div>
<div><label for='NumberOfMonthsToPay'>Number of months to pay</label></div>
<div><input ng-disabled='DeterminePaymentTerms' id='NumberOfMonthsToPay' ng-model='NumberOfMonthsToPay' ng-change='ComputeFromMonths()' type='number'/></div>
<div><label for='PaymentModeInMonths'>Payment Mode(1 = Monthly, 3 = Quarterly, ..., 12 = Yearly)</label></div>
<div><input ng-disabled='DeterminePaymentTerms' id='PaymentModeInMonths' ng-model='PaymentModeInMonths' ng-change='ComputeFromPaymentModeInMonths()' type='number'/></div>
<div><label for='InterestRatePerAnnum'>Interest Rate Per Annum</label></div>
<div><input ng-disabled='DeterminePaymentTerms' id='InterestRatePerAnnum' ng-model='InterestRatePerAnnum' ng-change='ComputeFromInterestRatePerAnnum()' percent/></div>
</fieldset>
<fieldset>
<legend>Amortization</legend>
<div><label for='AmortizationFactor'>Amortization Factor</label></div>
<div><b>{{AmortizationFactor}}</b></div>
<div><label for='AmortizationAmount'>Amortization Amount</label></div>
<div><b>{{AmortizationAmount | number:2}}</b></div>
<div><label for='InterestRatePerAnnum'>Amortization Start Date</label></div>
<div><input style='width: 300px' ng-disabled='true' id='AmortizationStartDate' ng-model='AmortizationStartDate' ng-change='ComputeFromInterestRatePerAnnum()'/></div>
<div><input type='date' ng-model='AmortizationStartDateText' ng-change='ComputeFromInterestRatePerAnnum()'/></div>
<hr/>
<div><input type='checkbox' ng-model='DeterminePaymentTerms' ng-change='EvaluatePaymentTerms()'/>Determine Payment Terms based on your budget</div>
<div ng-show='DeterminePaymentTerms'>
<div><input ng-show='DeterminePaymentTerms' id='Budget' ng-model='Budget' ng-change='ComputeFromAmortizationAmount()'/></div>
<div ng-show='IsBudgetLessThanTheFirstInterest()'>Budget should be more than the first interest({{FirstInterest() | number:2}}). Significantly higher if possible, otherwise you are just wasting your money on the interest ({{InterestRatePerAnnum * 100}}%)</div>
</div>
</fieldset>
<fieldset><legend>Totals</legend>
<div style='float: left; margin-right: 5px'>Total Interest: </div>
<div><b>{{TotalInterest | number:2}}</b></div>
<div style='float: left; margin-right: 5px'>Total Principal: </div>
<div><b>{{InstallmentPrincipal | number:2}} (aka Installment Principal)</b></div>
<div style='float: left; margin-right: 5px'>Total Amortization: </div>
<div><b>{{TotalAmortization | number:2}}</b></div>
<div style='float: left; margin-right: 5px'>Total Amortization + Downpayment: </div>
<div><b>{{TotalAmortization + DownPayment | number:2}}</b></div>
</fieldset>
<fieldset>
<legend>Extra Payments<button ng-click="AddAdvancedPayment()">+</button></legend>
<div ng-repeat="adv in AdvancedPayments">
<input ng-model="adv.MonthNumber" placeholder="Month Number" ng-change="CreateAmortizationSchedule()" type='number'>
<input ng-model="adv.Payment" placeholder="Payment" ng-change="CreateAmortizationSchedule()" type='number'>
<button ng-click="RemoveAdvancedPayment($index)">-</button>
</div>
</fieldset>
</div>
<div class='amort' style='float: left'>
<fieldset>
<legend>Amortization Schedule</legend>
<table ng-show='Amortizations.length > 0' class='table'>
<thead>
<tr class='header'>
<th>Month#</th><th>Date</th><th>Interest</th><th>Principal</th><th>Amortization</th><th>Extra Payment</th><th>Balance</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='a in Amortizations'>
<td>{{a.MonthNumber}}</td>
<td style='text-align: right'>{{a.AmortizationDate | date:'MMM dd, yyyy'}}</td>
<td style='text-align: right'>{{a.Interest | number:2}}</td>
<td style='text-align: right'>{{a.Principal | number:2}}</td>
<td style='text-align: right'>{{a.Amortization | number:2}}</td>
<td style='text-align: right'>{{a.AdvancedPayment | number:2}}</td>
<td style='text-align: right'>{{a.PrincipalBalance | number:2}}</td>
</tr>
</tbody>
<tfooter>
<tr>
<td>Total</td><td></td>
<td>{{TotalInterest | number:2}}</td>
<td>{{TotalPrincipal | number:2}}</td>
<td>{{TotalAmortization | number:2}}</td>
<td></td>
</tr>
</tfooter>
</table>
</fieldset>
</div>
</body>
fieldset { font-size: 13px; font-family : 'Calibri'}
div.input { width: 380px; }
div.amort { width: auto; }
table, th, td { font-size: 13px; font-family : 'Calibri'; }
input {
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 5px;
}
fieldset {
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
}
table.table {
margin: 20px;
color: 666;
font-size: 12px;
-moz-border-radius: 3px;
-moz-box-shadow: 0 1px 2px #d1d1d1;
-webkit-border-radius: 3px;
-webkit-box-shadow: 0 1px 2px #d1d1d1;
background: #eaebec;
border: #ccc 1px solid;
border-radius: 3px;
box-shadow: 0 1px 2px #d1d1d1;
font-family: Arial, Helvetica, sans-serif;
text-shadow: 1px 1px 0px #fff;
}
table.table tr.even td {
background: #f6f6f6;
background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#f6f6f6));
background: -moz-linear-gradient(top, #f8f8f8, #f6f6f6);
}
table.table th {
padding: 21px 25px 22px 25px;
border-bottom: 1px solid #e0e0e0; border-top: 1px solid #fafafa;
background: #ededed;
background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#ebebeb));
background: -moz-linear-gradient(top, #ededed, #ebebeb);
}
table.table th:first-child {
padding-left: 20px;
text-align: left
}
table.table a:visited {
color: #999999;
font-weight: bold;
text-decoration: none
}
table.table a:active,
table.table a:hover {
color: #bd5a35;
text-decoration: underline
}
table.table a:link {
color: #666;
font-weight: bold;
text-decoration: none
}
table.table tr {
padding-left: 20px;
text-align: center
}
table.table tr:first-child th:first-child {
-moz-border-radius-topleft: 3px;
-webkit-border-top-left-radius: 3px;
border-top-left-radius: 3px;
}
table.table tr:first-child th:last-child {
-moz-border-radius-topright: 3px;
-webkit-border-top-right-radius: 3px;
border-top-right-radius: 3px;
}
table.table tr:last-child td { border-bottom: 0 }
table.table tr:last-child td:first-child {
-moz-border-radius-bottomleft: 3px;
-webkit-border-bottom-left-radius: 3px;
border-bottom-left-radius: 3px;
}
table.table tr:last-child td:last-child {
-moz-border-radius-bottomright: 3px;
-webkit-border-bottom-right-radius: 3px;
border-bottom-right-radius: 3px;
}
table.table tr:hover td {
background: #f2f2f2;
background: -webkit-gradient(linear, left top, left bottom, from(#f2f2f2), to(#f0f0f0));
background: -moz-linear-gradient(top, #f2f2f2, #f0f0f0);
}
table.table tr td {
padding: 18px;
border-bottom: 1px solid #e0e0e0; border-left: 1px solid #e0e0e0; border-top: 1px solid #ffffff;
background: #fafafa;
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));
background: -moz-linear-gradient(top, #fbfbfb, #fafafa);
}
table.table tr td:first-child {
padding-left: 20px;
border-left: 0;
text-align: left
}