Angular.js nesting curly braces in ng-bind-html - javascript

I am making a spelling app using JavaScript and Angular.js. When the user has spelled their word correctly, the view should say "Word already completed!", and when the user has not yet completed their word, the view should display a list of friends.
Therefore, I need to change the innerHTML of the object from within my JS script (so that it updates when the user spells their word correctly).
This is the if statement that I use to decide between the two innerHTMLs:
if(wordCompletedVar) {
$scope.friendsHTML = "<li><a> Word already completed! </a></li>";
} else {
$scope.friendsHTML = "<li class='user-display' style='padding: 15px' ng-repeat='friend in friendNames track by $index'><img src='{{profilePicture($index)}}' class='img-circle user-display-img'><div class='user-display-name'><a href = '#/trade/{{getFriendUsername($index)}}' ng-click='playSound('whoosh')' style = 'color: white'>{{friend}}</a></div></li>";
}
In my HTML doc, I am trying to inject friendsHTML using ng-bind-html:
<ul class="dropdown-menu" id="friendsDropdown" ng-bind-html = "to_trusted(friendsHTML)"></ul>
It works with the simple sentence, but with the more complicated HTML (with nested Angular.js curly brackets {{}}), the brackets display instead of the angular object (i.e. {{friends}}). The same happened when I used ng-bind-html without the to_trusted function.
This is a clearer view of the HTML I am trying (and failing) to inject:
<li class='user-display' style='padding: 15px' ng-repeat='friend in friendNames track by $index'>
<img src='{{profilePicture($index)}}' class='img-circle user-display-img'>
<div class='user-display-name'>
<a href = '#/trade/{{getFriendUsername($index)}}' ng-click='playSound('whoosh')' style = 'color: white'>{{friend}}</a>
</div>
</li>
The to_trusted function that I am using is:
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
};
(and I do include $sce in my controller).
How can I nest the curly-bracket angular notation within the ng-bind-html injection? I have found a lot of documentation on either the curly brackets or ng-bind-html, but I haven't seen anything on how to use them together (or how to accomplish what I am trying to accomplish in a different way-I am open to suggestions!)

The best way to handle this would be to avoid injecting HTML at all, and instead use a conditional statement like ng-if or ng-show.
something like the following:
<li ng-if="wordCompletedVar"><a> Word already completed! </a></li>
<li ng-if="!wordCompletedVar" class='user-display' style='padding: 15px' ng-repeat='friend in friendNames track by $index'>
<img src='{{profilePicture($index)}}' class='img-circle user-display-img'>
<div class='user-display-name'>
<a href = '#/trade/{{getFriendUsername($index)}}' ng-click='playSound('whoosh')' style = 'color: white'>{{friend}}</a>
</div>
</li>

An alternative would be along the lines of making a directive as below. You can modify parameters if you want reuse etc -
app.directive('userDisplay', ['$compile',function ($compile) {
return {
template : '<input/>',
link: function(scope, element, attrs) {
scope.$watch('wordCompletedVar',function(){
var html;
if(scope.wordCompletedVar) {
html = "<li><a> Word already completed! </a></li>";
} else {
html= "<li class='user-display' style='padding: 15px' ng-repeat='friend in friendNames track by $index'><img class='img-circle user-display-img'><div class='user-display-name'><a href = '#/trade/{{getFriendUsername($index)}}' ng-click='playSound(\"whoosh\")' style = 'color: white'>{{friend}}</a></div></li>"
}
element.html(html);
$compile(element.contents())(scope);
});
}
};
}]);

Related

Render html tags from AngularJS object

I've looked up similar questions on how to render html tags within angularJS but none of them worked for me because I am using ng-repeat. Anyone have any advice.
<div ng-repeat="o in SurveyResults.SurveyQuestions">
{{o.SurveyQuestionId}}
{{o.QuestionText}}
</div>
My o.QuestionText has data such as <b>How well do we deliver on our promises?</b> <br />Consider our responsiveness to your requests, ability to meet deadlines and <i>accountability for the outcomes</i>.
It displays the tags without rendering the tags.
I did try putting this in my JS file
homePage.directive('htmlSafe', function($parse, $sce) {
return {
link: function(scope, elem, attr) {
var html = attr.ngBindHtml;
if(angular.isDefined(html)) {
var getter = $parse(html);
var setter = getter.assign;
setter(scope, $sce.trustAsHtml(getter(scope)));
}
}
};
});
Then setting html bind in the repeat as such
<div ng-repeat-html="o in SurveyResults.SurveyQuestions">
{{o.SurveyQuestionId}}
{{o.QuestionText}}
</div>
Any other advice?
Just use ng-bind-html, you don't need new directive
<div ng-repeat="o in SurveyResults.SurveyQuestions">
{{o.SurveyQuestionId}}
<span ng-bind-html="o.QuestionText"></span>
</div>

binding to controller object in Angular

I'm new to angular, trying to bind an an element's content into the controller's Scope to be able to use it within another function:
here is the scenario am working around:
I want the content of the <span> element {{y.facetName}} in
<span ng-model="columnFacetname">{{y.facetName}}</span>
to be sent to the controller an be put in the object $scope.columnFacetname in the controller
Here is a snippet of what I'm working on:
<div ng-repeat="y in x.facetArr|limitTo: limit track by $index ">
<div class="list_items panel-body ">
<button class="ButtonforAccordion" ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)">
<span>{{$index+1}}</span>
<span ng-model="columnFacetname">{{y.facetName}}</span>
<span>{{y.facetValue}}</span>
</button>
</div>
</div>
angular.module('mainModule').controller('MainCtrl', function($scope, $http) {
$scope.columnFacetname = "";
$scope.ListClicktnColumnFilter = "";
$scope.ListClicktnColumnFilterfunc = function() {
$scope.ListClicktnColumnFilter = "\":\'" + $scope.columnFacetname + "\'";
};
}
the problem is that the $scope.ListClicktnColumnFilter doesn't show the $scope.columnFacetname within it, meaning that the $scope.columnFacetname is not well-binded.
In your ng-click instead of calling two different function
ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)"
you can declare like this
ng-click="columnFacetname = y.facetName; onServerSideButtonItemsRequested(columnFacetname , myOrderBy)"
You are trying to pass that model to another function by assigning it to ListClicktnColumnFilter in your controller
By doing in this way, you can achieve the same thing.
I have done one plunker with sample array,
http://embed.plnkr.co/YIwRLWXEOeK8NmYmT6VK/preview
Hope this helps!

Evaluate an expression from within a JSON file

I have an Angular app where data is loaded in through JSON files. For the various objects, one of the properties is a "Description". In my app I pop it in my html via {{item.Description}}. My problem is that the string in the JSON file has values that need to be adjusted based on a variable. For example, "The value is 160 (+20 per var)". I would like this description to read out 160 plus 20 times the value of the provided variable.
Unfortunately I can't just put {{160+(20*var)}} in the description, because it just prints out the expression as string.
Is there anyway to create that binding in angular so it updates dynamically based on the other variable?
Update
As per request I'm adding as much code as I can.
In my file's head I'm including a JSON file with:
<script src="path/to/file.json"></script>
Then, I have my controller:
app.controller('itemController', function(){
this.items = Items //Items is declared in the JSON file as the JSON object.
});
Then in my HTML I call:
<div ng-controller="itemController as ctrl">
<span class="description" ng-repeat="item in ctrl.items">
{{item.Description}}
</span>
</div>
The problem is, that item.Description has expressions I would like to evaluate. I would normally just do {{160+(20*ctrl.var)}}, but since that expression is contained in the item.Description string, Angular doesn't evaluate it normally.
It appears you can do this by replacing {{item.Description}} with {{$eval(item.Description)}}, which will evaluate a string as an Angular expression. See the Angular docs for expressions, or a StackOverflow post about $eval.
Edit: OP has clarified that item.Description may contain mixed Angular expressions and other text, for example "The value is {{85 + 22 * ctrl.var}}". Fortunately the Angular docs for $compile contain an example that solves this exact problem! Here is a brief demo.
angular.module('app', [])
.directive('compile', function($compile) {
// directive factory creates a link function
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
})
.controller('itemController', function() {
this.var = 5;
this.items = [
{Description: "What's 1+1? It's {{1+1}}"},
{Description: "The value is {{85+22*ctrl.var}}"},
{Description: "He{{'llo'}} World!"}
];
});
body {
font-family: sans-serif;
}
#main > span {
background-color: #ddd;
padding: 8px 16px;
margin: 8px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app" class="ng-scope">
<h1>Compile Test!</h1>
<div ng-controller="itemController as ctrl" id="main">
<span class="description ng-scope" ng-repeat="item in ctrl.items" compile="item.Description"></span>
</div>
</body>

Is there a way to pass a variable with curly brace template tags through a method?

I have an ng-repeat where I run an ng-if with a method that checks to see if certain parameters match between two sets of data. If there's a match, it updates $scope with the current match, which I can use to output my template:
<div ng-repeat="repeatedItem in repeatedItems">
<a href="" ng-if="!matchData(repeatedItem.item1, repeatedItem.item2)">
<img ng-src="{{ matchedItem.falseImageUrl }}" />
</a>
<a href="" ng-if="matchData(repeatedItem.item1, repeatedItem.item2)" ng-click="open(matchedItem.id)">
<img ng-src="{{ matchedItem.imageUrl }}" />
<time>{{ matchedItem.date }}</time>
<div class="button-wrapper">
<button class="btn">{{ matchedItem.text }}</button>
</div>
</a>
</div>
The thinking is that everything out of repeatedItems needed to show, unless an item in repeatedItem matched another data set, in which case you'd show the matchedItem instead.
Everything works fine, except for the ng-click="open(matchedItem.id) bit. Because matchedItem.id isn't wrapped in template tags, ng-click is always calling the latest iteration of matchedItem, so for all but the last repeated element, it's opening the wrong link.
The most obvious solution in my Angular-inexperienced mind would have been to do something like ng-click="open( {{ matchedItem.id }} ), but that throws an error. My next idea was something like ng-click="open( {0} ):{{ matchedItem.id }} - a printf-type of solution, but I haven't found any built-in Angular solution for that. I also thought about stashing {{ matchedItem.id }} in an attribute somewhere (data-id="{{ matchedItem.id }}"?), but I'm not sure how to call that attribute in the method.
It's possible (probable?) that I'm just not "thinking in Angular" yet, and I'm going about this the wrong way entirely. Or perhaps there's a directive that I'm just not aware of?
Here's the method:
$scope.matchData = function(item1, item2) {
var isItAMatch = false;
for (i=0; i<$scope.repeatedItems.length; i++) {
itemAToMatch = $scope.repeatedItems[i].itemA;
itemBToMatch = $scope.repeatedItems[i].itemB;
if (itemAToMatch == item1 && itemBToMatch == item2) {
isItAMatch = true;
$scope.matchedItem = $scope.repeatedItems[i];
break;
}
}
return isItAMatch;
}

How do I leave static content if property is empty in angular?

I have a simple angular controller in my app. And I wish to enhance a page containing static content, with angular. This will allow me to have a good, content rich page as fall back for those without JS (who?!) and web crawlers and readers.
So far I have identified a few ways to do this, possibly using custom directives, or by doubling up content in 'ng-bind-template' directive (which is obviously not ideal).
I am fairly new to the world of angular so would like to try and garner the best practice for this scenario.
What is the best approach for this?
Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Markup:
(The problem here is that 'Static content' is replaced by '' if angular initializes)
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
EDIT
I should be clear that even though angular is bootstrapping, The aim is to leave the default content intact, and not replace it with dynamic content. That only wants to be achieved once the activate button is clicked. I have tried the following but it involves doubling up the content which could get bulky if it is a whole article.
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle || "Static Content"}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent || "Some more content"}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div> <a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
You can create a simple directive to create the html structure that you want.
I propose this to be your new html:
<div ng-cloak sk-custom-bind="eTitle">Static content</div>
Notice how you don't have to specify the or clause with the static content.
Now you can create a directive that will build the html template for you:
app.directive('skCustomBind',function() {
return {
restrict: 'A',
template: function(elem, attrs) {
var templ = "<div ";
templ += "ng-bind-template='{{";
templ += attrs.skCustomBind;
templ += ' || "';
templ += elem.text();
templ += '"';
templ += "}}'></div>";
return templ;
},
replace: true
};
});
And as simple as that you have the functionality that you are looking for without the duplication.
See this plunker to see a working sample.
The thing here is that when you define the controller, angular will parse through the html to find all the variables and link them with the corresponding controller items using $scope.
So, when angular initializes, since eTitle and eContent are not having any values (since you initialize them in the function only), which is called on click of the last item(where on your application works fine).
So until you click the button, the static text is replaced by $scope.eTitle and $scope.eContent both of which are undefined. Hence no text inside the tags.
So the solution,
1. Either you initialize the variables inside the controller with the static text, like you did for 'This is angular injecting this'(reason only this came!):
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this";
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Or you can initialize the variables in the html using ng-init, it will initialize the variable in html only:
<div ng-controller="TestArticle">
<div ng-init="eTitle = 'Static Content'" ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-init="eContent = 'Some more content'" ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
The thing is when you start using angular, the html will get kind of hijacked and angular will start using its own variables.
Thanks to #JoseM for putting me on to right track and helping me find the following solution...
SOLUTION
My Markup:
NOTE: the custom directive and properties ('cg-dyn' and 'cg-content'). These will not initialise any values when angular bootstraps. And further down you will see how these map from controller scope via an isolated scope.
<div ng-controller="TestArticle">
<div ng-cloak cg-dyn cg-content="eTitle">Static content</div>
<div ng-cloak cg-dyn cg-content="eContent">Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="upDateTitle()" ng-cloak>Update title</a> | <a href ng-click="upDateContent()" ng-cloak>Update content</a>
</div>
My Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.upDateTitle = function(){
$scope.eTitle = "Dynamic title";
};
$scope.upDateContent = function(){
$scope.eContent = "Dynamic Content Lorem ipsum";
};
});
And finally the magic in My Directive
app.directive('cgDyn',function() {
return {
restrict: 'A',
scope:{
'content':'=cgContent'
// this is a two way binding to the property defined in 'cg-content
},
link: function(scope, elem, attrs) {
// below is a watcher keyed on a combination content
scope.$watch('content',function(){
if(scope.content){
elem.html(scope.content);
// and here is the bit, normally plumbed by default, but instead we only replace the html if the value has been set
}
});
},
controller: 'TestArticle',
replace: false
};
});
Thanks for everyone's help. I am sure there is an even better solution out there so keep the suggestions coming!

Categories

Resources