AngularJS : update bindings ng-bind-html - angularjs

I've go a problem with binding. I use the "ng-bind-html" directive, because the details text of the object to display contains HTML code. Unfortunatly the text doesn't update when the object changes. 'title' and 'kurztext' change its values but 'langtext' (the html bound) not. Here's the code I use:
<div class="content scrollContainer" ng-model="selectedItem">
<h2 class="header">{{selectedItem.titel}}</h2>
<div class="kurztext" ng-show="selectedItem.kurztext">{{selectedItem.kurztext}}</div>
<div class="langtext" ng-bind-html="selectedItem.langtext"></div>
</div>
In Javascript I just pick an object out of an array and assign it to $scope.selectedItem to change the displayed item. Here's my JS code (should not be relevant to the problem):
var items;
var app = angular.module("app", ["ngSanitize"]);
app.controller("MainCtrl", function ($scope, $sce){
$scope.items = items;
$scope.selectedItem = $scope.items[0];
$scope.showItem = function(item){ // called on click on list item
$scope.selectedItem = item;
}
$scope.openItemLink = function(id){
for (var i=0; i<$scope.items.length; i++){
if ($scope.items[i].uid==id){
$scope.showItem($scope.items[i]);
break;
}
}
}
$scope.getLink = function(id){
var it = 0;
for (var i=0; i<$scope.items.length; i++){
if ($scope.items[i].uid==id){
it = $scope.items[i];
return it.titel;
}
}
return "----";
}
});
Any ideas?

you need to sanitize it, add angular-sanitize.js and in your module
var App = angular.module('process', ['ngSanitize']);
in your controller
App.controller(
'Init',
[
'$scope', '$sce',
function($scope, $sce)
and your code
$scope.details = $sce.parseAsHtml($scope.details);
i know that if i don't do that it doesnt work too,
according to the documentation
By default, the innerHTML-ed content will be sanitized using the $sanitize service.
To utilize this functionality, ensure that $sanitize is available,
for example, by including ngSanitize in your module's dependencies (not in core Angular.)
You may also bypass sanitization for values you know are safe.
To do so, bind to an explicitly trusted value via $sce.trustAsHtml.
See the example under Strict Contextual Escaping (SCE).

Related

Render a custom directive when loaded dynamically from controller

I have three custom directive, say <sample-grid></sample-grid> , <sample-form></sample-form> and <sample-view></sample-view>. These are defined in the following way:
angular.module("MyApp").directive('sampleGrid', [ function() {
return {
scope : {},
restrict: 'EA',
templateUrl : 'view/templates/sample-grid.tmpl.html',
controller : 'SampleGridCtrl',
scope: {
scnid: '=',
}
};
} ]);
I have a main view where I am using <md-tabs> from angular material in following way -
<md-tabs>
<md-tab ng-repeat="tab in myCtrl.pageTabs track by $index">
<md-tab-label>{{tab.tabName}}</md-tab-label>
<md-tab-body>
<div id="tab.tabName" ng-bind-html="tab.tabHTML"></div>
</md-tab-body>
</md-tab>
</md-tabs>
also the controller for this view defined as
angular.module("MyApp").controller("myCtrl",['$log','$scope','$sce', '$compile', function myCtrl ($log,$scope,$sce,$compile) {
var homeScope = this;
homeScope.pageTabs = [];
var newTab = {};
newTab.tabName = "Sample";
newTab.tabHTML = $sce.trustAsHtml("<sample-grid></sample-grid>");
newTab.tabAction = "grid";
homeScope.pageTabs.push(newTab);
var template = angular.element(document.querySelector('#Sample'));
$compile(template)(homeScope);
}])
The requirement is: user will be able to add new tabs in view, and I have to show one of the three custom directives in the view based on the selection. So I have to just push new tab objects into homeScope.pageTabs with the required property.
The problem is : After pushing the tab object, new tab is created but the custom directive does not render into view. I can see it in the console like :
<div id="tab.tabName" ng-bind-html="tab.tabHTML">
<sample-grid></sample-grid>
</div>
I have seen answers here such that I have to use $compile to render it correctly. I tried that, But I could not get the proper solution.
So my question is how I can achieve this functionality? And, is there any other easy and possible way to achieve this?
I have changed your controller logic a little bit. You have to use compile a little bit different.
angular.module("MyApp").controller("myCtrl",['$log','$scope','$sce', '$compile', function myCtrl ($log,$scope,$sce,$compile) {
var homeScope = this;
homeScope.pageTabs = [];
var template = $sce.trustAsHtml("<sample-grid></sample-grid>");
var linkFn = $compile(template);
var content = linkFn(scope);
var newTab = {};
newTab.tabName = "Sample";
newTab.tabHTML = content;
newTab.tabAction = "grid";
homeScope.pageTabs.push(newTab);
}])

Scope values to a requested content

I have a view that contains a button, when the button is clicked, a $http.get request is executed and the content is appended on the view.
View:
<button ng-click="includeContent()">Include</button>
<div id="container"></div>
Controller:
$scope.includeContent = function() {
$http.get('url').success(function(data) {
document.getElementById('container').innerHTML = data;
}
}
The content to include:
<h1>Hey, I would like to be {{ object }}</h1>
How can I scope a value to object? Do I need to approach this in a complete different way?
The built-in directive ng-bind-html is the way you are looking for.
Beware, that ng-bind-html requires a sanitized string, which is either done automatically when the correct libary is found or it can be done manually ($sce.trustAsHtml).
Don't forget to inject $sce in your controller.
$scope.includeContent = function() {
$http.get('url').success(function(data) {
$scope.data = $sce.trustAsHtml(data);
}
}
<button ng-click="includeContent()">Include</button>
<div ng-bind-html="data"></div>
As you also want to interpolate your requested HTML, I suggest using $interpolate or, if it can contain whole directives or should have a full fledged two-way-data-binding, use $compile instead.
In your case alter the assignment to
$scope.data = $sce.trustAsHtml($interpolate(data)($scope));
Don't forget to inject $interpolate/$compile aswell.
As I don't know about your $scope structure I assume that "object" is available in this scope. If this isn't the case then change the $scope parameter to whatever object contains your interpolation data.
You should use a controller to do this (I imagine you are since you're using $scope).
ctrl function () {
var ctrl = this;
ctrl.includeContent = function () {
$http.get("url").success(function (data) {
ctrl.object = data;
});
};
}
<div ng-controller="ctrl as ctrl">
<button ng-click="ctrl.includeContent()">Include</button>
<div id="container">
<h1 ng-show="ctrl.object">Hey, I would like to be {{ctrl.object}}</h1>
</div>
</div>
You need not select an element and append the data to it. Angular does it for you. That's what is magic about angular.
In your controller's scope, just update object and angular does the heavy-lifting
$scope.includeContent = function() {
$http.get('url').success(function(data) {
$scope.object = data;
}
}
If that's html code from a server, then you should use the 'ng-bind-html' attribute:
<button ng-click="includeContent()">Include</button>
<div id="container" ng-bind-html="htmlModel.ajaxData"></div>
Controller:
$scope.htmlModel = {ajaxData:''};
$scope.includeContent = function() {
$http.get('url').success(function(data) {
$scope.htmlModel.ajaxDataL = data;
}
}
One way is to use ng-bind-html as suggested.
Another way is with $compile:
app.controller('MainCtrl', function($scope, $http, $compile) {
$scope.error='error!!!';
$scope.includeContent = function() {
$http.get('url').success(function(data) {
var elm = angular.element(document.getElementById('container')).html(data);
$compile(elm)($scope);
}).error(function(){
var elm = angular.element(document.getElementById('container')).html('{{error}}');
$compile(elm)($scope);
})
}
});
Also, typically in angular, when you want to manipulate the DOM you use directives.
DEMO

$sce.trustAsHtml not working

Alright, so the following below that I've tested in code works:
javascript:
var string = '<p>hello</p>';
$scope.html = $sce.trustAsHtml(string);
html:
<span ng-bind-html="html"></span>
The thing I'm really trying to do is this: Pulling a bunch of items from the server in JSON form. One of the JSON's key is called "description", which is just a long string of html code.
I'm pushing these items into a $scope array so I can show them on the page through an ng-repeat directive. The code below summarizes how it's being done:
javascript:
$.getJSON('someURL', function(data) {
$scope.items = [];
for (var i = 0; i < data.count; i++) {
var item = {};
item.description = $sce.trustAsHtml(data.item[i].description);
$scope.items.push(item);
}
});
html:
<p ng-repeat="item in items">
<span ng-bind-html="item.description"></div>
</p>
This isn't producing any output for some reason. Something I read was that whatever variable you bind the $sce.trustAsHtml() to must be a $scope variable. In this case, I'm setting it to a regular variable "item.description" and then adding it to a $scope.item array.
I suspect this may be why it's not working, but I don't know how to go about fixing it.
Can anyone help with this?
Thanks!
Try <span ng-bind-html="'{{item.description}}'"></span>.
Something like this worked for me.
With AngularJS 1.4.9 it worked for me like this:
Controller:
.controller('MyCtrl', ['$scope', '$sce',
function($scope, $sce) {
$scope.infoText = $sce.trustAsHtml('My <strong>HTML text</strong>');
}
])
Template:
<span ng-bind-html="infoText"></span>

How to get attribute value on click?

Hey guys I need to get tha data value of the button when I click on it. I tried in this way but it doesn't work..
var app = angular.module('myApp', []);
app.controller("myAppCtrl", function($scope, $attrs) {
$scope.events = {}
$scope.events.btnLoadMore = function() {
var panel = $attrs.emptywidget;
alert(panel);
}
});
HTML
<html ng-app="myApp">
<head>
<title></title>
</head>
<body ng-controller="myAppCtrl">
<button data-emptywidget="#panel2" ng-click="events.btnLoadMore()">Click</button>
</body>
</html>
The only way you can access that data attribute would be through standard DOM access, which is not wise inside a controller. The $attrs variable you're passing into your controller also won't give you very much, as your controller doesn't directly related to anything (or at least it doesn't need to or shouldn't)
If you need to do something like that, then you could change your ng-click to something like
ng-click="events.btnLoadMore('#panel2')"
Then change the definition of your btnLoadMore function to take an argument. Alternatively you can write a directive that would be given that value, but that's more complex. But it depends what you want to do with it really. The above should work though
You could use a combination of angular.element and passing in the $event source as well: http://jsfiddle.net/ahchurch/9USEv/1/
<div ng-controller="myAppCtrl">
<button data-emptywidget="#panel2" ng-click="events.btnLoadMore($event)">Click</button>
</div>
var app = angular.module('myApp', []);
app.controller("myAppCtrl", function($scope, $attrs) {
$scope.events = {}
$scope.events.btnLoadMore = function($event) {
console.log($event);
var panel = angular.element($event.target).attr("data-emptywidget");
console.log(panel);
}
});
You can do a few different things.
1) You can pass in the data via the function so like
ng-click="events.btnLoadMore('#panel2')"
Then change your function to match so:
$scope.events.btnLoadMore = function(panel) {
alert(panel);
}
2) You can pass in the data via the $event parameter
ng-click="events.btnLoadMore($event)"
Then change your function to match so:
$scope.events.btnLoadMore = function(clickEvent) {
alert(clickEvent.target.attributes['data-emptywidget'].value);
}
3) You can just look at the arguments passed in to the function
$scope.events.btnLoadMore = function() {
alert(arguments[0].target.attributes['data-emptywidget'].value);
}

Angularjs different controllers for one include

I want to have element where i can have 2 views using their own controller but only one at a time.
I can't use a ng-view and use the routeProvider because in the future I need to include more ng-includes that need to change their content depending on the possible actions.
I created a fiddle http://jsfiddle.net/EvHyT/29/.
So I used a ng-include and then I set the src for it from a main controller. At that point I want to use controller 1 or controller 2.
function MainCtrl($rootScope, $scope, navService){
$scope.template = {};
$scope.loadCtrl1=function(param){
navService.loadCtrl1(param);
}
$scope.loadCtrl2=function(param){
navService.loadCtrl2(param);
}
$rootScope.$on('loadCtrl1', function(e, args){
$scope.template = {'url': 'temp1'};
});
$rootScope.$on('loadCtrl2', function(e, args){
$scope.template = {'url': 'temp2'};
});
}
I use a service for communication because i want to move the load controller functions in a child controller.
var myApp = angular.module('myApp',[]);
myApp.factory('navService', function($rootScope) {
return {
loadCtrl1:function(param){
$rootScope.$broadcast('loadCtrl1', {'id':param});
},
loadCtrl2:function(param){
$rootScope.$broadcast('loadCtrl2', {'id':param});
}
};
});
I know this solution is bad because the controllers are not yet created when a different template is inserted so my event listener will not fire. Also can I destroy the previous instances of the controller because switching between the two controllers makes my event fire multiple times.
function Child1Ctrl($scope, $rootScope){
$rootScope.$on('loadCtrl1', function(e, args){
alert(args.id);
});
}
function Child2Ctrl($scope, $rootScope){
$rootScope.$on('loadCtrl2', function(e, args){
alert(args.id);
});
}
You don't need to broadcast (and shouldn't be broadcasting) to make this happen.
In my experience, if you're broadcasting on the rootScope, you're probably working too hard.
A simpler way of loading the template is very similar to what you're doing:
my.NavService = function() {
this.template = 'index.html';
this.param = null;
};
my.NavService.prototype.setTemplate(t, p) {
this.template = t;
this.param = p;
};
my.ctrl = function($scope, nav) {
$scope.nav = nav;
$scope.load = function(t, p) {
nav.setTemplate(t, p);
};
};
my.ctrl1 = function($scope, nav) {
$scope.param = nav.param;
};
my.ctrl2 = function($scope, nav) {
$scope.param = nav.param;
};
module.
service('nav', my.NavService).
controller('mainCtrl', my.ctrl).
controller('ctrl1', my.ctrl1).
controller('ctrl2', my.ctrl2);
<script type="text/ng-template" id="temp1.html">
<div ng-controller="ctrl1">Foo {{param}}.</div>
</script>
<script type="text/ng-template" id="temp2.html">
<div ng-controller="ctrl2">Bar {{param}}.</div>
</script>
<div ng-controller="mainCtrl">
<a ng-click="load('temp1.html', 16)">Load 1</a>
<a ng-click="load('temp2.html', 32)">Load 2</a>
<div ng-include src="nav.template"></div>
</div>
A setup like this would work much better for you.
Alternatively, you should look into selectively showing elements with ng-switch. Unlike ng-show/hide, ng-switch does not simply add "display:none" to the CSS. It removes it from the DOM.
Some notes:
The above example may not be a working example, it's a demonstration of the idea.
A working example can be seen here: http://jsbin.com/ofakes/1 It modifies your original code.
JSFiddle had some issues with loading the include from the on page script
tag.
JSBin was a little better.
I didn't really get it to work as expected until I broke out the templates
into their own files.

Resources