AngularJS' $sce.trustAsHtml being ignored - javascript

I'm new to AngularJS and I feel like I'm just scratching the surface of what's possible with the framework. However, I'm running into problems with the sce.trustAsHtml function. I'm running AngularJS 1.2.4.
In my application, I'm loading items using JSON. These items are displayed in a list using a directive. Sometimes, I would want to inject HTML into the retrieved content (e.g. to make links clickable).
I've read I can use $sce.trustAsHtml to allow html in the binds. However, the following snippet isn't working. I would expect all items to be replaced with a bold text 'test', but instead it's displaying <strong>Test</strong> for each item.
Is there a simple way to make this snippet work?
angular.directive('ngStream', function($timeout, $sce) {
var url = "getitems.json";
return {
restrict: 'A',
scope: {},
templateUrl: 'templates/app_item.html',
controller: ['$scope', '$http', function($scope, $http) {
$scope.getItems = function() {
$http.get(url,{}).success(function(data, status, headers, config) {
$scope.items = data;
});
}
}],
link: function(scope, iElement, iAttrs, ctrl) {
scope.getItems();
scope.$watch('items', function(newVal) { if (newVal) {
angular.forEach(newVal, function(vars,i) {
# Example html string for testing purposes.
var editedContent = '<strong>Test</strong>';
newVal[i].contentHtml = $sce.trustAsHtml(editedContent)
});
}});
},
}
});

What's on your template? $sce.trustAsHtml must be used with ng-bind-html instead of normal ng-bind (or {{}})

Related

Trouble using $attrs in directives controller

My directive has an attribute id-model that is used to pass an array to the directive. The directives controller should execute a function using that array as an argument. I can access the attribute in my controller and log the array but my function never runs and I'm not sure why.
If I don't use the attribute and change placesFact.getDetails(idModel) to placesFact.getDetails($scope.model) everything works fine but I want there to be more flexibility with the directive.
*Update:
To clarify on the placesFact.getDetails() method, It looks up google place info with a place id and returns the results as a promise. It has been tested and works fine.
Directive
.directive('gPlaces', function() {
return {
restrict: 'E',
scope: '#',
link: function(scope, element, attrs) {
scope.tempUrl = attrs.tempUrl;
},
controller: function($scope, $attrs, placesFact) {
var idModel = $attrs.idModel;
$attrs.$observe('idModel', function(value) {
idModel = value;
console.log(idModel); // This logs the array
placesFact.getDetails(idModel).then(function(results) {
console.log(results); // This logs nothing
$scope.places = results;
});
});
},
template: '<div ng-include="tempUrl"></div>'
}
})
Controller
angular.module('myApp').controller('PlacesCtrl', function($scope) {
$scope.model = [
'ChIJR4dOl_hYwokRApSCaQiBidk',
'ChIJv-Ghof5YwokRMtWLEV12hJI',
'ChIJjyX2GqRZwokRT-gdcGoPuSI',
'ChIJqSurReFYwokRec7JFACToas',
'ChIJn8dCo-NYwokRC_4nRUQWbNE',
'ChIJszmN0-JYwokRk-XCDbO6X_Y',
'ChIJt4TrE_1YwokRVedrKxaqYoo',
'ChIJiW0WvwJZwokRIWyzCvo3o5k',
'ChIJsS1xLQJZwokRGfXJPMwXA1A',
'ChIJI5xCX6NZwokR3jdSQwsw2DI',
'ChIJuVE5aLtZwokR-K75OxUEtzI',
'ChIJ7R4tgLtZwokRM8thlhlzE2o',
'ChIJxbWTG_pYwokRPgtFVKi-Cuc',
'ChIJKZVnwFVYwokRgDw_sxw3NCo',
'ChIJw_JUgvhYwokR91EMxVDhB8M',
'ChIJZ3oXOVZYwokRNnAXaDRKAzg',
'ChIJP9idxlZYwokRLH-I1mNfzYQ',
'ChIJndGJ5FNYwokRricJvhT0t1s',
'ChIJhZ0Sn1ZYwokRRA1MZJZHrHA',
'ChIJhTdv51NYwokR7V105uVzf8g',
'ChIJvfVwDFJYwokRtWobbwOMEVM',
'ChIJpY9Tg01YwokRCr_aQpDrqgk',
'ChIJ-fRuLFdYwokR0KKQ6Av_WhQ',
'ChIJh-DIsE1YwokRhuFrdM1ge5E'
];
});
View
<g-places temp-url="pages/places/multiTemp.html" id-model={{model}}></g-places>
As estus mentioned in the comments, my scope: '#' syntax was wrong and was the root of the problem. This is my first directive so a little trial and error and a lot of reading lead me to figure out what to do though.
Here is how the directive looks now that it is working.
.directive('gPlaces', function() {
return {
restrict: 'E',
scope: {
idModel: '=',
tempUrl: '#'
},
controller: function($scope, placesFact) {
placesFact.getDetails($scope.idModel).then(function(results) {
$scope.places = results;
});
},
template: '<div ng-include="tempUrl"></div>'
}
})

Highlight.js in AngularJS SPA doesn't work

I have an AngularJS SPA which loads articles into the view. Some articles have code examples and I want to use highlight.js to highlight it.
In my example below I have simulated a get request, 'cause that's how I load my dynamic content in the actual app. The $scope.test is very similar to what my actual app could get returned; some regular HTML to print out which includes code examples.
My problem: it doesn't really seem to work.
Specifically, nothing gets highlighted. It seems to me like I am missing an init or something... Halp?
I've also tried <div hljs/> with the same (lack of) result. There are no console errors.
This answer provides a solution that uses ng-model in the template. However, I don't use ng-model anywhere.
EDIT: I've made some changes to my example code to further explain the problem.
Here's my app (simplified):
var app = angular.module('app', ['ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.title = 'Some Title';
$scope.metaStuff = 'Written by Awesome MacFluffykins';
$scope.articleBody = '<p>Here\'s an example of a simple SQL SELECT:</p><pre><code class="sql" highlight>SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]);
Here's my HTML:
<div ng-app="app" ng-controller="ctrl">
<h2>{{ title }}</h2>
<p><small>{{ metaStuff }}</small></p>
<div ng-bind-html="articleBody"></div>
</div>
And finally a jsFiddle.
In my opinion it's best to use a directive for DOM manipulations like this. Pass your sourcecode through ng-model (you could also use another attribute) and run HLJS in the directive. Since you're using a asynchronous method to supply the value to your scope, you'll need to use $watch to catch the value and then run HLJS:
HTML:
<div highlight ng-model="test"></div>
Directive:
.directive('highlight', [
function () {
return {
replace: false,
scope: {
'ngModel': '='
},
link: function (scope, element, attributes) {
scope.$watch('ngModel', function (newVal, oldVal) {
if (newVal !== oldVal) {
element.html(scope.ngModel);
var items = element[0].querySelectorAll('code,pre');
angular.forEach(items, function (item) {
hljs.highlightBlock(item);
});
}
});
}
};
}
]);
Working JSFiddle: https://jsfiddle.net/1qy0j6qk/
Fiddle
https://jsfiddle.net/vg75ux6v/
var app = angular.module('app', ['hljs', 'ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.test = '<h2>Here\'s some code:</h2><pre><code hljs class="sql">SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]).directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}]);

Change asynchronously coming data in angular direcitve

I have directive:
angular.module('mymodule').directive('someName', ['$http', function($http) {
return {
restrict: 'AE',
scope: {
headerId: '=header',
data: '=data'
},
templateUrl: 'path/to/template.html',
compile: function() {
return function($scope, $link, $attr) {
console.log($scope.headerId); // print actual header id
console.log($scope.data); // undefined
/* ... code ... */
The point is header is hardcoded as number in directive attribute. And everything fine with it. But data is got from server with angular service asynchronously. So at the moment when compile function runs there is no data in $scope. My problem is that I want to change data so that it will be readable by directive template.
How can I do it?
Add watcher to data
scope.$watch('data', function() {...})

Any way to dynamically load angular directives?

Here's a short fiddle:
http://jsfiddle.net/aSg9D/
Basically, neither <div data-foo-{{letterA}}></div> nor <div data-ng:model="foo-{{letterB}}"></div> are interpolated.
I'm looking for a way to dynamically load one of several inline templates.
Pardon me if this has already been asked before, but I searched and couldn't find it.
I believe Radim Köhler has the correct answer. Just before it was posted, I hacked together something to load directives from another directive like this:
angular.module('myApp', []).directive('loadTmpl', function($compile) {
return {
restrict: 'A',
replace: true,
link: function($scope, $element, $attr) {
$element.html("<div data-card-"+$attr.loadTmpl+"></div>");
$compile($element.contents())($scope);
}
};
});
And:
<div data-load-tmpl="{{directiveName}}"></div>
I think that's the minimalist approach, but there's probably something wrong with it, so just look at the answer below.
Let's adjust it this way (the udpated fiddle). The view:
<div my-selector name="letterA"></div>
<div my-selector name="letterB"></div>
the controller:
function myCtrl($scope) {
$scope.letterA = 'bar';
$scope.letterB = 'baz';
}
And here is new directive mySelector, containing the selector
.directive('mySelector',
[ '$templateCache','$compile',
function($templateCache , $compile) {
return {
scope: {
name: '='
},
replace: true,
template: '',
link: function (scope, elm, attrs) {
scope.buildView = function (name) {
var tmpl = $templateCache.get("dir-foo-" + name);
var view = $compile(tmpl)(scope);
elm.append(view);
}
},
controller: ['$scope', function (scope) {
scope.$watch('name', function (name) {
scope.buildView(name);
});
}],
};
}])
.run(['$templateCache', function ($templateCache) {
$templateCache.put("dir-foo-bar", '<div data-foo-bar></div>');
$templateCache.put("dir-foo-baz", '<div data-foo-baz></div>');
}])
In case you like it, all credits goes to Render a directive inside another directive (within repeater template) and AngularJS - Directive template dynamic, if you don't, blame me.

How to pass a collection to a directive in angular.js?

Please forgive my lack of understanding.
I pass the name of a collection to my directive:
<ul tag-it tag-src="preview_data.preview.extract.keywords"><li>Tag 1</li><li>Tag 2</li></ul>
The directive is defined:
app.directive('tagIt', function (){
return {
restrict: 'A',
link: function(scope,elem, attr) {
elem.tagit();
console.log(attr.tagSrc); //the name of my collection, but how do I access it?
}
}
});
How do I access my collection from the directive and make sure my directive is called when the collection is populated? Here is how preview_data.preview.extract.keywords gets populated.
app.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
console.log('config');
$routeProvider.when("/", {
templateUrl: "/templates/addItem.html",
controller: "AddItemController",
resolve: {
loadData: addItemCtrl.loadData
}
});
});
var addItemCtrl=app.controller("AddItemController", function ($scope, $route, $sce, Preview) {
var title = decodeURIComponent($route.current.params.title);
var ua = decodeURIComponent($route.current.params.ua);
var uri = decodeURIComponent($route.current.params.uri);
$scope.preview_data = {
uri: uri,
title: title,
ua: ua
}
//pass parameters to web preview API
Preview.get(uri, ua, title).then(function (data) {
$scope.preview_data.preview = data;
if (data.embed.html) {
$scope.preview_data.preview.embed.html = $sce.trustAsHtml(data.embed.html);
}
}, function (data) {
alert('Error: no data returned')
});
});
You need to set the variable in the directive scope and set the template to iterate between the tags:
template : '<li data-ng-repeat="tag in tagSrc">{{tag.name}}</li>',
scope : {
tagSrc : '='
},
And will became this:
app.directive('tagIt', function (){
return {
restrict: 'A',
template : '<li data-ng-repeat="tag in tagSrc">{{tag.name}}</li>',
scope : {
tagSrc : '='
},
link: function(scope,elem, attr) {
console.log(attr.tagSrc);
}
}
});
the '=' attribute will tells to angular to use a tw way binding with the array passed in the directive declaration in the HTML.
Here is a plunker with a working example.
And here is a good arcticle explaning the directive's attributes and life cycle.
I hope it helps.
[EDIT]
If you want just iterate the array, without creating some different behavior in the list items, you can just simply use the ng-repeat directive:
<ul>
<li data-ng-repeat="tag in tags">{{tag.name}}</li>
<ul>

Categories