AngularJS append HTML in the ng-repeat element - javascript

Using AngularJS I need to append HTML to the elements of ng-repeat, using the selector: 'objectMapParent-' + post._id
<div ng-repeat="post in posts">
<div id="{{ 'objectMapParent-' + post._id }}">
<h5 style="color:blue; margin-top: 3px;">{{post.title}}</h5>
</div>
</div>
so I created a loop to loop through the elements of the posts array, where for each element I call the function: createElement
$scope.posts = [{
_id: "1",
title: "map of london"
}, {
_id: "2",
title: "map of brazil"
}, {
_id: "3",
title: "map of usa"
}];
$scope.posts.forEach(function(post) {
// console.log(post);
createElement("#objectMapParent-" + post._id, function() {
//create map here after append
});
});
This callback function gets a selector, elementParent, which we use to find the node in the DOM, and then apend the desired HTML
var createElement = function(elementParent, cb) {
var aux = elementParent;
var postId = aux.replace("#objectMapParent-", "");
var myElement = angular.element(document.querySelector(elementParent));
var html = "<div id='objectMap-" + postId + "'><div id='BasemapToggle-" + postId + "'></div></div>";
myElement.append(html);
cb();
};
But something does not work, as far as I'm concerned, the document.querySelector function can not find the selector.
It seems to me that when it tries to select, the node does not yet exist in the DOM.
I reproduced the code in the codepen, it might help.

How to Encapsulate jQuery code in a Custom Directive
Any jQuery code that manipulates the DOM should be encapsulated in a custom directive so that it is executed when the AngularJS framework ng-repeat directive instantiates the element.
<div ng-repeat="post in posts">
<div id="{{ 'objectMapParent-' + post._id }}">
<h5 style="color:blue; margin-top: 3px;">{{post.title}}</h5>
<my-element post="post"></my-element>
</div>
</div>
app.directive("myElement", function() {
return {
link: postLink,
};
function postLink(scope,elem,attrs) {
var post = scope.$eval(attrs.post);
var html = `
<div id="objectMap-${post._id}">
<div id="BasemapToggle-${post._id}">
</div>
</div>
`;
elem.append(html);
}
})
To use jQuery, simply ensure it is loaded before the angular.js file.
For more information, see
AngularJS Developer Guide - Creating Custom Directives
AngularJS angular.element API Reference

Related

Why can`t I access scope via controller?

I am working on passion project. And I cant access scope to pass data to back-end frame work.
Here is my index file
<div id="main-menu" ng-controller="appCtrl">
//some other code
<div id="includedDocumentsFilter" style="float:right; display:none; padding-right: 10px;">
<my-documents validate-options="validateDialogOptions()" call-dialog="showDialog()"> </my-documents>
</div>
//some other code
</div>
My custom directive
'use strict';
dbApp
.directive('myDocuments', [
function () {
var documentTemplate =
' <div class="caption-row">' +
'<kendo-button style="width:62px" ng-click="changeDocument(true)"> Ok </kendo-button>'+
'<kendo-button style="width:62px" ng-click="changeDocument(false)" > Revert changes </kendo-button>'+
'</div>'
}
return {
scope: true,
template: documentTemplate
}
}]
)
My controller
$scope.changeDocument = function (applyFilter) {
if (applyFilter === true) {
//Here is where I cant access $scope
}
}
Firstly, I see a extra closing curly braces in your directive. Secondly in your html code there is display:none in div with id "includedDocumentsFilter". Just wondering if you are hiding the div, how will you be able to see the template defined in your directive. I have added a working jsfiddle link below using your above mentioned code
dbApp.directive('myDocuments', [
function () {
var documentTemplate =
' <div class="caption-row">' +
'<kendo-button style="width:62px" ng-click="changeDocument(true)"> Ok </kendo-button>'+
'<kendo-button style="width:62px" ng-click="changeDocument(false)" > Revert changes </kendo-button>'+
'</div>'
return {
scope: true,
template: documentTemplate
}
}]
)
JsFiddle link: https://jsfiddle.net/anilsarkar/gk2dfh1p/21/
Note: I have replaced kendo-button with span in jsfiddle

AngularUI Bootstrap Carousel not working properly when binding HTML content with $compile

AngularUI Bootstrap version ^2.4.22
AngularJS version 1.6.4
Angular Sanitize version ^1.6.1
I'm having trouble using AngularUI Bootstrap's Carousel plugin. In my scenario, i need to read an external file containing some template paths, and load each of them as a slide. See example below:
index.html (where directive is being called)
<body id="body" ng-app="homePage">
<div id="miolo">
<div example-directive class="ng-hide"></div>
<div banner-rotativo></div>
<div id="menu-footer"></div>
</div>
</body>
banner-rotativo.directive.js - Basically, in this directive, I have a template following the structure of AngularUI Bootstrap's demo, and i'm binding the response data to $scope.slides array. When i push into htmlContent property the $compile(objResponseInner)($scope) result, the carousel behavior works okay, but it renders [[object HTMLDivElement]] and things like that as an item.
angular.module('homePage')
.directive('bannerRotativo', ['$compile', '$http', 'moduleUrl', '$templateRequest', function ($compile, $http, moduleUrl, $templateRequest) {
return {
template: '<div style="height: 305px" ng-controller="bannerHomeController" class="" >\
<div uib-carousel active="active" interval="myInterval" no-wrap="noWrapSlides">\
<div uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">\
<div ng-bind-html="slide.htmlContent">\
</div>\
</div>\
</div>\
</div>',
link: function (scope, element, attributes, controller) {
//Carousel
scope.myInterval = 5000;
scope.noWrapSlides = false;
scope.active = 0;
scope.slides = [];
var intCurrentIndex = 0;
$http({
method: 'GET',
url: moduleUrl.getUrl('homepage', '../config/banner-rotativo.conf')
}).then(function success(objResponse, status, headers, config) {
var objData = objResponse.data;
if (objData.slides) {
angular.forEach(objData.slides, function (objItem, strObjectName) {
var strTemplatePath = moduleUrl.getUrl('homepage', '..' + objItem.caminho);
if (strTemplatePath) {
$templateRequest(strTemplatePath).then(function success(objResponseInner) {
var objContent = $compile(objResponseInner)(scope);
scope.slides.push({
htmlContent: objContent,
id: intCurrentIndex++
});
});
}
});
}
});
}
}
}]);
banner-rotativo.conf
{
"slides": {
"banner-ex-one": {
"titulo": "exone",
"caminho-imagem": "assets/one.jpg",
"caminho": "/html/components/banner-rotativo/banner-ex-one.view.html"
},
"banner-ex-two" : {
"titulo": "extwo",
"caminho-imagem": "assets/two.jpg",
"caminho": "/html/components/banner-rotativo/banner-ex-two.view.html"
},
"banner-rav" : {
"title": "rav",
"caminho-imagem": "assets/rav.jpg",
"caminho": "/html/components/banner-rotativo/banner-rav.view.html"
},
"banner-aviso" : {
"title": "Quadro de comunicações 1",
"caminho-imagem": "assets/aviso.jpg",
"caminho": "/html/components/banner-rotativo/banner-aviso.view.html"
},
"banner-custom" : {
"title": "Quadro de comunicações 2",
"caminho-imagem": "assets/custom.jpg",
"caminho": "/html/components/banner-rotativo/banner-custom.view.html"
}
}
}
Loaded template example:
<div id="frameOne" ng-controller="slideOneController" class="varejo-clique-promocao-one" title="Conteúdo Varejo - Quadro One">
<div class="item">
<div id="dados">
<!-- Imagem banner one -->
<img id="one" ng-click="enviarFormOne()" class="one" alt="" ng-if="1==1" ng-src="caminhoImagem"
/>
<!-- End imagem banner -->
<span ng-if="hasText">{{bannerText}}</span>
</div>
<div id="excecao" class="excecao" ng-if="typeof(one.excecao) !== 'undefined'">
DEU EXCECAO
</div>
<div class="carousel-title" id="tituloOne" ng-if="1==1" title="{{bannerTitle}}">
{{bannerTitle}}
</div>
</div>
Some important points:
I'm using $compile because my injected templates have controllers too. If i don't use it, my controllers aren't processed. When i simply insert the HTML without the $compile, it renders okay.
What's wrong?
The problem is that ng-bind-html expects html string and you are giving it the result from $compile that is actually an element object. You could take the HTML string from the compiled element and pass that instead, but you would probably run into all kind of trouble with that.
But you can actually just skip the hassle with $compile by using ngInclude instead of ng-bind-html. It will handle both the template request and the compilation for you.
So instead of requesting and compiling strTemplatePath, store that to the slide list:
var strTemplatePath = moduleUrl.getUrl('homepage', '..' + objItem.caminho);
if (strTemplatePath) {
scope.slides.push({
htmlUrl: strTemplatePath,
id: intCurrentIndex++
});
}
And then use that in the template:
<div uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
<div ng-include="slide.htmlUrl"></div>
</div>
Here's a somewhat working fiddle: jsfiddle.net, although I had to fill in a few blanks here and there.

Two way bindings trigger 10 digest() iteration reached

I have some problems with angular binding and I'm not very experienced with it. I will post all questions here as they are related.
Here is some angularjs code snipped that triggers 10 digest() cycle reached. I found some similar posts and the cause is that a change is performed recursively in digest(), but I cannot find the cause in my example.
Here is code:
<work-timekeepings-day timekeepings="dailyTimekeepingCtrl.timekeepingList | timekeepingDay : dailyTimekeepingCtrl.selectedDay" day="dailyTimekeepingCtrl.selectedDay"></work-timekeepings-day>
Component:
var workTimekeepingsDay = TimekeepingsApp.component('workTimekeepingsDay', {
templateUrl : 'angular/components/work-timekeepings-day.html',
controllerAs: '$workTkDayCtrl',
bindings : {
timekeepings : '=',
day: '='
}
});
HTML template:
<div class="row lightgreen-row padding-5 border-rounded" ng-repeat-start="workTk in $workTkDayCtrl.timekeepings">
<div class="col-md-4"> <b> {{ workTk.user.firstName + ' ' + workTk.user.lastName }} </b> </div> </div> ...
Filter function:
var timekeepingDayFilter = TimekeepingsApp.filter('timekeepingDay', function() {
return function(timekeepings, filterDay) {
var result=[];
angular.forEach(timekeepings, function(timekeeping) {
var timekeepingDay = moment(timekeeping.workDay);
if (timekeepingDay.isSame(filterDay, 'day')) {
result.push(timekeeping);
}
});
return result;
}
});
If I apply filter function inside HTML template it doesn't trigger the error, but the two-way binding with 'day' variable seems to not work properly. If I update 'dailyTimekeepingCtrl.selectedDay' in another component, bound in the same way, the change is not reflected in workTimekeepingsDay component.
Here is the filter applied in component template:
<work-timekeepings-day timekeepings="dailyTimekeepingCtrl.timekeepingList" day="dailyTimekeepingCtrl.selectedDay"></work-timekeepings-day>
<div class="row lightgreen-row padding-5 border-rounded" ng-repeat-start="workTk in $workTkDayCtrl.timekeepings | timekeepingDay : day">
<div class="col-md-4"> <b> {{ workTk.user.firstName + ' ' + workTk.user.lastName }} </b> </div> </div> ..
Q1: Why is the 'digest() aborted' error occur if I am not updating the base array? How can I pass directly the filtered array to component in timekeepings variable?
Q2: Why is day variable not updated in component if dailyTimekeepingCtrl.selectedDay is updated?
I solved this by using lodash memoize function to use result from cash. Although I'd have preferred to not use an external library, but to change the algorithm, I am still happy with this.

How do I output a specific nested JSON object in Angular?

I'm a rank newbie to Angular, and I'm attempting to port an old jQuery-based app to NG. The JSON I'm starting with (parsed from an XML file) looks like this:
{
"setUps":{
"cartImage":
{
"_cartIm":"addtocart.gif",
"_cartIm_o":"addtocart.gif"
}
,"_supportImsPath":"/"
},
"product":
{
"matrix":
{
"thumbnails":[
{
"_myLabel":"Antique Brass Distressed",
"_thumbImage":"3",
"_cartCode":"160"
},
{
"_myLabel":"Antique Brass Light",
"_thumbImage":"156",
"_cartCode":"156"
},
{
"_myLabel":"Old Iron",
"_thumbImage":"ap",
"_cartCode":"157"
},
{
"_myLabel":"Oil-Rubbed Bronze",
"_thumbImage":"ob",
"_cartCode":"3"
}
],
"_myLabel":"Finishes"
},
"_Title":"Flower Cabinet Knob",
"_itNum":"100407x"
}
}
What I need to do with this is output specific elements on my template - first and foremost, the matrix object with its associated thumbnails. In this example, there is only one matrix, but for many of the products there are multiples, each with their own thumbnail arrays.
This is the controller:
var XMLt = angular.module("XMLtest",[]);
XMLt.factory("Xfactory",function($http){
var factory = [];
factory.getXML = function(){
return $http.get(productXML);
}
return factory;
});
XMLt.controller("Xcontroller",function($scope,Xfactory) {
$scope.Xcontroller = [];
loadXML();
function loadXML() {
Xfactory.getXML().success(function (data) {
var x2js = new X2JS();
prodData = x2js.xml_str2json(data);
$scope.thisItem = prodData.contents;
$scope.matrices = [];
angular.forEach($scope.thisItem.matrix,function(value,key)
{
$scope.matrices.push(value,key);
});
});
}
});
And this is my view template:
<div ng-controller="Xcontroller">
<h2>Title: {{ thisItem.product._Title }}</h2>
<div ng-repeat="thisMatrix in thisItem.product" class="matrix">
{{ thisMatrix._myLabel }}
</div>
</div>
My problem is that this ng-repeat, not surprisingly, returns a div for every child element of the product that it finds, not just the matrix. So I wind up with a couple of empty divs (for _Title and _itNum) in addition to the matrix div.
I've seen quite a few examples of filtering by comparing value literals, but they don't seem to apply in this case. I also tried writing a custom filter:
$scope.isObjType = function(input) {
return angular.isObject(input);
};
<div ng-repeat="thisMatrix in thisItem.product | filter:isObjType(matrix)">
That seemed to have no effect, still returning the extraneous divs. I can't seem to wrap my head around how I'd limit the repeat to a specific object type. Am I thinking of this the completely wrong way? If so, I'd welcome any input.
Since you only have one matrix, you don't need the repeat for the matrix label. You only need it for the thumbnails.
<div ng-controller="Xcontroller">
<h2>Title: {{ thisItem.product._Title }}</h2>
<div class="matrix">
{{ thisItem.product.matrix._myLabel }}
<div ng-repeat="thisThumbnail in thisItem.product.matrix.thumbnails" class="thumbnail">
{{thisThumbnail._myLabel}}
</div>
</div>
</div>
If it were possible to have multiple matrixes, the object would need to be modified to be able to represent that (by wrapping the matrix object in an array.)
Update per comments:
If you have the possiblity of multiple matrixes, you will need to modify the object to ensure that it is consistent when there is 1 vs when there are 2+.
<div ng-controller="Xcontroller">
<h2>Title: {{ thisItem.product._Title }}</h2>
<div ng-repeat="thisMatrix in thisItem.product.matrix" class="matrix">
{{ thisMatrix._myLabel }}
<div ng-repeat="thisThumbnail in thisMatrix.thumbnails" class="thumbnail">
{{thisThumbnail._myLabel}}
</div>
</div>
</div>
and in controller:
XMLt.controller("Xcontroller",function($scope,Xfactory) {
$scope.Xcontroller = [];
loadXML();
function loadXML() {
Xfactory.getXML().success(function (data) {
var x2js = new X2JS();
prodData = x2js.xml_str2json(data);
// must always have an array of matrixes
if (!prodData.contents.product.matrix.slice) {
prodData.contents.product.matrix = [prodData.contents.product.matrix];
}
$scope.thisItem = prodData.contents;
$scope.matrices = [];
angular.forEach($scope.thisItem.matrix,function(value,key)
{
$scope.matrices.push(value,key);
});
});
}
});

angularjs data binding with dynamically created elements

I have this code in my application:
$scope.appendBets = function()
{
$.each($scope.phaseBets, function(i, bet)
{
var betElement = angular.element('<div ng-model="phaseBets[i]">Bet id: {{phaseBets[i].id}}</div>');
angular.element(document.querySelector('#betsHolder')).append(betElement);
$compile(betElement)($scope);
});
}
the $scope.phaseBets is loaded from $http.get.
Now the problem is that the {{phaseBets[i].id}} content not seen on the html page, I am getting this Bet id:.
I have seen this OS but it's not working for me, maybe because of the array.
Is there anything wrong with my code?
Note
The thing is that by the I'd I will create an element (different for each id) so ng-repeat not helping that mutch.
Here's how I'd do it using ng-repeat and ng-include:
$scope.items = [
{id: 1, title: 'foo', data: {body: 'baz1'}},
{id: 2, title: 'bar', data: {body: 'baz2'}}
];
<div ng-repeat="item in items">
<h2>{{item.title}}</h2>
<div ng-include src="getTemplateById(item.id)"></div>
</div>
Where the templates are defined inline like this:
<script type="text/ng-template" id="template-1.html">
Content of template-1.html
<div>{{item.data.body}}</div>
</script>
<script type="text/ng-template" id="template-2.html">
<p>Content of template-2.html</p>
</script>
and getTemplateById is:
$scope.getTemplateById = function(id) {
return 'template-' + id + '.html';
};
You can see it in action here.
I think you got it from wrong side, in angularjs controllers/data drives the view, here you are creating elements (and even worse adding them to page) in loop (expensive DOM operations)
I'd replace your code with following
<div id="betsHolder">
<div ng-repeat="bet in phaseBets track by bet.id">Bet id: {{bet.id}}</div>
</div>
as soon as you assign your array/object to $scope.phaseBets the DOM will be created
but using ng-repeat is better option,
angular.forEach($scope.phaseBets, function(bet, i)
{
var betElement = angular.element('<div ng-model="bet">Bet id: {{bet.id}}</div>');
angular.element(document.querySelector('#betsHolder')).append(betElement);
$compile(betElement)($scope);
});

Categories

Resources