Dynamically load in Angular template - javascript

I'm pretty new to Angular, so I think template is the wrong word. Here's what I mean:
We have a Rails backend serving up data to an Angular frontend. I need to use an ng-repeat to show off this data. The problem is, the desired template comes as part of the data that Rails serves us. Here's an example:
JSON served by Rails
[
{
data: {
title: "example title",
body: "example body"
},
template: "<h1>{{title}}</h1><p>{{body}}</p>"
},
{
data: {
body: "example body two"
},
template: "<div>{{body}}</div>"
}
]
We don't know how many records there will be, what the templates will look like, or what bindings they call for (body, title, caption, etc). I cannot store the templates locally, so all the data has to come from the Rails backend.
Here is pseudocode of what I'd like to accomplish:
<span ng-repeat="block in blocks">
<template src="block.template" data="block.data"></template>
</span>
This would use the specified template and bind the data object to it.
Again, I'm new to angular so if I need to clarify let me know. Thank you!
EDIT: Here's my attempt at a directive. It doesn't work since it appears that template doesn't have access to the desired template represented by block.template. This wouldn't cover binding the data, so I'd have to work that in once I fixed the directive.
app.directive("template", function() {
return {
template: block.template,
scope: {
block: '=data'
},
restrict: 'E'
};
});
EDIT 2:
Here's another attempt that doesn't work. This one shows the template on screen, but in the console it errors that scope.$watch is not a function
app.directive('template', function ($compile) {
return {
restrict: 'A',
replace: true,
scope: {
tmpl: '=template'
},
link: function postLink(scope, element, attrs) {
scope.$watch('tmpl', function (block) {
element.html(block.template.blank_template);
$compile(element.contents())(block.payload);
});
}
};
});

You can do it with a directive:
basically something along the lines of
//Assuming App is your Angular App Instance and Angular at least 1.4ish
app.directive('template',['$compile', function($c) {
return {
link: function (scope, el, attrs, controller) {
el.html(scope.template);
$c(el.contents())(scope);
},
scope: {
template: "=tpl",
data: "=data"
}
}
});
Then you can use it like so:
<span ng-repeat="block in blocks">
<template tpl="block.template" data="block.data"></template>
</span>

Related

Reapply AngularJS after changing HTML

Inside a larger legacy app I have a view that uses AngularJS. In this view, I use a directive to display a Bootstrap modal (its content is created on the backend, then appended to the DOM).
No AngularJS methods seem to be working inside the modal. I understand it's because the modal content is appended to the DOM after AngularJS has compiled.
I'm looking for a way to allow the use AngularJS braces and methods inside the modal template. I've tried looking into $compile, $apply, and $digest, but so far I haven't been able to get it to work.
Relevant part of the code:
myApp.directive('formModal', ['$http', function($http) {
return {
restrict: 'E',
scope: true,
replace: true,
template: '...',
link: function postLink(scope){
// ...
$http({url: url}).then(function (response) {
if (response.data.status) {
modal.show({
title: 'Modal title',
content: response.data.form_html,
onShow: function ($modal) {
// ...
}
});
};
})
};
}
};
}]);
And part of the template where the modal is opened:
<form-modal class="btn-success" id="btn-form">
</form-modal>

AngularJS directive not recognised when starting with data-*

I'm new to AngularJS. Need some help with the directive I created.
This is My HTML:
<data-table template-url="dataTable.html" info="someData"></data-table>
I get "someData" from server in my controller - directive.js:
app.directive('dataTable', function() {
return{
restrict: 'E',
scope: {
data : '=info'
},
link: function($scope,elem,attrs){
///some code here.
},
templateUrl : function(elem, attrs) {
return attrs.templateUrl;
}
});
The issue is when I debug my code, it come to the directive by doesn't go inside. (I used javascript debug in chrome). Is there anything I'm missing. The restrict Tag is proper, name is correct what else is needed? I did look at the similar questions but couldn't find any solution. Here is a fiddle : Demo
You can't use directive names starting with data-* because its reserved by AngularJS ng core namespaces. Just use an other name to start with and you will be fine.
<my-data-table template-url="dataTable.html" info="someData"></my-data-table>
And your directive:
myApp.directive('myDataTable', function() {
return {
scope: {
data: '=info'
},
link: function($scope, elem, attrs) {
///some code here.
console.log(attrs.templateUrl);
},
templateUrl: function(elem, attrs) {
return attrs.templateUrl;
}
}
});

Angular | Directive not passing correct value back

Hi i have created a directive and it does not pass back the correct message.
The directive is used to pass tool-tips back to the html page
this is what the html looks like
<info-text info-msg="Adding another applicant might help you to get approved."></info-text>
below is the directive
(function(){
angular.module('mainApp').directive('infoText', [function () {
return {
scope: { infoMessage: '&infoMsg' },
restrict: 'E',
replace: true,
template: '<p class="info-text"><i class="fa fa-info-circle"></i> {{infoText}}</p>',
link: function(scope, elem, attrs) {
$(elem).prev().hover(function(){
$(elem).addClass('info-hover');
}, function(){
$(elem).removeClass('info-hover');
});
}
};
}]);
}());
the message i get rendered on the page is as follows (it does send the glyphicon):
{{infoText}}
Any ideas,
thanks. Kieran.
You should not use & for this sort of binding, basically it is used for expression binding. I think one way binding (#) is efficient for what you are doing.
Also you should change directive template {{infoText}} to {{infoMessage}}
Markup
<info-text
info-msg="{{'Adding another applicant might help you to get approved.'}}"></info-text>
Directive
angular.module('mainApp').directive('infoText', [function () {
return {
scope: { infoMessage: '#infoMsg' },
restrict: 'E',
replace: true,
template: '<p class="info-text"><i class="fa fa-info-circle"></i> {{infoMessage}}</p>',
link: function(scope, elem, attrs) {
$(elem).prev().hover(function(){
$(elem).addClass('info-hover');
}, function(){
$(elem).removeClass('info-hover');
});
}
};
}]);
And making more cleaner and readable html you could place that string into some scope variable and pass that scope variable in info-msg attribute

Angular Three Way Button with Directive

I'm looking for an answer to this question:
Multi State button like toggle in angularJS
...but using a directive. The main reason being that I'm trying to achieve isolate scope in order to create a reusable button. I have tried:
angular.module('myApp', [])
.directive('buttonToggle', function() {
return {
restrict: 'A',
scope: {
myBtnArr: "="
},
myBtnTxt: ["AND", "OR", "NOT"],
template: '<button>{{ myBtnTxt[myBtnArr] }} </button>'
}
});
With something like this in the HTML:
<div button-toggle my-btn-arr=0></div>
But Angular doesn't seem to like any flavor of this, either showing the button but not the text or throwing the cryptic a.match is not a function error. Thoughts?
You need to modify your directive to include a link function. Then place myBtnTxt on scope in there. Like so:
app.directive('buttonToggle', function() {
return {
restrict: 'A',
scope: {
myBtnArr: "="
},
template: '<button>{{myBtnTxt[myBtnArr]}}</button>',
link: function(scope){
scope.myBtnTxt = ["AND", "OR", "NOT"];
}
};
});

Angularjs not interpolating title for Bootstrap Popover

I am trying to use a Bootstrap Popover in an AngularJS ngRepeat directive. The problem is the title attribute isn't being interpolated. How can I resolve this issue?
View
<div ng-app="app" id="ng-app">
<ul ng-controller="Main">
<li ng-repeat="i in items" data-toggle="popover" title="{{ i.title }}" <!-- Title isn't interpolated so Popover doesn't render a title -->
data-content="Events: {{ day.events.length }}"
init-popover>{{ i.data }}</li>
</ul>
Code
var app = angular.module('app', []);
app.controller('Main', ['$scope', function($scope){
$scope.items = [
{ title: 'Title 1', data: 'lorem' },
{ title: 'Title 2', data: 'ipsum' },
];
}]);
app.directive('initPopover', function() {
return function(scope, element, attrs) {
if (scope.$last){
$('[data-toggle="popover"]').popover({container: 'ul'});
}
};
});
FIDDLE
You can make it work but it's pretty ugly, here is my solution
var app = angular.module('app', []);
app.controller('Main', ['$scope', function($scope){
$scope.items = [
{ title: 'Title 1', data: 'Test in ngRepeat' },
{ title: 'Title 2', data: 'Test in ngRepeat' },
];
}]);
app.directive('initPopover', function() {
return function(scope, element, attrs) {
if (scope.$last) {
attrs.$observe('title', function () {
$('[data-toggle="popover"]').popover({container: 'ul'});
});
}
};
});
You shouldn't use title="{{i.title}}", but ng-attr-title="{{i.title}}"...
I had the same problem. I'm using bootstrap v3.3.6 and Angular v1.5.0
Adding attribute data-original-title="{{someTitle}}" fixed the problem.
<button type="button" class="btn btn-lg btn-danger" data-toggle="popover" title="{{someTitle}}" data-original-title="{{someTitle}}" data-content="Some Content">Click to toggle popover</button>
Also, as bootstrap requires, call .popover() in directive
module.directive('myDir', ['jQuery', function (jQuery) {
return {
link: function () {
jQuery('[data-toggle="popover"]').popover();
}
};
}]);
Add $timeout to directive will solve your problem.
$timeout will gets call when last element of ng-repeat rendered on UI & run $digest cycle.
Directive
app.directive('initPopover', function($timeout) {
return function(scope, element, attrs) {
if (scope.$last) {
$timeout(function() {
angular.element('[data-toggle="popover"]').popover({
container: 'ul'
});
});
}
};
});
Working Fiddle
Hope this could help you. Thanks.
Consider using ui-bootstrap for native Angular directives instead of Bootstrap's jQuery version. They're a lot easier to work with in an Angular project.
Edit: If you can't do that, here's a solution:
<li ng-repeat="i in items" data-toggle="popover" data-ng-attr-popover_title="{{ i.title }}"
data-content="Popover Content"
init-popover>{{ i.data }}</li>
With a small change to the directive:
app.directive('initPopover', function() {
return function(scope, element, attrs) {
if (scope.$last){
$('[data-toggle="popover"]').popover({ container: 'ul', title: attrs.popoverTitle });
}
};
ng-attr-popup_title will create an HTML attribute called popupTitle on the element with the Angular syntax inside {{}} parsed correctly. Then, in the directive, we can just examine that attribute to set the popover title.
Fiddle: here
Just to add to the knowledge base here. Here's the angular way, in case anyone needs. I based my solution on Mark Coleman's PopOver tutorial, and of course what has been contributed by Nate Barbettini.
My situation was I have a nested ng-repeat over items in items, so I need to have a title for my popup that is based on an array stored in a scope, while the content of the popup is from another array stored in the array of the scope.
I added ng-attr-title to my HTML element, like so:
<div pop-over items='week.items' ng-attr-title="{{'Week number:'+ week.week}}"> Total sales this week : {{week.sales}}</div>
Then, in my directive, I followed mostly what Mark Coleman did.
angular.module('vegadirectives', [])
.directive('popOver', function ($compile, $templateCache) {
var itemsTemplate = "<ul class='popper'><li ng-repeat='thing in items track by $index'>";
itemsTemplate+=" <i class='fa fa-square' ng-class=";
itemsTemplate+="{purple:thing.color==='purple','orange':thing.color==='orange','white':thing.color==='white','pink':thing.color==='pink','red':thing.color==='red','green':thing.color==='green','yellow':thing.color==='yellow'}";
itemsTemplate+="></i> {{thing.model}} - {{thing.qty}}</li></ul>";
var getTemplate = function () {
var template=itemsTemplate;
return template;
}
return {
restrict: "A",
transclude: true,
template: "<span ng-transclude></span>",
link: function (scope, element, attrs) {
var popOverContent;
if (scope.items) {
var html = getTemplate();
popOverContent = $compile(html)(scope);
var options = {
trigger:'click',
content: popOverContent,
placement: "right",
html: true,
title:scope.title
};
if (scope.$last) {
}
$(element).popover(options);
}
},
scope: {
items: '=',
title:'#'
}
};
});
Explanation:
To fill up my popover with a list of items, with some fancy color square to indicate different types of items, I created a chunk of HTML code. This chunk named itemsTemplate will be used by $compile to create the popover content with scope. I think the usage is $compile(htmlstring)(scope).
Next comes the standard stuff for options settings for the popover.
For the interpolation of scope with title, I specified the option title: with scope.title. Then, in the scope: option way down at the end, the two scope elements are specified with '=' and '#'.
So, the title:scope.title refers or binds to scope: {items:'=', title:'#' }, and the title:'#' is the attributes of the title in the HTML, which is ng-attrs-title={{week.week}}, where week is from the outer ng-repeat loop.
Hope it helps.
After finding this answer at https://stackoverflow.com/a/17864868/2379762, I tried data-original-title={{i.title}}, and it works.
http://jsfiddle.net/f1tjj8x5/
<li ng-repeat="i in items" data-toggle="popover" data-original-title="{{ i.title }}"
data-content="Popover Content"
init-popover>{{ i.data }}</li>

Categories