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);
}
);
};
}]);
Related
I am working to add modals to my directives using ui-bootstrap and did so fine on the previous directive. I don't believe I am doing anything differently in this one but I get the ReferenceError: milestoneController is not defined when I run the edit() function from within the directive.
milestone.html (this is the template HTML for the directive below):
<div ng-controller = "milestoneController"></div>
milestone directive:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
milestoneController.editMilestoneDialog(data);
};
}
}
});
angular.module('ireg').controller('milestoneController', function ($scope, $modal){
$scope.editMilestonesDialog = function (objectid) {
//fun
}
});
EDIT: I allso felt I should mention that the milestone directive is repeated in a ng-repeat loop. Thanks!
ok you're going to want to use a transcluded scope in your directive to pass a controller function to the directive. Your directive now becomes:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid',
editMilestoneDialog:'&'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
$scope.editMilestoneDialog(data);
};
}
}
and your markup becomes:
<milestone edit-milestone-dialog="editMilestoneDialog"></milestone>
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.
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.
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 {{}})
I have the following code,
HTML
<div ng-app="test">
<div ng-controller="containerCtrl">
<component data-module="components"></component>
</div>
</div>
JS
var test = angular.module('test', []);
test.controller('containerCtrl', ['$scope', '$rootScope', function ($scope, $rootScope) {
$scope.components = [];
$scope.$on('onSomething', function(e) {
$scope.components = $rootScope.config;
});
}]);
test.directive('component', function () {
var linkFn = function(scope, element, attrs) {
scope.$on('onSomething', function(e) {
console.log(scope, e, scope.module, e.currentScope.module);
/*
* Here 'module' is an array in both 'e' and 'scope' , but when I console it says [].
*/
console.log("onSomething!");
});
};
return {
restrict: 'E',
scope: {
module: '=module'
},
link : linkFn
};
});
test.run(['$rootScope', '$timeout', function ($rootScope, $timeout) {
$timeout(function(){
$rootScope.config = [{id: "c1", width: 100, height: 100 }];
$rootScope.$broadcast("onSomething", "");
},5000);
}]);
Fiddle http://jsfiddle.net/vFncP/4/
Problem
In run method, I have an ajax request which receives a configuration from the database. Once the request is complete, It is broadcast-ed to the scope level. Now the problem is, I am receiving the broadcast in a directive, when I console e or scope, I can see the module which has an array with data whereas when I console scope.module, it says []. I am not getting the error, am I doing something wrong?
Note: Adding a $scope.$watch might help, but I am trying to avoid $watch. Is there any other way to do it.
Thanks in advance
A solution could be $digest to refresh scope values:
scope.$digest();
http://jsfiddle.net/Anthonny/vFncP/7/