So I have some html that gets loaded into the #panel div dynamically depending on which questionNumber the user is on. This is not all of the code but all of the relevant code I think. Anyway, the <input> get's loaded into the page but it doesn't actually do anything. what am I missing here? I have the same problem when the questionNumber === 1, where the binded variables just show up as {{variable}} etc
var readingController = function (scope, Romanize){
scope.usersRomanization;
//alert(scope.usersRomanization);
}
var app = angular.module('Tutorials', ['functions', 'tutorials']).controller('getAnswers', function ($scope, $element, Position, Romanize) {
$scope.sectionNumber = Position.sectionNumber;
if ($scope.sectionNumber === 0){
$('#panel').html('<div ng-controller="readingController"><input ng-model="usersRomanization"></input></div>');
readingController($scope, Romanize);
}
<body ng-controller="getAnswers">
<div id="panel">
</div>
</body>
If you add HTML to the DOM, you have to tell Angular to $compile it. This should be done in a directive. You'll need to inject $compile then do something like this:
var content = '<div ng-controller=...></div>';
var compiled = $compile(content)(scope);
// then put the content where you want
Or better, define a directive and use a template, which will automatically get compiled for you by Angular.
Other alternatives are ng-include (which will compile the loaded content for you) and ng-switch, which would allow you to put the templates into the HTML.
Related
I already know what is the purpose of each item in : compile vs link(pre/post) vs controller
So let's say I have this simple code :
HTML
<body ng-controller="mainController">
{{ message }}
<div otc-dynamic=""></div>
</body>
Controller
app.controller("mainController", function($scope) {
$scope.label = "Please click";
$scope.doSomething = function() {
$scope.message = "Clicked!";
};
});
Directive
app.directive("otcDynamic", function($compile) {
var template = "<button ng-click='doSomething()'>{{label}}</button>";
return {
compile: function(tElement, tAttributes) {
angular.element(tElement).append(template);
for (var i = 0; i < 3; i++) {
angular.element(tElement).append("<br>Repeat " + i + " of {{name}}");
}
return function postLink(scope, element, attrs) {
scope.name = "John";
}
}
}
});
So as we can see , I modify the template (at the compile function - which is where it should be actually)
Result ( plnker):
But
I didn't know that template:... can also take a function.
So I could use the template function instead (plunker) :
app.directive("otcDynamic", function() {
var template1 = "<button ng-click='doSomething()'>{{label}}</button>";
return {
template: function(element, attr) {
element.append(template1);
for (var i = 0; i < 3; i++)
element.append("<br>Repeat " + i + " of {{name}}");
},
link: function(scope, element) {
scope.name = "John";
}
}
});
Question
If so - When should I use the template function vs compile function ?
Let me try to explain what I understood so far.
Directives is a mechanism to work with DOM in Angular. It gives you leverage of playing with DOM element and it's attribute. So it also gives you callbacks to make your work easy.
template , compile and link are those examples. Since your question is specific with compile and template I would like to add about link as well.
A) Template
Like it state, it is a bunch of HTML tags or files to represent it on DOM directly as the face of your directive.
Template can be a file with specific path or inline HTML in code. Like you stated above. template can be wrap in function but the sole use of template is the final set of HTML which will be placed on DOM. Since you have the access to element and its attributes, you can perform as many DOM operation here as well.
B) Compile
Compile is a mechanism in directive which compiles the template HTML or DOM to do certain operation on it and return final set of HTML as template. Like given in Angular DOC
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
Which clearly says that, this is something on top of template. Now like I said above you can achieve similar operations in template as well but when we have methods for its sole purpose, you should use them for the sake of best practice.
You can read more here https://docs.angularjs.org/api/ng/service/$compile
C) Link
Link is used to register listeners like $watch, $apply etc to link your template with Angular scope so that it will get binded with module. When you place any directive inside controller, the flow of scope goes through the link that means the scope is directly accessible in link. Scope is sole of angular app and thus it gives you advantage of working with actual model. Link is also useful in dom manipulations and can be used to work with any DOM element using jQlite
So collecting all above in one
1. Template is the primary source of DOM or HTML to directive. it can be a file or inline HTML.
2. Compile is the wrapper to compile HTML into final template. It is used to gather all the HTML element and attribute to create template for directive.
3. Link is the listener wrapper for various scope and watchers. It binds scope of current controller with html of template and also do manipulation around it.
Hope this helps a bit to understand. Thanks
I have a simple angularjs directive that I use to show a tooltip.
<div tooltip-template="<div><h1>Yeah</h1><span>Awesome</span></div>">Click to show</div>
It works fine but now I'm trying to use it inside a timeline javascript component (visjs.org)
I can add items with html to this timeline like this
item...
item.content = "<div tooltip-template='<div><h1>Yeah</h1><span>Awesome</span></div>'>Click to show</div>";
$scope.timelineData.items.add(item);
The item is well displayed on the page BUT the code of the tooltip-template directive is never reached.
I suspect that because a third party component is rendering the item, the dom element is not read by angular.
I've tried to do a $scope.$apply(), $rootScope.$apply but the result is the same. The directive is never reached.
How can I tell angular to read my dom to parse these directives ?
Here is the directive code :
.directive("tooltipTemplate", function ($compile) {
var contentContainer;
return {
restrict: "A",
link: function (scope, element, attrs) {
var template = attrs.tooltipTemplate;
scope.hidden = true;
var tooltipElement = angular.element("<div ng-hide='hidden'>");
tooltipElement.append(template);
element.parent().append(tooltipElement);
element
.on('click', function () { scope.hidden = !scope.hidden; scope.$digest(); })
$compile(tooltipElement)(scope);
}
};
});
Edit
Added plunker : http://plnkr.co/edit/lNPday452GiZJBhMH4Kl?p=preview
I tried to do the same thing and came with a solution by manually creating scope and compile'ng the html of the directive with the scope using $compile method. Below a snippet
I did the below part inside a directive that created the timeline . Using the scope of that directive ,
var shiftScope = scope.$new(true);
shiftScope.name = 'Shift Name'
var shiftTemplate = $compile('<shift-details shift-name="name"></shift-details>')(shiftScope)[0];
I passed shiftTemplate as the content and it worked fine .
But trying to do this for >50 records created performance issues .
I'm trying to show a tooltip with html content and the html I would like to be fetched from a children div which has angular markup in it.
Before I switched to ui-bootstrap I used the default bootstrap tooltip, which I created in a directive filling the content with ('.my-tooltip-content',element).html()
Now with ui-bootstrap I tried using the same directive/logic on the ui-tooltip except that now I tried setting attribute variables. The problem is, that I don't know how to get the html from my .my-tooltip-content div rendered inside the tooltip, without loosing the bindings. If I use $interpolate, I get the html of my div properly rendered, but the output is fixed then of course (doesn't update anymore), $compile I have never used before, but I thought this would be the right place for such a use, maybe I don't know how to use it, but $compile gives me an exception about circular structure.
This is a shortened version of what my app looks like:
http://plnkr.co/edit/46NsEPArtm4hph0ROlPS?p=preview
Excerpt:
<div class="hover" tooltip-html-unsafe="" tooltip-trigger="mouseenter" tooltip-placement="bottom">
<div>
<p>Hover me {{booking.name}} !</p>
<!-- about 20 more lines -->
</div>
<div class="my-tooltip-content"><!-- hidden by default -->
<p class="booker-name">{{booking.name}}</p>
<p>Does the name in this tooltip update?</p>
<!-- about 10 more complex lines -->
</div>
</div>
anApp.directive('hover', ['$compile','$interpolate', function($compile,$interpolate){
return{
restrict: 'C',
link: function(scope,element,attrs){
var html1,html2,html3;
var content = $(element).find('.my-tooltip-content');
// This works, but no binding
html1 = $interpolate(content.html())(scope);
// This I'd like to work, but I get "Can't interpolate: {{tt_content}} TypeError: Converting circular structure to JSON"
html2 = $compile(content.html())(scope);
// This brings my html into the tooltip but curlybraces are curlybraces (not compiled)
html3 = content.html();
attrs.tooltipHtmlUnsafe = html1;
}
}
}])
Is there an easy way to to get the html of .my-tooltip-content with all it's angular markup/bindings injected into the tooltip-content variable ?
PS: I know that I could squeeze all the html just directly into the tooltip-html-unsafe attribute, but I have so many lines in my real-world my-tooltip-content that this just wouldn't work (would make the tooltip-content html unreadable and unchangeable for humans).
If you need to use a child div, one solution use a function to pass the interpolated contents of the div to the tooltip. For example:
anApp.directive('hover', ['$compile','$interpolate', function($compile,$interpolate){
return{
restrict: 'C',
link: function(scope,element,attrs){
scope.myTooltip = function() {
var content = $(element).find('.tooltip-content');
return $interpolate(content.html())(scope);
};
}
}
}])
And your html would would need to be updated to use the new scope value for the tooltip:
<div class="hover" tooltip-html-unsafe="{{myTooltip()}}" tooltip-trigger="mouseenter" tooltip-placement="bottom">
Here's the updated plnkr
So I decided after much debate, to split my webpage into two parts, one for ie7 and another for ie8+. I have many
<!-- if lte ie7--->
scattered around, and I just want to separate the html as much as possible. Sure it's heavier maintenance, but whatev.
So as I moved my html content from the main.html to submain.html, I noticed one of my custom directives wasn't working.
Note: All my javascript gets loaded AFTER I load the html content.
I have a directive called ng-map, where if this attribute appears on any div with an ID, it'd call mapquest to fill in the div with an interactive map. Currently this works.
However, if I move the directive:
<div ng-map id="map"></div>
out of main.html and place it into submain.html and add this in my main.html:
<div ng-include="'view/submain.html'">
it doesn't work, even though my submain.html loads properly.
It gives me the error: "Error: Object # has no method 'initMap'"
This is what my directive looks like:
mapapp.app.directive('ngMap', ['logger', '$http', function (logger, $http) {
var directiveDefinitionObject = {
restrict: 'A',
controller: function link($scope, $element) {
$scope.shouldMapLoadOnPageLoad = true;
$scope.loadCodeAndMap =
function () {
var key = {...};
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "http://www.mapquestapi.com/sdk/js/v7.0.s/mqa.toolkit.js?key=" + key;
document.body.appendChild(script);
};
$scope.loadCodeAndMap();
$scope.initMap = function () {
...
}
}
};
return (directiveDefinitionObject);
}]);
Any help in understanding what is happening here is appreciated.
Okay figured it out, but I don't have complete understanding.
When I load up my html content, controller, and directives, I was able to call the $scope.initMap function located in my directive from a $watch on my controller.
So this worked:
controller:
$scope.$watch('blahData', function(newVal, oldVal) {
$scope.initMap();
}
directive:
$scope.initMap = function() { .. }
however, when I change my html to load via ajax (which I assume that's what ng-include does to fetch the html from a location, you need to change the above logic.
So the previous html goes from this:
<html><head></head>
<body>
<!-- Stuff -->
<div>Hello!</div>
<!-- More Stuff -->
</body></html>
to
<html><head></head>
<body>
<!-- Stuff -->
<!-- Home.html has <div>Hello!</div> -->
<div ng-include="'Template/Home.html'" />
<!-- More Stuff -->
</body></html>
Then the controller becomes something like this:
$scope.initMap = false; //not a function anymore
$scope.$watch('blahData', function(newVal, oldVal){
$scope.initMap = !$scope.initMap;
}
and the directive becomes:
$scope.$watch('$scope.initMap', function() { ... }
So the directive now watches the data happening from the controller. Now, if your directive doesn't include scope: '=' for the directive options, then you have to access the controller scope as a parent:
$scope.$watch('$scope.$parent.initMap, function() { ... }
that's because scope: '=' binds the local $scope on the directive to the parent scope (the controller).
I'm using inline editing with CKEditor, and I'd like to bind an element to an angular scope value.
<div contentEditable="true">
<p>Here is the value: {{testval}}</p>
</div>
testval should update in the same manner as it would outside the editor.
To protect this text in the editor, I'd like to do something similar to the placeholder plugin. In other words I plan to have a placeholder, dynamically displaying the final text rather than just the placeholder.
I've seen several examples of how to bind the entire contents with angular, but not individual elements. I'm still fairly new to both angular and ckeditor, so any help or pointers would be much appreciated.
It sounds to me like you will need to use a directive for what you want. I might be soewhat off because I'm not completely familiar, but goig by what you've provided, let's assume this example.
html
<body ng-app="myApp">
<div content-editable content="content"></div>
</body>
javascript
angular.module('myApp', [])
.directive('contentEditable', function() {
restrict: 'A',
replace: true,
scope: {
// Assume this will be html content being bound by the controller
// In the controller you would have:
// $scope.content = '<div>Hello World</div>'
content: "="
},
template: '<div contentEditable="true"><p>Here is the value {{ content }}</p></div>'
});
Still not sure if I completely comprehend, but let me know if I'm getting closer.
I assume that you want to bind the HTML text in model to the element. I used ng-bind-html to render what is in the model and I created the directive ck-inline to add the inline feature and bind the model to the changes that happen in the inline editor. This directive requires a ng-bind-html to work and you also need to have ngSanitize added to your module. Add directive ck-inline to your element and
I also use $timeout because I noticed that if I don't the text is rendered and then ckeditor somehow deletes all the values which messes up the model (this does not happen with the non-inline option). Here is the code.
yourModule.directive('ckInline', ['$sce', '$timeout', function($sce, $timeout){
return{
require : '?ngBindHtml',
scope:{value:"=ngBindHtml"},
link : function(scope, elm, attr, ngBindHtml)
{
$timeout(function()
{
var ck_inline;
elm.attr("contenteditable", "true");
CKEDITOR.disableAutoInline = true;
ck_inline = CKEDITOR.inline(elm[0]);
if (!attr.ngBindHtml)
return;
ck_inline.on('instanceReady', function()
{
ck_inline.setData(elm.html());
});
function updateHtml()
{
scope.$apply(function()
{
scope.value = $sce.trustAsHtml(ck_inline.getData());
});
}
ck_inline.on('blur', updateHtml);
ck_inline.on('dataReady', updateHtml);
});
}
};
}]);