angularjs data binding with dynamically created elements - javascript

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

Related

AngularJS append HTML in the ng-repeat element

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

AngularJS - ngRepeat and ngMessages

I struggle to make ngMessages work in a ngRepeat loop. Please consider the following example :
<form name="$ctrl.demoForm.$fc" novalidate>
<div ng-repeat="tag in $ctrl.demoForm.tags track by $index">
<div>
<label>Tag:</label>
<input name="tag{{$index}}" ng-model="tag.value" type="text" ng-pattern="/^[a-z]*$/">
<button ng-click="$ctrl.removeTag($index)">Remove</button>
</div>
<ng-messages for="$ctrl.demoForm.$fc['tag'+$index].$error">
<ng-message when="pattern">Must only contain letters.</ng-message>
</ng-messages>
</div>
<button ng-click="$ctrl.addTag()">Add new tag</button>
</form>
I simply iterate over an array of tags stored in $ctrl.demoForm.tags. I'm aware that ng-repeat creates a new scope each iteration so each tag is an object like {value: 'tag value'}.
Here is the controller associated, very basic stuff:
app.controller('DemoCtrl', function() {
this.demoForm = {
tags: [
{value: 'tag 1'},
{value: 'tag 2'}
]
};
this.addTag = function(){
this.demoForm.tags.push({value: ''});
};
this.removeTag = function(index) {
if (this.demoForm.tags.length > index) {
this.demoForm.tags.splice(index, 1);
}
};
});
It works fine, I can add and remove tags normally. But, problems arise when a field has an error. If you try to remove a field above the field on error, their values get messed up.
The odd thing is that the good field is actually removed, only their values seems desynchronized when on error. I added an id attribute in the jsbin to illustrate the problem, you can find it here :
http://jsbin.com/nifipatojo/edit?html,js,output
Does anyone have an idea of what happen ?
Thanks for your help.

prevent reflecting ng-model value across all select tags

I am pretty new to AngularJS. I am working on a project wherein I need to append certain html select tags based on a button click. Each select tag is bound to a ng-model attribute (which is hardcoded). Now the problem I am facing is, once I append more than 2 such html templates and make changes in a select tag then value selected is reflected across all the tags bound to the corresponding ng-model attribute (which is pretty obvious). I would like to know if there is a way around it without naming each ng-model differently.
JS code:
EsConnector.directive("placeholderid", function($compile, $rootScope, queryService, chartOptions){
return {
restrict : 'A',
scope : true,
link : function($scope, element, attrs){
$scope.current_mount1 = "iscsi";
$scope.current_dedupe1 = "on";
$scope.y_axis_param1 = "Total iops";
var totalIops =[];
var totalBandwidth =[];
element.bind("click", function(){
$scope.count++;
$scope.placeholdervalue = "placeholder12"+$scope.count;
var compiledHTML = $compile('<span class="static" id='+$scope.placeholdervalue+'>choose mount type<select ng-bind="current_mount1" ng-options="o as o for o in mount_type"></select>choose dedupe<select ng-model="current_dedupe1" ng-options="o as o for o in dedupe"></select>choose y axis param<select ng-model="y_axis_param1" ng-options="o as o for o in y_axis_param_options"></select></span><div id='+$scope.count+' style=width:1400px;height:300px></div>')($scope);
$("#space-for-buttons").append(compiledHTML);
$scope.$apply();
$(".static").children().each(function() {
$(this).on("change", function(){
var id = $(this).closest("span").attr("id");
var chartId = id.slice(-1);
queryService.testing($scope.current_mount1, $scope.current_dedupe1, function(response){
var watever = response.hits.hits;
dataToBePlot = chartOptions.calcParams(watever, totalIops, totalBandwidth, $scope.y_axis_param1);
chartOptions.creatingGraph(dataToBePlot, $scope.y_axis_param1, chartId);
});
});
});
});
}
}
});
Code explanation:
This is just the directive which I am posting.I am appending my compiledHTML and doing $scope.apply to set the select tags to their default values. Whenever any of the select tags are changed I am doing a set of operations (function calls to services) on the values selected.
As you can see the ng-model attribute being attached is the same. So when one select tag is changed the value is reflected on all the appended HTML even though the data displayed does not match to it.
Hope this PLunker is useful for you. You need to have one way binding over such attributes
<p>Hello {{name}}!</p>
<input ng-model="name"/>
<br>Single way binding: {{::name}}
Let me know if I misunderstood your question
It is a bit hard to understand your whole requirement from your description and your code, correct me if I'm wrong: you are trying to dynamically add a dropdown on a button click and then trying to keep track on each of them.
If you are giving the same ng-model for each generated items, then they are bound to the same object, and their behavior is synchronized, that is how angular works.
What you can do is, change your structure to an array, and then assigning ng-model to the elements, so you can conveniently keep track on each of them. I understand you came from jquery base on your code, so let me show you the angular way of doing things.
angular.module('test', []).controller('Test', Test);
function Test($scope) {
$scope.itemArray = [
{ id: 1, selected: "op1" },
{ id: 2, selected: "op2" }
];
$scope.optionList = [
{ name: "Option 1", value: "op1" },
{ name: "Option 2", value: "op2" },
{ name: "Option 3", value: "op3" }
]
$scope.addItem = function() {
var newItem = { id: $scope.itemArray.length + 1, selected: "" };
$scope.itemArray.push(newItem);
}
$scope.changeItem = function(item) {
alert("changed item " + item.id + " to " + item.selected);
}
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app='test' ng-controller='Test'>
<button type='button' ng-click='addItem()'>Add</button>
<select ng-repeat='item in itemArray'
ng-options='option.value as option.name for option in optionList'
ng-model='item.selected'
ng-change='changeItem(item)'></select>
</div>

Remove object from ng-repeat

I have a PhoneGap + Onsen UI + AngularJS app in the works, where I have a list in the view, where the items will be fetched from the controllers variable.
I want to be able to remove items from this list, by clicking on them.
The list looks like this:
<ons-list>
<ons-list-item modifier="tappable" class="item" ng-repeat="citem in completeditems" ng-click="delete(citem)">
<ons-row>
<ons-col>
<div class="titlediv">
<header>
<span class="item-title">{{citem.name}}</span>
</header>
</div>
<div class="item-dates">
<span class="item-start">{{citem.start}}</span>
</div>
</ons-col>
</ons-row>
</ons-list-item>
</ons-list>
The completeditems object in the $scope looks like this:
var completeditemname = "item" + i;
$scope.completeditems[completeditemname] = {
id : "ID",
name : "Name for it",
start: "Start date"
}
Tried the following method, but it didn't work out:
$scope.delete = function(item) {
var index = $scope.completeditems.indexOf(item);
$scope.completeditems.splice(index,1);
//$scope.completeditems.remove(item); //tried this aswell
$scope.$apply() //i need this to update the view
}
You do not need the $scope.$apply() invocation. As you are making alterations to scope variables the digest cycle will be triggered anyhow and you will be encountering an error because of this I believe.
UPDATED:: You're working with an actual object by the looks of it so I've updated the code in the plunker to help you out. It means altering the ng-repeat to use both key and value.
Here is a simple plunkr showing a basic example of what you are trying to do with a one liner in the delete function http://plnkr.co/edit/NtQD....
<body ng-app="myApp">
<div ng-controller="myController as ctrl">
<ul ng-repeat="(key, value) in ctrl.items track by key">
<li ng-click="ctrl.delete(key)">{{value}}</li>
</ul>
</div>
</body>
var myApp = angular.module('myApp', [])
.controller('myController', [
'$scope',
function($scope) {
var self = this;
self.items = {
item1: {
id: 1,
name: 'a'
},
item2: {
id: 2,
name: 'b'
},
item3: {
id: 3,
name: 'c'
}
};
self.delete = function(key) {
delete self.items[key];
};
}
]);
Hope that helps you out!
$scope.$apply() should only be used when changes are coming in from outside the Angular framework. Since your delete() function is being called from an ng-click, it is already being managed by Angular and calling $apply() will raise a "$digest is already in progress" error (check your browser console). Removing that call will most likely get your code working.

Building html from an ajax call to jquery UIs sortable list

Im having a design problem in HTML/JavaScript.
I appended jquery UIs sortable to my web-application:
Heres a demo on sortable (cant show my application now):
http://jqueryui.com/demos/sortable/default.html
Now im populating that drag and drop list in JavaScript with data from an ajax call. The list is changed by users all the time.
I try do something like this:
Var htmlData = '<div id=wrapper>'
+'<div>'
+data.title
+'</div>'
+'<div>'
+data.description
+'</div>';
${"#sortable-list"}.html(htmlData);
And so on. Some of the divs also have attributes set in variables like 'id="' + data.id + '"'
I then try to fit this string htmldata in the sortable-list. But it's getting messy pretty quick.
I tried to fit <tables> in it, and <p> with <span>s in. But it's still hard to get the design that I want.
Cant post images due to lack of reputation but here's the design i want (this is just one <li> in the <ul>):
http://img546.imageshack.us/img546/9179/48361880.gif http://img546.imageshack.us/img546/9179/48361880.gif
So how would you do this? I've been reading about templates like mustache but it don't seems to help me.
And the way I building the table with a string can't be the best way.
Any example or info on how to do this is much appreciated
There are many advantages of client side tempting but the main one is that you don't have to mess with stringing HTML together in JavaScript. Doing so is not only error prone but it also quickly becomes a maintenance nightmare.
For example here's how you could tackle your requirements with Underscore.js tempting.
As you can see the HTML is clearly laid out and will be easy to alter as your requirements change.
<script type="text/template" id="sortable-entry">
<% _.each(items, function(item) { %>
<div>Title: <%= item.title %></div>
<div>Description: <%= item.description %></div>
<hr />
<% }); %>
</script>
<ul id="sortable-list"></ul>​
<script>
var data = {
items: [
{ title: 'title1', description: 'description1' },
{ title: 'title2', description: 'description2' },
{ title: 'title3', description: 'description3' },
{ title: 'title4', description: 'description4' },
{ title: 'title5', description: 'description5' }
]
};
var event_html = _.template($('#sortable-entry').html());
$(event_html(data)).appendTo($('#sortable-list'));
</script>
Here's a live example of this - http://jsfiddle.net/tj_vantoll/kmXUr/.
You could do something like this.
var items[]
items.push($('<div />', { html: data.title }));
items.push($('<div />', { html: data.description}));
$("#sortable-list").html($('<div />', {
'id' : 'wrapper',
'class' : 'yourclass',
html: items.join('')
})
);
the items.push you can use in a loop to cycle all data and push items into the array. Later join the array items into the html

Categories

Resources