ng-bind-html renders data as HTML but ignores children elements - javascript

I'm working on this function that generates HTML based on user input and I can render it as proper HTML inside a list item (instead of a string version) using ng-bind-as-html.
The only problem is that it doesn't render the span tag that is also a child element in the li tag. I'm an Angular n00b and could use any insight ya got.
The code below allows me to have my functions output as proper HTML, which is nice, but I need that span tag to show up as it allows my user to copy the contents of the text.
Basically, I can either rewrite without ng-bind-html and have the span render appropriately, or I can have my HTML output render and not get the span tag. I'm stuck with one or the other and not both... and I want both. Classic.
Thanks for your help!
<li
class="entry"
ng-repeat="entry in output track by $index"
ng-mouseenter="onEnter()"
ng-bind-html="entry">
{{entry}}
<span
class="copy"
ng-click="copyData(entry)"
ng-mouseenter="onEnter()">
{{message}}
</span>
</li>

You can create a custom directive to achieve this with transclude and ng-transclude as below.
Documentation for ng-transclude
var app = angular.module('app', []);
app.controller('TestController', ['$scope', function($scope) {
$scope.message = 'You are welcome!';
$scope.output = ['<h1>Hello user</h1>'];
}]);
app.directive('bindHtml', ['$sce', function($sce) {
return {
restrict: 'A',
transclude: true,
template: '<span><span ng-bind-html="html"></span><span ng-transclude></span></span>',
link: function (scope, element, attrs) {
scope.html = $sce.trustAsHtml(scope.$eval(attrs.bindHtml));
}
};
}]);
angular.bootstrap(document, ['app']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="TestController">
<li
class="entry"
ng-repeat="entry in output track by $index"
ng-mouseenter="onEnter()"
bind-html="entry">
<span
class="copy"
ng-click="copyData(entry)"
ng-mouseenter="onEnter()">
{{message}}
</span>
</li>
</div>

Related

How can I render compiled Transcluded HTML in Angular?

I am trying to render html that represents a custom directive using angular so that I nest divs an arbitrary number of times. When I run the below code I do see the tag properly transcluded and the browser in my output shows literally the string text " ". I would like to compile this into html and render and tried doing below with
$compile(element.contents())(scope);
However this results in console errors regarding an orphaned transclusion as well as errors saying it cannot read properties of my objects. It's my thought that this is compiling into something I do not expect it to but am not sure why or how I could see what the result is. Any ideas to get me going here? Is there a better more angular way to do this?
Module with directives
angular.module('myApp.APP', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/testapp', {
templateUrl: 'javascripts/nestedExample/testHTML.html',
controller: 'testController'
});
}])
.controller('testController', ['$scope', '$http', '$sce', function($scope, $http, $sce) {
$scope.paneDirective = "<div pane> </div>";
}])
.directive('pane', function($compile) {
return {
scope: {},
template: "<div data-split-pane> <div data-split-pane-component data-width='33%'><p>top</p></div><div data-split-pane-divider data-width='5px'></div> <div data-split-pane-component> <ng-transclude></ng-transclude> <p>bottom</p></div></div>",
transclude: true,
link: function(scope, element, attrs) {
console.log(element.contents())
//$compile(element.contents())(scope);
},
};
});
simplified html
<div ng-controller="testController">
<div pane> {{paneDirective}} </div>
</div>
editing in formatted html code used in template
<div data-split-pane>
<div data-split-pane-component data-width='33%'>
<p>top</p>
</div>
<div data-split-pane-divider data-width='5px'></div>
<div data-split-pane-component>
<ng-transclude></ng-transclude>
<p>bottom</p>
</div>
</div>
Use Angular's ng-transclude directive in your template to specify the insertion point for the transcluded content, like so:
template: '<div ...><div ng-transclude></div> </div>'
I am not sure if this will help, but one way you can approach this is to generate your html string, then just compile the whole thing out. Any other directives you include will get compiled. Check this out and see if its workable for you...
https://plnkr.co/edit/FacA3AwOqJA2QARK28c8?p=preview
angular.module('myApp', [])
.controller('testController', ['$scope', '$http', '$sce', function($scope, $http, $sce) {
$scope.paneDirectiveContent = `
<div data-split-pane>
<div data-split-pane-component data-width='33%'>
<p>top</p>
</div>
<div data-split-pane-divider data-width='5px'>
</div>
<sample></sample>
<div data-split-pane-component>
<p>bottom</p>
</div>
</div>
`;
}])
.directive('pane', function($compile) {
return function(scope, element, attrs) {
element.html(scope.$eval(attrs.pane));
$compile(element.contents())(scope);
}
});
Acts on this Html:
<div ng-controller="testController">
<div pane="paneDirectiveContent"></div>
</div>
And any other directives will get rendered:
.directive('sample', function($compile) {
return {
template: `<b>Other directives will be rendered</b>`
}
})

Angular ng-transclude scope in repeat

I just started to study AngularJS and tries to implement customized table directive with the multiple slots transclude.
And faced situation that scope not transferred to transclude. There is a lot of solutions in other StackOverflow questions, but all of them works only when in directive template ng-repeat appears for top element, but that is not my case.
At least i can't adopt all that solutions.
Simplified version.
Directive:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines" ng-transclude="row">
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
Using of directive:
<my-table>
<row>
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</row>
</my-table>
Full example with the script on Plunkr:
https://plnkr.co/edit/rg43ZdPMGHLBJCTLOoLC
Any advice very appreciated.
The simplest and probably cleanest way to directly reference the $scope object created by the ng-repeat in a transcluded template is through the $parent property:
<my-table>
<td>{{$parent.line.col1}}</td>
<td>{{$parent.line.col2}}</td>
</my-table>
The $parent property of the $scope created for a transcluded template points to the $scope of the target template into which such template is ultimately transcluded (in this case, the ng-repeat), even though such transcluded $scope is not a child of the target $scope in the usual sense as a result of the transclusion. See this wonderful blog post for a more complete discussion of this.
Working plunkr: https://plnkr.co/edit/LoqIMiQVZKlTt5epDnZF?p=preview.
You need to use $transclude function manually and create new child scope for each line. Other than that you need to pass lines to directive if you are using isolated scope (and you are using it).
Your linking function should look something like this:
link: function($scope, $element, $attrs, controller, $transclude) {
var tbody = $element.find('tbody');
$scope.$watch('lines', function (lines) {
tbody.empty();
lines.forEach(function (line) {
var childScope = $scope.$new();
childScope.line = line;
$transclude(childScope, function (content) {
tbody.append('<tr>');
tbody.append(content);
tbody.append('</tr>');
}, null, 'row');
});
});
}
Plunker: https://plnkr.co/edit/MLNZOmoQyMazgIpluMqO?p=preview
But that is bad idea anyway, cos it is hard to create table this way. As you can see child of is not elements. You would have to do a little bit of DOM manipulation to make it work.
i see you don't really need to use attribute
so code look more simple and clean:
<body ng-controller="tableCtrl">
<h1>Table test</h1>
<my-table lines="lines"></my-table>
</body>
your template:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines">
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
and angular directive:
angular.module('myApp', [])
.directive("myTable", function() {
return {
restrict: 'E',
transclude: true,
scope: {
lines:'=lines',
api: '#'
},
templateUrl: "template.html",
};
})
.controller("tableCtrl", ['$scope', function($scope) {
$scope.lines = [
{col1: "testCol1", col2: "testCol2"},
{col1: "testCol11", col2: "testCol21"}
];
}]);
working example in plunkr: https://plnkr.co/edit/iMxRoD0N3sUXqmViHAQh?p=preview

Angularjs - repeater appending elements in different parents in view

I know that if I use the directive ng-repeat, like below, I get every element inside and including the div to repeat on the DOM.
<div class="col col-3" ng-repeat="movie in popular" >
<figure>
<img ng-src="{{movie.backdropURL}}" alt="{{movie.code}}">
<div class="overlay"></div>
<figcaption>{{movie.code}}</figcaption>
<!-- <span class="extra-info">{{movie.extra}}</span> -->
<span class="price">{{movie.price}}</span>
</figure>
</div>
However now I want to have some parent elements that won't repeat but will use the same scope object for their children, that will then repeat.
So, I would like to do a repeater that would append the properties of the scope into their parent, something like this:
<ul class="parent1">
<li><img src={{myScope[0].imgUrl}}></li>
<li><img src={{myScope[1].imgUrl}}></li>
<li><img src={{myScope[2].imgUrl}}></li>
</ul>
<div class="parent2">
<span>{{myScope[0].description}}</span>
<span>{{myScope[1].description}}</span>
<span>{{myScope[2].description}}</span>
</div>
I would like to know if it is possible to reuse a native angular directive (I would prefer not to run the same repeater every time for every parent) where it could append the element to the parent. If not, do you have any suggestion for a solution. I've looked up some links for custom directives I haven't succeeded in applying them. So if you have a 'beginners' custom directive tutorial that could help me go on the right direction, it would be highly appreciated.
I don't know if I understand exactly what you are asking for.
Anyway, the ngRepeat directive is placed in the DOM under a particular parent, so you cannot run it once and append the leaves under different parents. The only way to do that is to create a custom directive that runs a loop internally and sets the leaf under the parent of your choice.
That is:
angular
.module('mymodule')
.directive('mydirective', mydirective);
function mydirective(){
var directive = {
restrict: 'A'
, link: link
}
return directive;
function link($scope, elem, attrs) {
for(var i=0;i<$scope.myScope.length;++i){
var el1 = angular.element('<li><img src='+$scope.myScope[i].imgUrl+'></li>'),
el2 = angular.element('<span>'+$scope.myScope[i].description+'</span>');
elem.find('.parent1').append(el1);
elem.find('.parent2').append(el2);
}
}
}
Please let me know if I misunderstood your goal.
Check this:
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<ul class="parent1">
<li ng-repeat="item in myScope">
<img ng-src={{item.imgUrl}}>
</li>
</ul>
<div class="parent2">
<p ng-repeat="item in myScope"><span>{{item.description}}</span></p>
</div>
</div>
Controller:
angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.myScope = [
{imgUrl:"someUrl1", description: "this is first url"},
{imgUrl:"someUrl2", description: "this is second url"}
]
}]);
Acceptable :) ?

Angular ngchange doesn't fire for <pre contenteditable="true">

I'm trying to get a tag to fire the ng-change event but to no avail. The purpose is to watch changes to post.content. The code is pretty straightforward and parts of it are omitted for brevity, but it's basically a ng-template that is repeated like so:
<li ng-repeat="post in posts" data-id="[[post.id]]" data-rank="[[post.sortrank]]" class="post">
<ng-include src="post.template"></ng-include>
</li>
The repeated template:
<script type="text/ng-template" id="postText.html">
<pre ng-blur="savePost($event, post)" ng-change="changePost()" ng-model="post.content" class="postText" contenteditable="true">[[post.content]]</pre>
</script>
The JS:
$scope.savePost = function($event, post) { //This fires correctly
console.log($event);
console.log(post);
};
$scope.changePost = function() { //This doesn't fire at all
console.log("changed!");
};
Can this be because of the contenteditable attribute? Because I can get the ngChange examples working on the Angular tutorial site.
You can use akatov's angular-contenteditable.js.
From the docs... JS:
angular.module('myapp', ['contenteditable'])
.controller('Ctrl', ['$scope', function($scope) {
$scope.model="<i>interesting</i> stuff"
}])
HTML:
<div ng-controller="Ctrl">
<span contenteditable="true"
ng-model="model"
strip-br="true"
select-non-editable="true">
</span>
</div>
Plunker

How do you execute directive code after rendering?

I'm trying to build a directive that runs after a nested ng-repeat has completed rendering. Here's what I've tried (fiddle):
The HTML:
<div ng-app="MyApp" ng-controller="MyCtrl">
<ul my-directive>
<li ng-repeat="animal in animals">{{animal}}</li>
</ul>
</div>
And the JavaScript:
angular.module("MyApp", [])
.directive("myDirective", function () {
return function(scope, element, attrs) {
alert(element.find("li").length); // 0
};
});
function MyCtrl($scope) {
$scope.animals = ["Dog", "Cat", "Elephant"];
}
The linking function in my custom directive runs before all of the <li> elements have been rendered (and 0 is alerted). How do I run code after the ng-repeat has completed rendering?
You could move the directive inside ng-repeat, http://jsfiddle.net/ZTMex/3/
<div ng-app="MyApp" ng-controller="MyCtrl">
<ul>
<li ng-repeat="animal in animals" my-directive>{{animal}}</li>
</ul>
</div>
Or have a look at https://stackoverflow.com/a/13472605/1986890 which is related to your question.

Categories

Resources