Angular: using transclude with ng-bind-html - javascript

I have the following angular setup:
var app = angular.module('app', []);
app.filter('unsafe', function($sce) {
return $sce.trustAsHtml;
});
app.controller('SearchController', ['$scope', '$http', function($scope, $http) {
$scope.searchString = '';
$scope.searchType = 'Artist';
$scope.updateHtml = function() {
$http.get('/search', {
params: {
searchString: $scope.searchString,
searchType: $scope.searchType
}
}).success(function(data) {
$scope.html = data;
}).error(function(err){
$scope.html = err;
});
};
$scope.updateHtml();
}]);
app.directive('searchDirective', function() {
return {
restrict: 'A',
transclude: true,
template: '<div ng-bind-html="html | unsafe" ng-transclude></div>'
};
});
It pulls raw html markup through ajax in the controller and stores it in #scope.html. In the directive, this html is inserted into the DOM through ng-bind-html.
The html (jade) looks as follows:
#search(ng-controller="SearchController" search-directive)
It basically works. But inside this html that is included, i have some transclusive content, like {{searchType}} that i want to be resolved. Unfortunatly, that is not the case, it shows "{{searchType}}" in the browser. What can i change to make the transclusion work?
I read about $compile and $transclude, but i don't know how to use it or if it can help me solve my issue. thx!

with the help of Patrick, i was able to solve it. i changed my controller to
app.controller('SearchController', ['$scope', '$http', '$interpolate',
function($scope, $http, $interpolate) {
$scope.searchString = '';
$scope.searchType = 'Artist';
$scope.updateHtml = function() {
$http.get('/search', {
params: {
searchString: $scope.searchString,
searchType: $scope.searchType
}
}).success(function(data) {
$scope.html = $interpolate(data)($scope); // <<-- difference here
}).error(function(err){
$scope.html = err;
});
};
$scope.updateHtml();
}]);
and now my html is interpolated based on the passed-in scope. thank you!
edit:
$interpolate is only for rendering the DOM and parsing it through the scope. it simply returns plain html. if you need to actually retrieve a full working html template, with angular code in it, use $compile. i found this answer extremely helpful in sorting out the differences between $interpolate, $compile and $parse.

Related

Angular: Variable template inside directive

In my Angular template I use an attributive directive as follows:
HTML:
<div id="my-template-one" my-template-directive></div>
JS:
// ...
.directive('myTemplateDirective', ['myconfig', function (myconfig) {
return {
templateUrl: myconfig.TEMPLATE_PATH + 'my-template-one.html',
controller: function ($scope, $rootScope) {
// code
},
controllerAs: 'dir'
}
}]);
For including another template, my-template-two.html, on another page, I would like to use the same directive. I do not want to duplicate the directive. How can I pass the template as an variable?
HTML on another page:
<div id="my-template-two" my-template-directive></div>
My goal is that somehow I can tell my directive to render my-template-two.html when this HTML is called.
The templateUrl property value may be a function which takes two arguments tElement and tAttrs and returns a string value:
app.directive('myTemplateDirective', ['myconfig', function (myconfig) {
return {
templateUrl: function (tElem, tAttrs) {
var template = "my-template-one.html";
if (tAttrs.use) {
template = tAttrs.use;
};
return myconfig.TEMPLATE_PATH + template;
},
controller: function ($scope, $rootScope) {
// code
},
controllerAs: 'dir'
}
}]);
Usage
<div my-template-directive use="my-template-two.html"> </div>
For more information, see AngularJS Comprehensive Directive API -- template

Not having it pretty clear about JSON and its uses on Angular

I've got what I consider a very basic question, but I've almost never worked with JSON nor web devolpment (but yes with desktop's with Java), so I'm having really big troubles in my practices with Angular.
You see, I've got this JSON:
{
"imgs": [
{
"title": "Primera foto",
"src": "images/1.gif"
},
{
"title": "Segunda foto",
"src": "images/2.png"
},
{
"title": "Tercera foto",
"src": "images/3.gif"
},
{
"title": "Cuarta foto",
"src": "images/4.jpg"
},
{
"title": "Quinta foto",
"src": "images/5.png"
},
{
"title": "Sexta foto",
"src": "images/6.gif"
}
]
}
I retrieve it using $http and $q via a service, which works greatly. After that, I save the info in a scope variable in a controller:
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
$scope.images = getJSON.getImages();
}
$scope.loadImages();
alert($scope.images);
}])
... and I try to show it in the HTML:
<!DOCTYPE html>
<html ng-app="miApp">
<head>
<meta charset="UTF-8">
<!-- Importamos las dependencias principales !-->
<script src="angular.js"></script>
<!-- <script src="angular-routeangular-route.js"></script> !-->
<!-- Importamos nuestros propios archivos (controladores, directivas, etc...) !-->
<script src="app.js"></script>
<script src="controllers.js"></script>
<script src="directives.js"></script>
</head>
<body ng-controller="imagesCtrl">
<littleImage src="{{image.src}}" ng-repeat="image in images"></littleImage>
</body>
</html>
What am I doing wrong? Nothing appear after the ng-repeat, so I guess I'm not accessing correctly to the JSON data. In case you're doubtious, this is the directive I'm trying to use:
directives.directive('littleImage', function(){
return{
restrict: E,
scope: {
src : "#"
},
template: "<div><img src="+src+" class='img-responsive' alt='Image'></div>"
}
});
In case there are more doubts about my code, this is my service (pretty straightforward):
ctrls.factory('getJSON', ['$http', '$q', function($http, $q){
return{
getImages : getImages
}
function getImages(){
var defered = $q.defer();
var promise = defered.promise;
$http.get("data.json")
.success(function(data){
defered.resolve(data);
})
.error(function(err){
defered.reject(err);
});
return promise;
};
}]);
Anybody knows what am I doing bad?
**EDIT: **
Thanks for the help so far! You helped me a lot, but there's one last thing: now the directive, somehow, isn't computing correctly, and it doesn't wrap it's template into the actual HTML.
This is the HTML:
<div ng-repeat="image in images">
{{image.title}}
<littleImage source="image.src" ></littleImage>
</div>
And this is the directive:
directives.directive('littleImage', function(){
return{
restrict: E,
scope: {
source : "="
},
template: "<div><img ng-src="+source+" class='img-responsive' alt='Image'></div>"
}
});
Any idea on why it doesn't work?
The problem might be in your <img src="..."> Angular has this directive ng-src, because the $scope variable might not be loaded yet when your image tag gets rendered.
I think your problem lies in this line:
<littleImage src="{{image.src}}" ng-repeat="image in images"></littleImage>
The handlebars aren't required and the repeater needs changed, you can simply use:
<littleImage src="image.src" ng-repeat="image in images.imgs"></littleImage>
Your directive is wrong, you should use the normal binding and use an angular expression in the template: src variable doesn't exists, it's src attributes in scope that exists:
directives.directive('littleImage', function(){
return{
restrict: E,
scope: {
src : "="
},
template: '<div><img ng-src="src" class="img-responsive" alt="Image"></div>'
}
});
oh and also in the template, it is binded by angular expression not by text remove the curly braces:
<div ng-repeat="image in images">
{{image.src}}
<littleImage src="image.src" ></littleImage>
</div>
I slightly change your code to check if the data is well loaded, it should help the debug.
you are using Promise.Do this change in controller
getJSON.getImages().then(function(data) {
$scope.images = data.imgs;
}, function() {
//error hanlding part
})
you can't get images array synchronously, you must get it asynchronously.
you must get image list through promise like this:
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
getJSON.getImages().then(function (data) {
$scope.images = data;
})
}
//$scope.loadImages();
//alert($scope.images);
}])
without using of $q and promise way you can using of simple javascript callback to get data from service:
services.factory('getJSON',['$q', '$http', function ($q, $http) {
return {
getImages: function (cb) {
$http({
url: "YOUR_IMAGES_LIST_ADDRESS",
method: 'GET'
}).success(function (data) {
cb(data);
}).error(function (error) {
cb(null, error)
})
}
}
}]);
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
getJSON.getImages(function (data, error) {
if(error) {
//show error
}
$scope.images = data;
})
}
//$scope.loadImages();
//alert($scope.images);
}])
there is another alternative simplest way :), just return $http in your service:
services.factory('getJSON',['$q', '$http', function ($q, $http) {
return {
getImages: function () {
return $http({
url: "YOUR_IMAGES_LIST_ADDRESS",
method: 'GET'
});
}
}
}])
service return promise object of $http.
ctrls.controller('imagesCtrl', ['$scope', 'getJSON', function($scope, getJSON){
$scope.images = [];
$scope.loadImages = function(){
getJSON.getImages().then(function (response) {
$scope.images = response.data;
})
}
//$scope.loadImages();
//alert($scope.images);
}])
Ok! I voted all your posts because every one of them helped me. Thank you for that!
These were my troubles:
I wasn't getting the JSON properly all time, since I use promises. Also, I wasn't accessing to the "imgs" array. Changed my controller function to:
$scope.loadImages = function() {
getJSON.getImages().then(function(data) {
$scope.images = data.imgs;
}, function() {
alert("Ha habido un error cargando los datos");
});
}
The ng-repeat works better in a different div. Also, as you were saying, the src value must be the variable name, instead of its value (as it was being represented by using the curly braces):
{{image.title}}
As last but not least, my directive was totally wrong. Had to access to the src variable in the scope, using "=", change the src from the <img> tag to ng-src, and the last thing that nobody said: instead of writing "src" as its value, it works writing its actual value, via {{src}}:
directives.directive('littleImage', function(){
return{
restrict: "E",
scope: {
src : "="
},
template: ""
}
});
Now it's properly working! Thank you for your help!

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);
}
);
};
}]);

AngularJS' $sce.trustAsHtml being ignored

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 {{}})

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