I have a model that comes back from the server which contains html instead of text (for instance a b tag or an i tag)
when I use ng-repeat to built a list out of it it shows the html as pure text, is there a built in filter or directive that put's the html inside the li items or not? I've looked in the documentation but since I'm still very new to it I'm having difficulties finding it.
ng-repeat:
<li ng-repeat="opt in opts">
JSFiddle:
http://jsfiddle.net/gFFBa/1/
It goes like ng-bind-html-unsafe="opt.text":
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text" >
{{ opt.text }}
</li>
</ul>
<p>{{opt}}</p>
</div>
http://jsfiddle.net/gFFBa/3/
Or you can define a function in scope:
$scope.getContent = function(obj){
return obj.value + " " + obj.text;
}
And use it this way:
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="getContent(opt)" >
{{ opt.value }}
</li>
http://jsfiddle.net/gFFBa/4/
Note that you can not do it with an option tag: Can I use HTML tags in the options for select elements?
Note that ng-bind-html-unsafe is no longer suppported in rc 1.2. Use ng-bind-html instead. See: With ng-bind-html-unsafe removed, how do I inject HTML?
You can use NGBindHTML or NGbindHtmlUnsafe this will not escape the html content of your model.
http://jsfiddle.net/n9rQr/
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text">
{{ opt.text }}
</li>
</ul>
<p>{{opt}}</p>
</div>
This works, anyway you should be very careful when using unsanitized html content, you should really trust the source of the content.
use ng-bind-html-unsafe
it will apply html with text inside like below:
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text" >
{{ opt.text }}
</li>
Here is directive from the official examples angular docs v1.5 that shows how to compile html:
.directive('compileHtml', function ($compile) {
return function (scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compileHtml);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
});
Usage:
<div compile-html="item.htmlString"></div>
It will insert item.htmlString property as html any place, like
<li ng-repeat="item in itemList">
<div compile-html="item.htmlString"></div>
If you want some element to contain a value that is HTML, take a look at ngBindHtmlUnsafe.
If you want to style options in a native select, no it is not possible.
ng-bind-html-unsafe is deprecated from 1.2. The correct answer should be currently:
HTML-side: (the same as the accepted answer stated):
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text">
{{ opt.text }}
</li>
</ul>
<p>{{opt}}</p>
</div>
But in the controller-side:
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.opts.map(function(opt) {
opt = $sce.trustAsHtml(opt);
});
}
Related
I am using ng-repeat to print a list of posts to the page via the WordPress REST-API. I am using Advanced Custom Fields on each post. From what I can see everything is working, but the post data is not showing in one container, yet it is displaying in another. I should also mention that this is set up like tabs. (user clicks a tab for a post and it displays that posts data)
var homeApp = angular.module('homeCharacters', ['ngSanitize']);
homeApp.controller('characters', function($scope, $http) {
$scope.myData = {
tab: 0
}; //set default tab
$http.get("http://bigbluecomics.dev/wp-json/posts?type=character").then(function(response) {
$scope.myData.data = response.data;
});
});
homeApp.filter('toTrusted', ['$sce',
function($sce) {
return function(text) {
return $sce.trustAsHtml(text);
};
}
]);
HTML:
<section class="characters" ng-app="homeCharacters" ng-controller="characters as myData">
<div class="char_copy">
<h3>Meet the Characters</h3>
<div ng-repeat="item in myData.data" ng-bind-html="item.content | toTrusted" ng-show="myData.tab === item.menu_order">
<!--this is the section that will not display-->
<h3>{{ item.acf.team }}</h3>
<h2>{{ item.acf.characters_name }} <span>[{{item.acf.real_name}}]</span></h2>
<p class="hero_type">{{ item.acf.hero_type }}</p>
{{ item.acf.description }}
Learn More
</div>
</div>
<div class="char_tabs">
<!--if I put the identical {{}} in this section it WILL display-->
<nav>
<ul ng-init="myData.tab = 0" ng-model='clicked'>
<li class="tab" ng-repeat="item in myData.data" ng-class="{'active' : item.menu_order == myData.tab}">
<a href ng-click="myData.tab = item.menu_order">
<img src="{{ item.featured_image.source }}" />
<h3>{{ item.title }}</h3>
</a>
</li>
</ul>
</nav>
</div>
</section>
I should also mention that I use Ng-inspector, and it does show the data being pulled in. I can confirm this via the console. I have checked to ensure no css is in play; the div is totally empty in the DOM.
I appreciate all the help the GREAT angular community has shown!
The problem is you had used ng-bind-html over ng-repeat element which is changing the inner content of ng-repeat div. I guess as you have inner transcluded template inside ng-repeat directive, you should not be using ng-bind-html there in a place.
Markup
<div ng-repeat="item in myData.data" ng-show="myData.tab === item.menu_order">
<!--this is the section that will not display-->
<h3>{{ item.acf.team }}</h3>
<h2>{{ item.acf.characters_name }} <span>[{{item.acf.real_name}}]</span></h2>
<p class="hero_type">{{ item.acf.hero_type }}</p>
{{ item.acf.description }}
Learn More
</div>
I have a dinamically builded list of tests:
<script id="TestsTemplate" type="text/ng-template">
<article class="tests-main">
<section class="tests">
<div class="test" ng-repeat="test in tests">
<p>Question {{ test.number }}:</p>
<div ng-repeat="answer in test.answers">
<div ng-repeat="(key, value) in answer">
<p><button ng-click="processAnswer(value, $event)" class="btn" >○ {{ key }}</button></p>
</div>
</div>
</div>
</article>
</script>
and I want to add a class to the div(class="test") in which button was pressed.
I am familiar only with the jQuery and I can think of only this approach:
$scope.processAnswer = function(isRight, event) {
isRight ? $scope.count++ : $scope.count--;
event.target.parentNode.parentNode.parentNode.addClass('hide');
}
How to do it in Angular style ?
EDIT:
My solution:
Use ng-class and $index:
<div ng-class="($index==selectedIndex) ? 'hide' : 'test'" ng-repeat="test in tests">
controller:
$scope.selectedIndex = -1;
$scope.processAnswer = function(isRight, index) {
isRight ? $scope.count++ : $scope.count--;
$scope.selectedIndex = index - 1; //$index starts at 0;
}
where index is test.number
Try like this
angular.element(event.target).parent().parent().addClass("hide");
Manipulating dom in html is not recommended . You should do your dom manipulation only in directives.
You could maybe use ng-class to apply the class when you set a boolean to true. For example :
<div class="test" ng-repeat="test in tests" ng-class="{'hide' : onProcessAnswer}">
and in your controller
$scope.onProcessAnswer = false;
$scope.processAnswer = function(isRight, event) {
...
$scope.onProcessAnswer = true;
...
}
In my opinion, Angular is a library to make coders do less dom option.
You can use data-bind feature to achieve most request mostly.
If you do want to get a parent element and add an attribute, just use jQuery or pure javascript.
Try as simple
$element.parent().addClass('default-class');
Here is my code:
<body ng-controller="QueryCntl">
<div>
<h1> Target:{{target}}</h1>
</div>
<div ng-app="myApp" ng-controller="mytableCtrl">
<div ng-include="'header.html'"></div>
<div id="menuList">
<ul class="menuNav">
<li class="menuList_Slide" ng-repeat="x in names">
<div>
{{ x.category }}
</div>
<div ng-if="{{target}} == {{x.id}}"> //Display subcategory if true
<ul id="subCategories">
<li>
Childid should be displayed
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
<script>
var app = angular.module('myApp', [], function($locationProvider) {
$locationProvider.html5Mode(true);
});
function QueryCntl($scope, $location) {
$scope.target = $location.search()["categoryid"];
}
app.controller('mytableCtrl', function($scope, $http) {
$http.get("api/topCategories")
.success(function(response) {$scope.names = response;});
});
</script>
I want to display only 1 subcategory if {{target}} is equal to {{x.id}}. But for now it prints everything... {{target}} gets value from the url where ?categoryid=some_number and x.id is a value from DB. These values works fine in my code.
I would appreciate any help.
EDIT: ng-if="target == x.id" does not help.
ngIf doesn't need to use {{}} - it's already an Angular expression:
div ng-if="target == x.id"
Problem solver:
I inserted
<base href="category.html">
in head tags and deleted bad script source.
You don't need to interpolate properties within the ng-if directive's conditional as Angular evaluates the complete expression against the scope already. Try this:
ng-if="target == x.id"
Replace:
<div ng-if="{{target}} == {{x.id}}">
With:
<div ng-if="target == x.id">
The ng-if attribute should contain plain JavaScript, instead of those interpolation tags ({{}}).
This holds true for all ng- attributes.
ngIf directive expects an expression, so you don't need interpolation tags:
<div ng-if="target == x.id"> //Display subcategory if true
<ul id="subCategories">
<li>
Childid should be displayed
</li>
</ul>
</div>
And one more thing: you are not initializing $scope.target because corresponding controller is outside of the ng-app. Remove ng-controller="QueryCntl" and add target initialization into mytabeCtrl:
app.controller('mytableCtrl', function($scope, $http) {
$scope.target = $location.search()["categoryid"];
$http.get("api/topCategories")
.success(function(response) {$scope.names = response;});
});
I've written a directive that dynamically creates a popover for an element:
app.directive('popover', function($compile, $timeout){
return {
link: function(scope, element, attrs) {
$timeout(function() {
// grab template
var tpl = $(element).find('.popover-template')
// grab popover parts of template
var template = {
//$compile( $(element).siblings(".pop-content").contents() )(scope)
title: tpl.find('.template-title').contents(),
content: tpl.find('.template-content').contents()
};
// render template with angular
var content = $compile(template.content)(scope);
var title = $compile(template.title)(scope);
$(element).popover({
html: true,
placement: "right",
content: content,
title: title
});
scope.$digest()
});
}
};
});
In application it looks like this:
<span popover>Click me</span>
<div ng-hide="true" class="popover-template">
<div class="template-title">
<strong>{{ x.name }} and {{ y.name }}</strong>
</div>
<div class="template-content">
<div>
<pre>f in [1,2,3]</pre>
<div ng-repeat="f in [1,2,3]">
item {{ f }}, index {{ $index }}
</div>
</div>
</div>
</div>
The popover is created and displayed. The title works correctly as well. However, ng-repeat is applied multiple times in any iteration:
As you can see, the iteration that should only include 3 elements in fact includes 3*3 elements. The directive creates popovers for exactly 3 elements, so I guess that's where my mistake lies. How can I make sure that within each popover, ng-repeat is only called once?
The problem
Since the popover-template element is already in the document when you bootstrapped the angular application (at page load), it has already been compiled once. The ng-repeat element is replaced with 3 new elements:
<!-- original -->
<div ng-repeat="f in [1,2,3]">item {{ f }}, index {{ $index }}</div>
<!-- replaced -->
<div ng-repeat="f in [1,2,3]">item 1, index 0</div>
<div ng-repeat="f in [1,2,3]">item 2, index 1</div>
<div ng-repeat="f in [1,2,3]">item 3, index 2</div>
When you compile it again in the link function, each of the 3 ng-repeats is triggered, making 3 identical copies, 9 total.
The solution
Keep your popover-template in a separate file so it is not compiled on page load. You can then load it with the $templateCache service.
In general, just make sure you don't compile your HTML multiple times.
Instead using the compiled html for the popover template, load the template using $http or templateCache.
The HTML:
<span popover>Click me</span>
<script type="text/ng-template" id="popover.html">
<div class="popover-template">
<div class="template-title">
<strong>{{ x.name }} and {{ y.name }}</strong>
</div>
<div class="template-content">
<div>
<pre>f in [1,2,3] track by $index</pre>
<div ng-repeat="f in [1,2,3]">
item {{ f }}, index {{ $index }}
</div>
</div>
</div>
</div>
</script>
The Javascript:
angular.module('app',[]).directive('popover', function($compile, $timeout, $templateCache){
return {
link: function(scope, element, attrs) {
$timeout(function() {
// grab the template (this is the catch)
// you can pass the template name as a binding if you want to be loaded dynamically
var tpl = angular.element($templateCache.get('popover.html'));
// grab popover parts of template
var template = {
title: tpl.find('.template-title').contents(),
content: tpl.find('.template-content').contents()
};
// render template with angular
var content = $compile(template.content)(scope);
var title = $compile(template.title)(scope);
$(element).popover({
html: true,
placement: "right",
content: content,
title: title
});
scope.$digest()
});
}
};
});
Also, I have made this plunker with an working example: http://embed.plnkr.co/IoIG1Y1DT8RO4tQydXnX/
I'm trying to display some comment with two nested ng-repeat:
<div ng-repeat="element in elements" >
<ul>
<!-- comments -->
<li ng-repeat="comment in comments">
<span>Comment</span> <strong>{{comment.username}}</strong>
<blockquote>{{comment.content}}</blockquote>
</li>
</ul>
</div>
Here's what's in the controller:
$scope.comments = [];
$scope.populateComments = function(content_id){
ProjectsService.commentsQuery(content_id)
.then(function (data) {
$scope.comments = data;
})
}
$scope.populateComments(element.id);
Obviously it's not working, my problem is that I don't know how, nor if it's possible, to access the property of an object of the ng-repeat scope (to get element.id and get only the comments related to the element involved).
Any ideas?
Thank you in advance!
You can access the property of an object of the ng-repeat scope. You need to re-factor your code.
In Html
<div ng-repeat="element in elements" >
<ul>
<!-- comments -->
<li ng-repeat="comment in element.comments">
<span>Comment</span> <strong>{{comment.username}}</strong>
<blockquote>{{comment.content}}</blockquote>
</li>
</ul>
</div>
In JS
$scope.populateComments = function(element){
ProjectsService.commentsQuery(element.id)
.then(function (data) {
element.comments = data;
})
}
$scope.populateComments(element);