AngularJS ngClass not working in ngRepeat in custom directive inside ngRepeat - angularjs

Here's a plunker:
http://plnkr.co/edit/hJsZFCBZhFT5rRo1lgSZ?p=preview
It's a simple directive that is supposed to show a given number of dots and highlight a subset of them based on the value, min, and max given, similar to a star rating. It works great when on its own (as you can see in the last row of the plunkr), but not in an ngRepeat. The problem is the ngClass on the directive's own ngRepeat seems to fail, even though the expression used in the ngClass seems to be evaluating correctly outside of the ngClass. I've been tearing my hair out over this for the last few hours, and I just can't figure it out.
Here's the actual code:
HTML:
<div ng-repeat="row in [1,2,3,4,5]">
<match-dots value="{{ $index+1 }}" min="0" max="10">{{ isDotActive($index) }}</match-dots>
</div>
JS:
angular.module('app', [])
.directive('matchDots', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
template:
"<span class='match-dots-group'>" +
"<span class='match-dots-dot' ng-repeat='dot in dotsList()' ng-class='{ active: isDotActive($index) }' ng-transclude></span>" +
"</span>",
scope: {
dots: '#',
min: '#',
max: '#',
value: '#'
},
link: function(scope, elem) {
// save the previous class applied so we can remove it when the class changes
var previousClass = '';
/**
* set the class for the dots-group based on how many dots are active (n-active)
*/
scope.setDotsClass = function() {
if(previousClass != '') {
elem.removeClass(previousClass);
}
previousClass = "active-"+scope.numActiveDots();
elem.addClass(previousClass);
}
},
controller: function($scope) {
$scope.dots = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
$scope.min = angular.isUndefined($scope.min)?0:parseInt($scope.min);
$scope.max = angular.isUndefined($scope.max)?100:parseInt($scope.max);
/**
* create a list of numbers
* #returns {Array}
*/
$scope.dotsList = function() {
var arr = [];
for(var i=1; i<=$scope.dots; i++) {
arr.push(i);
}
return arr;
};
/**
* should the given dot be lit up?
* #param {number} dot
* #returns {boolean}
*/
$scope.isDotActive = function(dot) {
return dot < $scope.numActiveDots();
};
/**
* How many dots are active?
* #returns {number}
*/
$scope.numActiveDots = function() {
var activeDots = Math.ceil(($scope.value-$scope.min) / (($scope.max-$scope.min) / $scope.dots));
return isNaN(activeDots)?0:activeDots;
};
// make sure the value is an number
$scope.$watch('value', function(newValue) {
$scope.value = parseFloat(newValue);
$scope.setDotsClass();
});
// make sure the number of dots is actually a positive integer
$scope.$watch('dots', function(newDots) {
if(angular.isUndefined(newDots) || !newDots || newDots < 1) {
$scope.dots = 5;
} else {
$scope.dots = parseInt(newDots);
}
$scope.setDotsClass();
});
// make sure the min is not greater than the max
$scope.$watch('max', function(newMax) {
if(angular.isUndefined(newMax)) {
$scope.max = newMax = 100;
}
if(angular.isString(newMax)) {
$scope.max = parseFloat(newMax);
} else if(newMax < $scope.min) {
$scope.max = $scope.min;
$scope.min = newMax;
}
$scope.setDotsClass();
});
// make sure the max is not less than the min
$scope.$watch('min', function(newMin) {
if(angular.isUndefined(newMin)) {
$scope.min = newMin = 0;
}
if(angular.isString(newMin)) {
$scope.min = parseFloat(newMin);
} else if(newMin > $scope.max) {
$scope.min = $scope.max;
$scope.max = newMin;
}
$scope.setDotsClass();
});
}
}
});

The first thing that looked suspicious to me is that you were reassigning the $scope.dots property and your numActiveDots function, quite often was ending up with NaN. That property is passed into your directive as a string, then you are parsing/reassigning it as number. I tried renaming your parsed value to $scope.dotsinternal. That seems to make it work.
http://plnkr.co/edit/STwuts2D169iBLDoTtE2?p=preview
Change this:
$scope.dots = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
to this:
$scope.dotsinternal = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
then updated all the references to $scope.dots to $scope.dotsinternal.

Related

animating number change in directive in angular

I have a directive which I have included jquery's animate functionality in to. I'd like for a particular variable's number to change with easing animation. The issue is that then the directive loads, the initial number is shown but doesn't show the number changing with the animation effect.
I have created a similar version in Plunkr to make it easy to see what's going on.
If I trigger $apply() from elsewhere the final numbers show, skipping the whole animated sqeuqnce of numbers. Also, in the code when I try to do apply on each step, it throws an 'in progress' error.
This plugin almost does what I need it to, except that it doesn't increment over decimal places and doesn't use easing. http://sparkalow.github.io/angular-count-to/
scope.$watch('difference', function(newVal, oldVal) {
jQuery({someValue: oldVal}).animate({someValue: newVal}, {
duration: 1000,
easing:'swing',
step: function(e) {
scope.display = e.toFixed(2);
scope.$parent.$apply();
}
});
});
and..
template: function(scope, element, attrs) {
return '<h3>' +
'<i class="fa progress-arrow" ng-class="[{\'fa-caret-up\': direction_up}, {\'fa-caret-down\': direction_down}]" aria-hidden="true"></i> ' +
'{{ display }}' +
'</div>' +
'</h3>' +
'<label>{{ label }} (lbs)</label>';
The answer was to use the angular $timeout function in conjunction with scope.$apply().
Here's the updated code that does in fact work:
scope.$watch('difference', function(newVal, oldVal) {
jQuery({someValue: oldVal}).animate({someValue: newVal}, {
duration: 500,
easing:'swing',
step: function(e) {
$timeout(function () {
scope.$apply(function () {
scope.display = e.toFixed(2);
});
});
}
});
And here it is in Plunkr
create directive
export class IncrementCounterDirective implements AfterViewInit {
#Input('appIncrementCounter') to: number = 0;
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit(): void {
this.counterFunc(this.to, 2000);
}
private counterFunc(end: number, duration: number = 3000) {
let range, current: number, step, timer: any;
range = end - 0;
current = end - 150;
step = Math.abs(Math.floor(duration / range));
// console.log(`step`, step);
timer = setInterval(() => {
current += 1;
this.setText(current);
if (current >= end) {
clearInterval(timer);
}
}, step);
}
setText(n: number) {
this.renderer.setProperty(this.elRef.nativeElement, 'innerText', `${n}`);
}
}
To use
<h3 class="stat-count" [appIncrementCounter]="607">000</h3>

How to revalidate a form with multiple dependent fields?

I'm fairly new to Angular. I have a form where the user need to assign port numbers to 9 different port input fields (context: it's a form for a server environment configuration). The validation requirement is that no port number can be assigned twice, so each of the 9 port numbers needs to be unique.
For that, I have a custom validation directive called "srb-unique-port", which I assign to my input fields.
Directive:
(function () {
'use strict';
angular
.module('account')
.directive('srbUniquePort', [srbUniquePort]);
function srbUniquePort() {
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function (scope, element, attrs, ngModel) {
ngModel.$validators.srbUniquePort = function (val) {
if (val == null || val == undefined || val == "" || val==0) return true;
var fieldName = attrs.name;
var configuration = scope.$eval(attrs.srbUniquePort);
var portFieldsToCheck = [
"myRestServicePort",
"myRestServicePortSSL",
"alfrescoPortHttp",
"alfrescoPortHttps",
"alfrescoPortTomcatShutdown",
"alfrescoPortAJP",
"alfrescoPortMySql",
"alfrescoPortJOD",
"alfrescoPortVti"
];
for (var i = 0; i < portFieldsToCheck.length; i++) {
if (fieldName!=portFieldsToCheck[i] && configuration[portFieldsToCheck[i]] == val) {
return false;
}
}
return true;
}
}
}
}
})();
HTML form (excerpt, just showing 2 of the 9 fields):
...
<md-input-container>
<label for="company" translate>COMPANY.CONFIGURATION.DBLIB_WEB_SRVC_PORT</label>
<input ng-model="vm.configuration.dblibWebSrvcPort" name="dblibWebSrvcPort" srb-unique-port="vm.configuration">
<div ng-messages="configurationForm.dblibWebSrvcPort.$error">
<div ng-message when="srbUniquePort">
<span translate>COMPANY.CONFIGURATION.VALIDATION.PORT_NOT_UNIQUE</span>
</div>
</div>
</md-input-container>
<md-input-container>
<label for="company" translate>COMPANY.CONFIGURATION.DBLIB_WEB_SRVC_PORT_SSL</label>
<input ng-model="vm.configuration.dblibWebSrvcPortSLL" name="dblibWebSrvcPortSLL" srb-unique-port="vm.configuration">
<div ng-messages="configurationForm.dblibWebSrvcPortSLL.$error">
<div ng-message when="srbUniquePort">
<span translate>COMPANY.CONFIGURATION.VALIDATION.PORT_NOT_UNIQUE</span>
</div>
</div>
</md-input-container>
...
It basically works for the field that I am current entering a value into. But the problem is that when I change the value of one input field, I need to re-validate all other depending fields as well. But I am not sure what the best way is in order to not run into an endless loop here, since all fields have the "srb-unique-port" assigned.
I already looked on StackOverflow and found this very similar question:
Angular directive with scope.$watch to force validation of other fields
with this plunker sample code:
http://plnkr.co/edit/YnxDDAUCS2K7KyXT1AXP?p=preview
but the example provided there is different: it's only about a password and a password repeat field, where only one field has the validation directive assigned.
So it differs from my case.
I tried to add this in my above code:
scope.$watch(ngModel, function (newValue, oldValue) {
ngModel.$validate();
});
but this causes endless loops (why does the ngModel frequently change here without any further action other than a validation which should always result to the same?).
This is the solution I ended up with. Looks a bit hacked to me, but it works.
(function () {
'use strict';
angular
.module('account')
.directive('srbUniquePort', [srbUniquePort]);
function srbUniquePort() {
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function (scope, element, attrs, ngModel) {
function hasAValue(field) {
return !!field;
}
ngModel.$validators.srbUniquePort = function (val) {
var fieldName = attrs.name;
var configuration = scope.$eval(attrs.srbUniquePort);
var portFieldsToCheck = [
"dblibWebSrvcPort",
"dblibWebSrvcPortSLL",
"myRestServicePort",
"myRestServicePortSSL",
"alfrescoPortHttp",
"alfrescoPortHttps",
"alfrescoPortTomcatShutdown",
"alfrescoPortAJP",
"alfrescoPortMySql",
"alfrescoPortJOD",
"alfrescoPortVti"
];
configuration[fieldName] = val;
if (scope.$parent.configuration == undefined) {
scope.$parent.configuration = JSON.parse(JSON.stringify(configuration));
}
scope.$parent.configuration[fieldName] = val;
// compare each port field with each other and in case if equality,
// remember it by putting a "false" into the validityMap helper variable
var validityMap = [];
for (var i = 0; i < portFieldsToCheck.length; i++) {
for (var j = 0; j < portFieldsToCheck.length; j++) {
if (portFieldsToCheck[i] != portFieldsToCheck[j]) {
var iFieldHasAValue = hasAValue(scope.$parent.configuration[portFieldsToCheck[i]]);
var jFieldHasAValue = hasAValue(scope.$parent.configuration[portFieldsToCheck[j]]);
var valHasAValue = hasAValue(val);
if (iFieldHasAValue && jFieldHasAValue
&& scope.$parent.configuration[portFieldsToCheck[i]] == scope.$parent.configuration[portFieldsToCheck[j]]
) {
validityMap[portFieldsToCheck[i]] = false;
validityMap[portFieldsToCheck[j]] = false;
}
}
}
}
// in the end, loop through all port fields and set
// the validity here manually
for (var i = 0; i < portFieldsToCheck.length; i++) {
var valid = validityMap[portFieldsToCheck[i]];
if (valid == undefined) valid = true;
ngModel.$$parentForm[portFieldsToCheck[i]].$setValidity("srbUniquePort", valid);
}
// ending with the standard validation for the current field
for (var i = 0; i < portFieldsToCheck.length; i++) {
if (fieldName != portFieldsToCheck[i] && configuration[portFieldsToCheck[i]] == val) {
return false;
}
}
return true;
}
}
}
}
})();

ui-select multiselect is very slow in displaying the choices

I ran into this problem, and I don't know how to solve it. I have used a ui-select multiselect in my page. First, a http.get request is made to a url which gets the data, then the ui-select choices are populated. The data is big - the length of the data is 2100. This data is to be shown as choices. (The data is fetched at the beginning during the loading of the page and is stored in an array)
But the problem is each time I click on the multiselect to select a choice, it takes 4-5 seconds to populate the list and the page becomes very slow. What do I do to reduce this time?
The choices data is stored in an array, the datatype is array of strings.
<ui-select multiple ng-model="selectedFields.name" style="width: 100%;">
<ui-select-match placeholder="Select fields...">{{$item}}</ui-select-match>
<ui-select-choices repeat="fields in availableFields | filter:$select.search">
{{fields}}
</ui-select-choices>
</ui-select>
in the controller,
$scope.selectedFields = {};
$scope.selectedFields.name = [];
$scope.init = function() {
$http.get(url)
.success( function(response, status, headers, config) {
availableFields = response;
})
.error( function(err) {
});
};
$scope.init();
If not this way, is there any other options/choice I can work with which doesn't delay showing the select-choices?
This is a known issue in ui-select. I tried the following ways, both work
1) There is a workaround for this - use
| limitTo: 100
This limits the choice display to 100 but all the choices can be selected. Look at this thread for more details.
2) Since some of the time, there is a need to display the entire list in the choices, 1) is not a viable option. I used a different library - selectize.js. Here's a plunker demo given in the page
Here is complete solution that decorates uiSelectChoices directive.
Items are populated progressively as the user scrolls.
Also takes care of searches in the scrolls.
Also works for all values of position={auto, up, down}
Example
<ui-select-choices
position="up"
all-choices="ctrl.allTenThousandItems"
refresh-delay="0"
repeat="person in $select.pageOptions.people | propsFilter: {name: $select.search, age: $select.search} ">
<div ng-bind-html="person.name | highlight: $select.search"></div>
<small>
email: {{person.email}}
age: <span ng-bind-html="''+person.age | highlight: $select.search"></span>
</small>
</ui-select-choices>
Working Plnkr
Also with With v0.19.5
The directive
app.directive('uiSelectChoices', ['$timeout', '$parse', '$compile', '$document', '$filter', function($timeout, $parse, $compile, $document, $filter) {
return function(scope, elm, attr) {
var raw = elm[0];
var scrollCompleted = true;
if (!attr.allChoices) {
throw new Error('ief:ui-select: Attribute all-choices is required in ui-select-choices so that we can handle pagination.');
}
scope.pagingOptions = {
allOptions: scope.$eval(attr.allChoices)
};
attr.refresh = 'addMoreItems()';
var refreshCallBack = $parse(attr.refresh);
elm.bind('scroll', function(event) {
var remainingHeight = raw.offsetHeight - raw.scrollHeight;
var scrollTop = raw.scrollTop;
var percent = Math.abs((scrollTop / remainingHeight) * 100);
if (percent >= 80) {
if (scrollCompleted) {
scrollCompleted = false;
event.preventDefault();
event.stopPropagation();
var callback = function() {
scope.addingMore = true;
refreshCallBack(scope, {
$event: event
});
scrollCompleted = true;
};
$timeout(callback, 100);
}
}
});
var closeDestroyer = scope.$on('uis:close', function() {
var pagingOptions = scope.$select.pagingOptions || {};
pagingOptions.filteredItems = undefined;
pagingOptions.page = 0;
});
scope.addMoreItems = function(doneCalBack) {
console.log('new addMoreItems');
var $select = scope.$select;
var allItems = scope.pagingOptions.allOptions;
var moreItems = [];
var itemsThreshold = 100;
var search = $select.search;
var pagingOptions = $select.pagingOptions = $select.pagingOptions || {
page: 0,
pageSize: 20,
items: $select.items
};
if (pagingOptions.page === 0) {
pagingOptions.items.length = 0;
}
if (!pagingOptions.originalAllItems) {
pagingOptions.originalAllItems = scope.pagingOptions.allOptions;
}
console.log('search term=' + search);
console.log('prev search term=' + pagingOptions.prevSearch);
var searchDidNotChange = search && pagingOptions.prevSearch && search == pagingOptions.prevSearch;
console.log('isSearchChanged=' + searchDidNotChange);
if (pagingOptions.filteredItems && searchDidNotChange) {
allItems = pagingOptions.filteredItems;
}
pagingOptions.prevSearch = search;
if (search && search.length > 0 && pagingOptions.items.length < allItems.length && !searchDidNotChange) {
//search
if (!pagingOptions.filteredItems) {
//console.log('previous ' + pagingOptions.filteredItems);
}
pagingOptions.filteredItems = undefined;
moreItems = $filter('filter')(pagingOptions.originalAllItems, search);
//if filtered items are too many scrolling should occur for filtered items
if (moreItems.length > itemsThreshold) {
if (!pagingOptions.filteredItems) {
pagingOptions.page = 0;
pagingOptions.items.length = 0;
} else {
}
pagingOptions.page = 0;
pagingOptions.items.length = 0;
allItems = pagingOptions.filteredItems = moreItems;
} else {
allItems = moreItems;
pagingOptions.items.length = 0;
pagingOptions.filteredItems = undefined;
}
} else {
console.log('plain paging');
}
pagingOptions.page++;
if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
for (var k = 0; k < moreItems.length; k++) {
pagingOptions.items.push(moreItems[k]);
}
scope.calculateDropdownPos();
scope.$broadcast('uis:refresh');
if (doneCalBack) doneCalBack();
};
scope.$on('$destroy', function() {
elm.off('scroll');
closeDestroyer();
});
};
}]);
As stated, ui-select is having quite a few performance issues, but there is a workaround for the limit issue.
If you follow akashrajkn's approach then you will notice that it will actually cut out important pieces of data because it will only render 100 at a time. There is a fix that has passed the unit tests and it can be found on the thread here:
https://github.com/angular-ui/ui-select/pull/716
Basically, if you are storing the javascript file locally, then you can adjust the unminified version. All you need to do is implement the changes he made in the pull request and it should help out significantly. In order to apply the limiting factor, take a look at the below, modified example:
<ui-select multiple ng-model="selectedFields.name" limit = "10" style="width: 100%;">
<ui-select-match placeholder="Select fields...">{{$item}}</ui-select-match>
<ui-select-choices repeat="fields in availableFields | filter:$select.search | limitTo:$select.limit ">
{{fields}}
</ui-select-choices>
</ui-select>
The above will limit your data in the drop down while also maintaining the level of consistency needed.
Because I cannot leave a comment (not enough rep) I write this as an answer and I am sorry it is no answer for the problem.
#bhantol I changed the following line of code to your solution which is working perfectly for me so far
for (var k = 0; k < moreItems.length; k++) {
pagingOptions.items.push(moreItems[k]);
}
for (var k = 0; k < moreItems.length; k++) {
if (pagingOptions.items.indexOf(moreItems[k]) == -1){
pagingOptions.items.push(moreItems[k]);
}
}
This prevents duplicated items from showing up if the user is starting to write a filter and then deletes it.
Also I just figured out that if the list is smaller than 20 items it will not work so I changed:
if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
to:
if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
else{ moreItems = allItems;}
Maybe this will help you somehow and sorry again for not answering the question.

use filter in angular directive to change collection in ng-options

I have the following directive to set the 10 most used languages in the beginning of a select and set a custom css class:
app.directive('sortTopLocales', function ($timeout) {
// copied from angular-chosen (to get the array of ng-options)
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;
var top10languages = ['en', 'zh', 'es', 'ja', 'pt', 'de', 'ar', 'fr', 'ru', 'ko'];
var top10languagesReversed = top10languages.reverse();
var cssClass = 'top10';
return {
restrict: 'A',
link: function(scope, element, attrs) {
if (attrs.ngOptions) {
var match = attrs.ngOptions.match(NG_OPTIONS_REGEXP);
scope.$watchCollection(match[7], function(locales) {
var alreadySorted = element.find('option.'+cssClass).length;
if(angular.isUndefined(locales) || alreadySorted) { return; }
var top10Index = 0;
$timeout(function() {
element.find('option.'+cssClass).removeClass(cssClass); // reset CSS class
angular.forEach(top10languagesReversed, function(locale) {
var index = locales.indexOf(locale);
if(index !== -1) {
locales.splice(index, 1); // removes it from the array
locales.unshift(locale); // sets it at the beginning of the array
var option = element.find('option[value=' + top10Index + ']');
option.addClass(cssClass);
top10Index++;
}
});
console.log(scope.$eval(match[7])[0], locales[0]);
// without filter => 'en', 'en'
// with filter => 'aa', 'en'
});
});
}
}
};
});
It works as expected without filter, but when I use a filter in ng-options it doesn't:
working:
ng-options="locale for locale in locales" // match[7] = 'locales'
not working:
ng-options="locale for locale in locales | orderBy" // match[7] = 'locales | orderBy'
There is a different output of the changed array and the expression of $watchCollection.
scope.$eval(match[7])[0] // 'aa'
locales[0] // 'en'
When I use the filter inside the directive code and not in the HTML the array isn't updated either.
So basically my question is: How do I update an array with filter changed in $watchCollection to use it in ng-options?

AngularJS setting default values for directive

In my parent controller:
//soemtimes view invoice has taxtotal defined
$scope.viewinvoice = {taxtotal:4}
//sometimes viewinvoice does not have taxtotal defined
$scope.viewinvoice = {}
//sometimes it is defined but equal to 0
$scope.viewinvoice = {taxtotal:0}
In my parent view:
<div class="span6">
<invoice invoice='viewinvoice'></invoice>
</div>
My directive:
.directive('invoice', [ function () {
return {
restrict: 'E',
scope: {
invoice:'='
},
replace: true,
template: '<div>
<input type="checkbox" ng-model="taxflag">
<div> {{ calculate_grand_total() }} </div>
</div>',
link: function($scope, element, attrs) {
}
};
}]);
In my directive I want to set $scope.taxflag true based on the property: $scope.invoice.taxtotal, the problem is if $scope.invoice.taxtotal is undefined I want to set the $scope.taxflag to false, and if the $scope.invoice.taxtotal is greater than 0 and is defined I want the $scope.taxflag set to true.
i.e.
if($scope.invoice.hasOwnProperty('taxtotal')){
if($scope.invoice.taxtotal > 0 ) {
$scope.taxflag = true;
} else {
$scope.taxflag = false;
}
} else {
$scope.invoice.taxtotal = 0;
$scope.taxflag = false;
}
I want this (above code) to act like 'initialization' code so whenever my 'viewinvoice' changes in the parent the $scope.taxflag and $scope.invoice.taxtotal will both be initially be set up correctly
I also want to trigger a change whenever the checkbox is checked:
i.e.
$scope.$watch('taxflag',function(newValue){
if(newValue) {
$scope.invoice.taxtotal = 5
} else {
$scope.invoice.taxtotal = 0;
}
});
I am also using this $scope.invoice.taxtotal elsewhere in a function {{ calculate_grand_total() }}
(in my directives view)
ie.
$scope.calculate_grand_total = function() {
return $scope.invoice.taxtotal + 5;
}
but this is unable to render because $scope.invoice.taxtotal is not defined (at least initially) !!
Does this make sense? I have tried so many different combinations but I can't seem to get it working as I want it to.
I've created this plunkr that tries to capture your question:
http://plnkr.co/edit/02QAC8m9xyF4pSyxnfOf
Basically, any code that depends on a value that can change should be in a watch. This means that your initialization code for setting taxflag belongs in a watch, so that it can update the tax flag if things change. This looks something like:
$scope.$watch('invoice.taxtotal', function(taxTotal) {
if (!angular.isNumber(taxTotal)) {
$scope.taxflag = false
return;
}
if (taxTotal > 0) {
$scope.taxflag = true;
} else {
$scope.taxflag = false;
}
});
Keep in mind watches are always executed the first time to initialize the values, so they essentially serve as both initialization code and update code.
As far as your calculate_grand_total function, if it is up to you to define what it should return if tax total or invoice are undefined. Simply check whether it is undefined and return the appropriate value, for example in my plunkr I return empty string:
$scope.calculate_grand_total = function() {
if ($scope.invoice && angular.isNumber($scope.invoice.taxtotal)) {
return $scope.invoice.taxtotal + 5;
} else {
return "";
}
}
I'm not sure if my plunkr works exactly like you want it or not, but it should get you started in the right direction. Or serve as a starting point for further clarification.
related:
if you only want to set first-run defaults you can use angular.merge
inside your link:
link: function(scope, element, attr, ctrl) {
// ....
var settings_default = {
setting1: true,
setting2: {
x: 0,
y: 10
},
};
// order is important. last item overwrites previous
scope.settings = angular.merge({}, settings_default, scope.settings);
// ....
})

Resources