I have this part of code inside ng-repeat:
<div ng-show="parameter == 'MyTESTtext'">{{parameter}}</div>
where parameter is some $scope string variable ...
I wanted to see if its possible to check (inside ng-show for instance) whether parameter contains a substring.
You could do this with:
<div ng-show="parameter.indexOf('TEST') != -1">{{parameter}}</div>
which seems to be working; it displays every parameter that contains 'TEST' keyword.
I was wondering:
is this a correct way of doing this within AngularJS app?
Is it OK to use javascript built in functions like that?
EDIT:
parameter is actually formed like this: (and is thus not a $scope variable as I said above, sorry)
<div ng-repeat="(parameter,value) in oneOfMyScopeArrays">
UPDATE
Since you're dealing with strings in ngRepeat and not objects, there's no place to set flag to in your data elements. In this case I would advise using a custom directive. I do not agree with Darryl Snow's opinion that directive in this case is redundant. With directive (as it was the case with flag in controller) you can evaluate parameter once instead of doing so in every $digest cycle. Furthermore, if you decide to implement same functionality in other template, instead of copying the expression around, which is redundant, you'd reuse same directive. Here's a quick idea of such directive:
.directive('parameter', function() {
return {
link: function($scope, $element, $attrs) {
$attrs.$observe('parameter', function(parameter) {
if (parameter.indexOf('TEST') == -1) {
$element.hide();
} else {
$element.text(parameter);
$element.show();
}
});
}
}
});
Template:
<div parameter="{{parameter}}"></div>
This directive even sets up one watcher less per parameter comparing to your original solution, which is better performance wise. On the other hand, it disables two-way binding (parameter text is rendered once), so it won't work in case you want to edit parameter string in place.
ORIGINAL ANSWER
Is it correct way? Technically yes, because it works. Is it OK? Not so much because of several reasons:
Performance. Everytime $digest loop runs (it might run quite a lot, depending on interactivity of application), it has to process every such expression. Therefore string parameter.indexOf('TEST') != -1 has to be parsed and evaluated, which means calling .indexOf up to several times after each interaction, for example click on element with ngClick directive. Wouldn't it be more performant to test this assumption parameter.indexOf('TEST') != -1 once in Controller and set a flag, e.g.
$scope.showParameter = parameter.indexOf('TEST') != -1
In template you would write
<div ng-show="showParameter">{{parameter}}</div>
Model logic in template. It's hard to tell the actual reasoning from your example when the parameter should be visible, but is it up to the template to have this logic? I think this belongs to controller, if not model to decide, that your view layer would be decoupled from making assumptions about how the model actually works.
Yes, perfectly ok I think. You could write a separate directive of your own that does the same thing - it may look a bit tidier but it's ultimately redundant when angular comes with ng-show already built in, and it means a slight additional payload to the user. You could also do a $scope.$watch on parameter and set another scope variable for ng-show, but that just moves the mess from your view to your controller.
$scope.$watch('parameter', function(){
if(parameter.indexOf('TEST') != -1)
$scope.showit = true;
});
and then in the view:
<div ng-show="showit">{{parameter}}</div>
Related
What are the performance implications of running a function within an Angular expression? For example:
<button ng-if="isValid()">Valid</button>
I'm assuming the function isValid() is being run for each digest loop, which occur many times per second. What options are available within Angular for improving the performance here?
One idea would be to run isValid() within a timeout function a few times a second, then setting a scope variable, so I can control the speed of the "digest". Are there other options people use?
I think if function body is just a simple logic comparison, won't affect performance, but if it gets complex, you can try profiling your code via chrome tools to see how much is performing,
Another alternative is also you can try binding the ng-if to a controller scope property instead and modify the value from other part of your controller's code, so maybe you can apply kind of debounce (https://github.com/shahata/angular-debounce) technique to avoid set the scope variable tons of times
It looks like you're checking if a form is valid before submitting. First of all, your ng-if on the button defeats the purpose of using the same function to conditionally apply a class, since it won't even be in the DOM if isValid() returns false.
You should be able to use ng-pattern, ng-change hooks on your inputs to determine validity as the user is entering form values. Then on your button you can apply the class based on the form's validity:
<button ng-class="{
'btn-success': myFormName.$valid,
'btn-danger': !myFormName.$valid
}" ng-bind="(myFormName.$valid) ? 'Valid' : 'Invalid'"></button>
If you want to suppress form submission, you can check validity of the form inside a function with ng-submit on the <form> tag:
<form name="myFormName" ng-submit="myFormName.$valid && mySubmitFunction()">
One way is to run the isValid function within an interval function, setting a simple scope variable within. For example:
$interval(function() {
scope.isvalid = scope.isValid();
}, 100);
This will run every 100ms, but is probably preferable to the number of calls the digest makes. You can also use $watch or $observe to reduce the number of calls.
I'm wondering if there is any way I can get the original {{expression}} after angular has complied the directives and interpolated the expressions. For instance if there is a text e.g. Hi, Filip and the user clicks on it, I want to be able to show a pop-up with Hi, {{name}}.
Now, one way I thought of doing that is by analysing the DOM before angular (e.g. during run) and then saving the expressions as additional attributes to the parent element. However, I run into various problems with that (e.g. if parent has other child elements and they are removed, e.g. with ng-if, then I can't reliably know which expression belongs to which text node).
Since Angular keeps watchers for these expressions, it must have a reference to the text nodes they are applied on. Is there any way I could access those?
The second question is, can I somehow get the original element of ng-repeat (before it was compiled and transcluded), for the similar purpose (allowing the user to modify it on-the-fly).
I want to avoid introducing new directives as this is meant to work on existing angular applications.
I'm not concerned about performance or security (i.e. this is not for production applications but rather for prototyping/debugging).
Use factories to supply to your html expressions with reusable logic. Assumes you're using controller as syntax so you can the controller's scope as this in your view.
// factory
function() {
return 'bob';
}];
// in your controller
['somefactory', function(factory) {
this.factoryString = factory.toString(); // => "function() { return 'bob'; }"
this.factory = factory;
}];
// view
<div>hi {{this.factory()}} you were made with {{this.factoryString}}</div>
// interpolated
<div>hi bob, you were made with function() { return 'bob' }</div>
I didn't test any of that though.
I am trying to create a directive for md-autocomplete. I tried using the answer provide by AngularJS - Create a directive that uses ng-model, but it does not work for me.
My CodePen is here: http://codepen.io/colbroth/pen/QyMaQX/?editors=101.
The code is based on the Angular material demo for autocomplete at https://material.angularjs.org/latest/demo/autocomplete. I have multiple pages, which need an autocomplete field to select a state. I do not want to repeat the code for each web page component.
state-directive takes an md-autocomplete input, I need demoCtrl.selected state to reflect the same value in both cases. But when I update the input element, the state-directive does not reflect this and vice versa.
<input ng-model="demoCtrl.selectedState">
<state-directive ng-model="demoCtrl.selectedState">...</state-directive>
You are on the right track. Your problem is that your model is a string - a primitive in javascript but a ngModel always needs to be an object if you want to avoid these kind of problems.
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models.
Taken from Understanding-Scopes
It also links to this Youtube video - 3 minutes of well invested time
function DemoCtrl() {
var self = this;
self.state = {
selected: "Maine"
};
}
Fixed codepen
While using AngularMaterial, I have ng-checked like this:
<md-list>
<md-list-item ng-repeat="option in options">
<p> {{ option }} </p>
<md-checkbox class="md-secondary" aria-label="{{$index}}" ng-checked="exists($index)" ng-click="toggle($index)"></md-checkbox>
</md-list-item>
</md-list>
And my exists function:
$scope.exists = function (optionNum) {
console.log('Inside $scope.exists. option: '+optionNum);
};
My timer:
function updateTimer() {
var onTimeout = function(){
mytimeout = $timeout(onTimeout,1000);
}
var mytimeout = $timeout(onTimeout,1000);
}
With this, the $scope.exists function is getting called every second. Can someone please explain how ng-checked and $timeout related ? and how to avoid this ?
Reason in one word is: digest cycle. Since your function is bound to the view, every time digest cycle happens those expressions gets evaluated as a part of dirty check to make sure if respective DOM needs to be updated or not. This is nothing to do with angular material alone, it is the core angular implementation. Now in your case you are calling $timeout infinitely which means after each timeout execution digest cycle happens to perform dirty check.
Now what you have is fine, but whenever you bind a function to DOM (as a part of view binding, interpolation or property state attributes or even DOM filters - of course events are fine) you should be aware of the fact that you do not perform extensive operation in that function accidentally or intentionally as the app grows, it will slow down the entire app, and will be hard to refactor and diagnose when the app grows larger and problems starts happening. As much as possible bind to a property instead of a function. Note that even if you bind a property still angular $parse creates a getter on it and adds it to the $$watchers queue to be dirty checked every digest cycle, but difference is that it is a simple getter function.
So basically for instance in your case you could bind ng-checked to property
..ng-checked="doesExist"
and set the property doesExist whenever it needs to be updated. So with this instead of checking for the existence every time, you explicitly set the respective property when a respective event happens. That makes the logic explicit as well.
ng-checked, like many of angular's directives are based on watches. Anytime a digest cycle is called, it evaluates all of the watchers (which the function you are using is one of). So every time $timeout evaluates it is starting a new $digest cycle and evaluating all of the watchers. This is part of the "magic" that keeps the view updated with all of the data in your controllers and directives.
Watchers can become a performance issue if you make your functions complex, or make TONS of watchers. It is generally best to have simple logic that returns true or false very quickly and to avoid setting watches on everything.
In my angularjs project I need to go back and forth using both ng-src and src attributes in my tags.
Some images are static assets and will never change (they may be conditionally displayed) while other images are dynamic and dependent on scope variables.
May I use the mix of ng-src and src whenever I see fit?
I'm asking that because I once read that I should always use ng-src when working with angularjs, but I'm also afraid that I'm going to create bindings and watches that are really not necessary...
always using ng-src ?
I don't see any reason using ng-src without interpolation.
#Vojta Jína says: "watching attributes only makes sense, if you interpolate them"
The documentation says: "Using Angular markup like {{hash}} in a src attribute doesn't work right"
It not says using src in angular.js is not right...!
Same case with ng-href.
using ng-src without interpolation will probably not cause performance issues.
If you hit performance I guess ng-repeat is guilty.
does ng-src always creates bindings?
Actually the source code is pretty straightforward
https://github.com/angular/angular.js/blob/v1.2.7/src/ng/directive/booleanAttrs.js
ng-src will always $observe your attributes.
// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
var normalized = directiveNormalize('ng-' + attrName);
ngAttributeAliasDirectives[normalized] = function() {
return {
priority: 99, // it needs to run after the attributes are interpolated
link: function(scope, element, attr) {
attr.$observe(normalized, function(value) {
if (!value)
return;
attr.$set(attrName, value);
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
// to set the property as well to achieve the desired effect.
// we use attr[attrName] value since $set can sanitize the url.
if (msie) element.prop(attrName, attr[attrName]);
});
}
};
};
});
As for $observe, from the documantation:
The observer function will be invoked once during the next $digest
following compilation. The observer is then invoked whenever the
interpolated value changes.
Or more Simple, if there is no interpolation there is no dirty checking.
AFAIK, all performance issues angular.js has with (lots of) bindings is when it does dirty checking.
Looking further inside compile.js:
// no one registered attribute interpolation function, so lets call it manually
If there is no interpolation we end up invoking the callback only once.
In the case of ng-src you can see above the callback that $observe registers.
If there is an interpolation then angular will register a $watch (dirty checking) like so $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
Conclusion
use src when there is no interpolation
use ng-src when there is an interpolation
using ng-src without interpolation will end up running the callback only once. means it has subtle impact to performance.
Another issue is that images will not start loading until angular is bootstraped or in case you use routes (who's not?) until your views are compiled. This could a have a significant impact on load times.