I know from multiple blogs and some questions here in stackoverflow that in Angular 1 ng-bind has better performance than {{ }} interpolation because of the way the $digest process works.
Angular 2 has changed the way it does data-binding and I want to know if there is any test on the subject. Specifically if this
<h1 [innerText]="obj.name"></h1>
is still better than this
<h1> {{ obj.name }} </h1>
Using getTitle() method as example. checkBinding is debug mode check, can be ignored.
Attribute binding calls sanitize and el.setAttribute:
var currVal_0 = self.context.getTitle();
if (jit_checkBinding34(throwOnChange,self._expr_0,currVal_0)) {
self.renderer.setElementAttribute(self._el_0,'innerHTML',((self.viewUtils.sanitizer.sanitize(jit_SecurityContext36.HTML,currVal_0) == null)? null: self.viewUtils.sanitizer.sanitize(jit_SecurityContext36.HTML,currVal_0).toString()));
self._expr_0 = currVal_0;
}
Interpolation, calls el.textContent = value;:
var currVal_0 = jit_interpolate36(1,'',self.context.getTitle(),'');
if (jit_checkBinding34(throwOnChange,self._expr_0,currVal_0)) {
self.renderer.setText(self._text_1,currVal_0);
self._expr_0 = currVal_0;
}
The only difference is using sanitize, you can check html_sanitizer.ts source code to see that is does some complicated stuff.
Interpolation: It represents as {{}}. it may be concatenate two string ,calculate value and display value.
Property Binding: It represents as [].It is mainly used for non-concatenate string like variable.
Interpolation is a convenient alternative for property binding in many cases.
In fact, Angular 2 translates those interpolations into the corresponding property bindings before rendering the view.
In Angular 2, I think there is no technical reason to prefer one form to the other. You should be choosing the form that feels most natural for the task.
Related
I'm reviewing old classes and I try to finish the exercices I couldn't do before. This class is in Ionic1, using Angular1.
I have a directive using two parameters; the first one is an object which data are to be displayed, and the second one is a parameter to hide/show some elements in the display
Here is the view implementing the controller :
<ion-list>
<film-directive
ng-repeat="tmpMovie in myController.movieList"
movie="tmpMovie"
displayBtnAddFav="false"
></film-directive>
</ion-list>
And here is the directive construction :
const FilmDir = function(){
return {
"restrict":"E",
"scope":{
"movie" :"=",
"displayBtnAddFav" :"&"
},
"template":`
<ion-item>
<p ng-if="displayBtnAddFav">DISPLAY WHEN TRUE</p>
<p ng-if="!displayBtnAddFav">DISPLAY WHEN FALSE</p>
</ion-item>`,
"controller":function($scope){
//TODO
}
}
};
All the files are correctly referenced. My directive is displayed in the view, but the "displayBtnAddFav" value isn't interpreted correctly. The "DISPLAY WHEN TRUE" is always displayed
I tried :
calling the directive with displayBtnAddFav="false"
calling the directive with displayBtnAddFav=false
replacing the boolean value by a string ("a" or "b") and using ng-if="displayBtnAddFav==='a'"
Nothing works as intended and I seem to be out of options. Would any of you see what I'm doing wrong?
So I think the issue here is the scope binding:
Per the angular documentation: & bindings are ideal for binding callback functions to directive behaviors. (https://docs.angularjs.org/guide/directive)
Different bindings are ideal for different scenarios. Try changing it from a & to an =. This should allow for angular to interpret the boolean your trying to pass correctly.
const FilmDir = function(){
return {
"restrict":"E",
"scope":{
"movie" :"=",
"displayBtnAddFav" :"="
},
"template":`
<ion-item>
<p ng-if="displayBtnAddFav">DISPLAY WHEN TRUE</p>
<p ng-if="!displayBtnAddFav">DISPLAY WHEN FALSE</p>
</ion-item>`,
"controller":function($scope){
//TODO
}
}
};
Thanks a lot Kyle for your input.
After some more tests, it appears you're right despite what the doc was telling me.
Another crucial point I realized it that the directive doesn't like "camelCase" arguments : I had to change the displayBtnAddFav to displaybtnaddfav for it to work properly.
Here is my issue:
I am using ng-repeat to make a list of spans.
Each span has the contenteditable attribute and ng-model directive.
Everything works as expected (including two-way data binding), until I try to add a new item to the list.
<div ng-repeat="item in list">
<span ng-model="item.text" contenteditable></span>
</div>
<button ng-click="addItemToList"></button>
The methods look like this:
$scope.addItemToList = function () {
$scope.list.push({text: 'dummy text'});
}
or
$scope.addItemToList = function () {
$scope.list.splice(1, 0, {text: 'dummy text'});
}
When adding the new item to the list (push or splice), the DOM updates, but the last item is initialised empty, with no text whatsoever. The last item in the model list also empties out, even if I specifically push an element with text in it.
After a few tests, I've noticed that this only happens if the list's length is bigger after modifying it:
if I try to replace/modify/remove (not add) an element in the list, it works well.
I believe this has to do with the way contenteditable elements initialise in the DOM (I think they initialise empty, and somehow, the model empties out as well).
Has anyone encountered this problem before? If yes, how did you solve it / what workaround have you found?
Based on the angular docs related to ngModelController, it seems that there is not built-in support for two-way data binding with contenteditable elements, seeing as in the plunkr example they wrote their own contenteditable directive. You might be able to use a modified version of that as a workaround.
It looks to be a similar problem as this question and the contenteditable directive there looks similar to the contenteditable directive in the angular docs example.
I also found this directive on github that might accomplish what you are trying to do.
Edit: I did a new version of the plunk I posted in the comment above:
https://plnkr.co/edit/v3elswolP9AgWHDIPwCk
In this version I added a contenteditable directive that appears to be working correctly. It is basically a spin off of how the input[type=text] directive is written in angular, but I took out the places where it handles different types of input (since in this case it will just be text) and the places where it handles events that contenteditable elements don't even fire. I also changed it so that the $viewValue gets updated based on element.html() instead of element.val(). You might be able to use something like this as a workaround
The issue is old but that was the same problem for me today. (angular 1.5). My workaround was to add on blur update option: <td contenteditable data-ng-model="position.value" ng-model-options="{updateOn: 'blur'}"></td> somehow then model stopped getting be empty on initialize. I like using update on blur in many places (solves some performaces issues) so this is good for me, however it's some kind of trick.
I get JSON like this
{
"lots of":"keys"
"description" : {
"key":"Some sample key",
"value":"This is the markup™"
}
}
from server and I ultimately iterate the description objects and populate table rows with two columns: one for the key and one for the value.
I have tried putting on my <td> tag ng-bind-html as well as injecting $sce into my controller and using trustAsHtml but so far the string always displays as it is in the JSON. Not every value will be HTML but I can easily detect based on the key if HTML is a possibility. It seemed when I put in the directive on the td it did not display anything if no HTML was present. I am interested in using something that can allow HTML in the value but not require it so I can display either
HTML fragment
<tr ng-repeat="(key, val) in record.description">
<td>{{key}}:</td>
<td>{{val}}</td>
</tr>
I created a quick fiddle here:
https://jsfiddle.net/frishi/mvw97s3q/6/
I used angular-sanitize, which I am not sure you mentioned injecting in your module dependency list. Either way, the example works simply by using ng-bind-html
Relevant docs page: https://docs.angularjs.org/api/ng/directive/ngBindHtml
It works by using the directive ng-bind-html on the element you want to display the HTML string in. You use it like so:
<p ng-bind-html="data.firstName"></p>
assuming that data.firstName = "<strong>FeeFee</strong>" or something like that.
I would also like to add that Angular does not allow this natively because of legitimate security concerns. That and the fact that allowing arbitrary HTML to be rendered might not always produce desirable results. Your page layout could quite possibly break because of some HTML you allowed to be passed through.
Angular was designed with security in mind, and will prevent you from displaying HTML from raw strings whenever possible - to prevent various injection attacks.
Here is workarround for your problem: AngularJS: Insert HTML from a string. Generally you should use ng-bind-html insted of ng-bind (this is used by curly braces).
In my controller I have an array of objects. The object is called Well has a few properties, one of which is Location, which stores a string like "A1", "B4", "B13", etc. The location indicates a position on a grid. The letter represents the row, and the number represents the column.
Now that I have this nice list of objects, I would like to display them all on a grid in my view. When I say grid, I mean that loosely. The grid I have come up with is a series of divs, each with an id equal to a location name.
I have created a directive called tile that will display the properties of a single object. The directive looks like so:
<div class="row">
<div class="col-md-3" ng-repeat="well in wellArray">
<ul><li ng-repeat="prop in well">{{ prop }}</li></ul>
</div>
</div>
Great! And then I can create a tile in my view for a specific Well in the list of Well objects like so:
<div tile name="{{my.getName()}}" dil="{{my.getDilution()}}"></div>
If this list of objects was ordered by the location property, I could simply turn it into an array of arrays, one array for each row, and then use a double ng-repeat in my view. Unfortunately they are not in order, and I do not want to create a sorting method given the format that the location property is in. If i were to do the double ng-repeat on this list as it is now, I would end up with a grid of tiles that are in no particular order.
Given my limited exposure to javascript, I thought of using jquery's .append() method.(note: i have referenced jquery before angular, so angular.element() will use the jquery library instead of jqlite so I can use jquery selectors) In my view I created a bunch of divs in the following format:
<div id="A1"></div>
<div id="A2"></div>
etc.
And then in my controller I created a method that attempts to append a single Well which has a location of "A1" to the element on the view that has an id="A1". My code looks like so:
angular.element('#A1').append('<div tile name="{{my.getName()}}" dil ="{{my.getDilution()}}"></div>');
I thought it would append the div with the tile directive, to the div with id="A1", however, it does nothing. In fact, there are no errors at all.
Surely my psuedo jquery approach is not the best way to go about this. Not only is it not working (no idea why, maybe because angular needs to compile something somehow), but it's also not a very Angular approach. I keep reading in tutorials and introductions to "not use jquery at all for the first few weeks" and "90% of the things you'll waste lines of code in jquery, can be done suceinctly in Angular". Someone please lend this poor excuse of a programmer a hand!!
Just following your example in comments with .append, instead of iterating over your array and appending elements to a container element, create a conceptual representation of the data, and then use it in the view.
In controller, do something like the following:
$scope.wellData = {};
for (well in wellArray){
var key = wellArray[well].getLocation();
$scope.wellData[key] = well;
}
Then in the view, do ng-repeat over wellData:
<div id="item.getLocation()" ng-repeat="item in wellData">
<div tile name="{{item .getName()}}" dil="{{item .getDilution()}}"></div>
</div>
You definitely should stay away from jQuery in controllers. Just assume that there is no DOM in controllers whenever you get the urge to do anything related to DOM. Controller deals with ViewModels which are conceptual representation of the view, but it is view-independent. Whenever you break that separation, you make your controllers harder to test, and you make your view more difficult to change. And, by going against MVVM principles, you will keep bumping into issues with AngularJS.
I have the component and have a problem setting the css class to it.
I want it to always have a class of "box", then to have additional classes specified by the directive "class" argument and one conditional class "mini".
Conceptually what I want to achieve is something like this:
<div class="box {{class}}" data-ng-class="{mini: !isMaximized}">
...
</div>
The problem is that when I set the class html attribute, the ng-class attribute is omitted.
How to make my example work without changing the controller? Is it even possible, or should I set the class in the controller instead (which I wish to avoid)?
A quick solution would be define the box class inside ng-class attribute:
<div data-ng-class="{mini: !isMaximized, box: true}"></div>
If you want to include a scope variable as a class, you can't use ng-class:
<div class="{{class}} box {{!isMaximized && 'mini' || ''}}">
Angular expressions do not support the ternary operator, but it can be emulated like this:
condition && (answer if true) || (answer if false)
I needed multiple classes where one was $scope derived and others were literal classes. Thanks to the hint from Andre, below worked for me.
<h2 class="{{workStream.LatestBuildStatus}}"
ng-class="{'expandedIcon':workStream.isVisible, 'collapsedIcon':!workstream.isvisible}">{{workStream.Name}}</h2>
Edit: for newer versions of Angular see Nitins answer as it is the best one atm
For me, this worked (I'm working on AngularJS v1.2.14 at the moment so I guess 1.2.X+ should support this, not sure about the earlier versions):
<div class="box" data-ng-class="{ {{myScopedObj.classesToAdd}}: true, mini: !isMaximized }"></div>
I replaced your {{class}} with {{myScopedObj.classesToAdd}} to show that any scoped variable or even a bit more complex object can be used this way.
So, every DIV element crated this way will have "box" class and any class contained within myScopedObj.classesToAdd (useful when using ng-repeat and every element in the array needs to have a different class applied), and it will have the "mini" class if !isMaximized.
Another way to do this without double curly braces and includes scope variables, tested with angular v1.2+.
<div ng-class="['box',
aClass,
{true:'large': false: 'mini'}[isMaximized]]"></div>
It's also rather nice because the variable can use different types as a index without increasing complexity using ternaries. It can also remove any need for negations ;)
Here is a fiddle link
You can use simple expression given below
ng-class="{'active' : itemCount, 'activemenu' : showCart}"