var myApp = angular.module('RealEstateApp', []); 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('May 5, 2000'); // 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() { 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> <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 }