angularjs $compile html template in forEach not update variables - angularjs

I want to dynamically generate html. I have generateHtml function contains loop for items, currently it is not displaying proper variables added in template. It is always display the last items data on all the iteration on compiled html.
Following is the controller & template code
This is my controller code
angular.module('app').controller('PageController', ['$scope', '$sce', '$compile','$templateRequest',
function ($scope, $sce, $compile,$templateRequest) {
$scope.itemsHtml = '';
// Array contains dynamic data
vm.items = [{
id: 1,
name: 'abc',
}, {
id: 2,
name: 'pqr',
}, {
id: 3,
name: 'stu',
}, {
id: 4,
name: 'xyz',
}];
vm.currentItem = [];
let templateUrl = $sce.getTrustedResourceUrl('/views/item.html');
$templateRequest(templateUrl).then(function(template) {
vm.itemTemplate = template;
}, function() {
});
vm.generateHtml = function() {
items.forEach(function (item, key) {
vm.currentItem = item;
let compiledTemplate = $compile(vm.itemTemplate)($scope).html();
/* Append compiled dynamic html */
$scope.itemsHtml += compiledTemplate;
});
}
function init() {
vm.generateHtml();
}
init();
}
]);
This is template view
<script type="text/ng-template" id="item.html">
<div className="item-wrapper">
<div className="item-inner">
{{ pageCtrl.currentItem.name }}
</div>
<div className="action-inner">
<div className="btn-action"
role="button"
ng-click="pageCtrl.edit(
pageCtrl.currentItem.id
)">
<i className="fa fa-plus"></i>
</div>
</div>
</div>
</script>

I got the solution for this issue.
Actually when we use compile after that we have to interpolate the compiled template
compiledTemplate = $interpolate(compiledTemplate)($scope);
let compiledTemplate = $compile(vm.itemTemplate)($scope).html();
/* Here interpolated compiled template */
compiledTemplate = $interpolate(compiledTemplate)($scope);
/* Append compiled dynamic html */
$scope.itemsHtml += compiledTemplate;

Related

AngularJS - Watch filtered list for changes

Within angular I have a filtered list of people that takes the filter criteria from a predicate function. I want to watch a variable of the filtered list (called filteredPeople) every time the filtered list changes. But I am unable to see when that variable changes.
My code is below:
HTML:
<ul>
<li ng-repeat="person in ($ctrl.filteredPeople = ($ctrl.people | filter: $ctrl.filter))">
...
</li>
</ul>
JS:
controller: ['$scope',
function ($scope) {
var $ctrl = this;
$ctrl.people = {...}
$ctrl.filteredPeople = [];
$scope.$watch($ctrl.filteredPeople, function () {
console.log("called"); //not being called
});
$ctrl.filter = function (p) {
//custom filter function for each item in the array of people
}
}]
I can answer any questions of provide more code if needed
angular.module('app', []).controller('ctrl', function($scope) {
var vm = this;
vm.items = [
{ name: 'Sam' },
{ name: 'Max' },
{ name: 'Tom' },
{ name: 'Henry' },
{ name: 'Jack' },
{ name: 'Kate' }
]
var counter = 1;
$scope.$watchCollection('vm.filtered', function(){
console.log('Changed' + counter++);
})
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>
<div ng-app='app' ng-controller='ctrl as vm'>
<input type='text' ng-model='vm.filter' />
<ul>
<li ng-repeat='item in vm.filtered = (vm.items | filter : vm.filter)'>{{item}}</li>
</ul>
</div>

How to access child records of a firebase array dynamically using Angularfire

I am trying to implement a accordion using angularfire . I am able to retrieve the top level list ("A1","D1","R1")for display but I am unable to figure out how to retrieve the child for each accordion tab that is selected. For Eg if I select "D1", it should open up and display "C1", "H1".
Here is my data on firebase
{
"A1" : {
"B1" : 50
},
"D1" : {
"C1 " : 98,
"H1" : 12
},
"R1" : {
"RR1" : 67,
"RD1" : 54
}
}
My code
var app=angular.module("sampleApp",["firebase"]);
app.controller("MyCtrl", ["$scope", "$firebaseArray", "$firebaseObject",
function($scope, $firebaseArray,$firebaseObject) {
var ref = firebase.database().ref("Products/");
var list = $firebaseArray(ref);
$scope.list = list;
$scope.activeTabs = [];
// check if the tab is active
$scope.isOpenTab = function (tab) {
// check if this tab is already in the activeTabs array
if ($scope.activeTabs.indexOf(tab) > -1) {
// if so, return true
return true;
} else {
// if not, return false
return false;
}
}
// function to 'open' a tab
$scope.openTab = function (tab) {
// check if tab is already open
if ($scope.isOpenTab(tab)) {
//if it is, remove it from the activeTabs array
$scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
} else {
// if it's not, add it!
$scope.activeTabs.push(tab);
}
}
}
]);
HTML Code
<div class="container accordion__container" ng-controller="MyCtrl">
<div class="accordion__tab" ng-repeat="products in list">
<div class="accordion__tab-title" ng-click="openTab(products.$id)">{{products.$id}} </div>
<div class="accordion__tab-content" ng-show="isOpenTab(products.$id)">
<div class="accordion__tab-contentdet" ng-repeat="productDet in <sub Product details>">
</div>
</div>
</div>
</div>
I made some changes in your code.
In HTML i used nav tabs.
<ul class="nav nav-tabs">
<li ng-repeat="products in list">
<a data-toggle="tab" href="#{{products.id}}">{{products.id}}</a>
</li>
</ul>
<div class="tab-content">
<div id="{{products.id}}" class="tab-pane fade" ng-repeat="products in list">
<h3>{{products.id}}</h3>
<p>Content : {{products.data}}.</p>
</div>
</div>
Controller
app.controller("MyCtrl", ["$scope", "$firebaseObject",
function($scope, $firebaseObject) {
var ref = firebase.database().ref("Products");
var list = $firebaseObject(ref);
list.$loaded().then(function() {
$scope.list = [];
angular.forEach(list, function(value,key){
$scope.list.push({ id: key, data: value})
})
});
}
]);
Another Method
Instead of using list.$loaded() you can use the below code:
ref.once('value', function(snapshot) {
$scope.list = [];
angular.forEach(snapshot.val(), function(value,key){
$scope.list.push({ id: key, data: value})
})
})
I just created a plunker for you. Please check it
https://plnkr.co/edit/5dOr7xAWIFlmdykAC1yh
if you have any doubt please let me know.

Search array without ng-repeat in Angular

I'm fairly new in Angular and have an object I'm creating during the user flow. The user is basically setting some preferences, including choosing an avatar from a set of available images. I need to be able to show that avatar as associated with that individual. I need to do it by ID since these avatars will be stored in a DB for launch.
I want to set the background image of a dom element to this image.
Here's a plunkr showing what I'm trying to do. ng-repeat doesn't seem right for an inline style to me. Other options? Sample code:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.avatars = [
{
'id': 1,
'image': '../images/placeholder/avatar-settings-dog.png'
},
{
'id': 2,
'image': '../images/placeholder/avatar-settings-ladybug.png'
},
{
'id': 3,
'image': '../images/placeholder/avatar-settings-cat.png'
},
{
'id': 4,
'image': '../images/placeholder/avatar-settings-horse.png'
}
];
$scope.users = [
{
'id': 0,
'name': 'Joe',
'avatarID': 2
},
{
'id': 1,
'name': 'Mary',
'avatarID': 4
}
]
});
and the view:
<div ng-controller="MainCtrl">
<p ng-repeat="user in users" style="background:url(AVATAR PATH);">Hello {{user.name}}!</p>
</div>
Use ng-style to programmatically set the style.
<p ng-repeat="user in users"
ng-style="getAvatarBackground(user)">Hello {{user.name}}</p>
And then your controller:
$scope.getAvatarBackground = function(user) {
var avatar = // find the correct object in $scope.avatars based on user.avatarID
return {
background: "url('" + avatar.image + "');"
}
}
And an example to find the avatar object:
function findAvatarById(avatarId) {
for(var i = 0; i < $scope.avatars.length; i++) {
if($scope.avatars[i].id === avatarId) {
return $scope.avatars[i]
}
}
}
And then
var avatar = findAvatarById(avatarId);
use this code:
see plunker
Html code:
<body ng-controller="MainCtrl">
<p ng-repeat="user in users" style="background:url({{returnimageurl(user)}});">Hello {{user.name}}!</p>
</body>
and Js code:
$scope.returnimageurl=function(user){
for( key in $scope.avatars){
if(user.avatarID==$scope.avatars[key].id){
return $scope.avatars[key].image;
}
}
}

How to parse model data in the view onto a javascript function in Angular

Assuming I have the following:
<button ng-repeat="{{item in items}}" ng-click="{{item.name}}Function()">{{item.name}}</button>
I need to be able to get the ng-click to dynamically change based on the item.name such as
firstItemFunction()
secondItemFunction()
etc.
If $scope has references to the functions:
$scope.items =
[
{ name: 'firstItemFunction' },
{ name: 'secondItemFunction' }
];
$scope.firstItemFunction = function () {
console.log('firstItemFunction');
};
$scope.secondItemFunction = function () {
console.log('secondItemFunction');
};
HTML:
<button ng-repeat="item in items" ng-click="this[item.name]()">
{{item.name}}
</button>
Demo: http://plnkr.co/edit/FSrGumlZqm4Rdku6I3X5?p=preview
Alternatively:
$scope.items = [{
name: 'firstItemFunction'
}, {
name: 'secondItemFunction'
}];
$scope.firstItemFunction = function() {
console.log('firstItemFunction');
}
$scope.secondItemFunction = function() {
console.log('secondItemFunction');
}
$scope.execute = function(action) {
$scope[action]();
};
And:
<button ng-repeat="item in items" ng-click="execute(item.name)">
{{item.name}}
</button>
Demo: http://plnkr.co/edit/6jATpgEAvFgTFXbvQ6IE?p=preview
If the functions are defined globally use HTML from above, inject $window and:
$scope.items = [{
name: 'firstItemFunction'
}, {
name: 'secondItemFunction'
}];
$scope.execute = function(action) {
$window[action]();
};
Demo: http://plnkr.co/edit/TinMbmvMTIQS4vptQMYf?p=preview
I would move the logic for determining which function to call to your javascript.
html
<button ng-repeat="{{item in items}}" ng-click="figureOutFunction(item)">{{item.name}}</button>
javascript
$scope.figureOutFunction(item){
if(item.name == "firstItem"){
firstItemFunction();
}
else if(item.name == "secondItem"){
secondItemFunction();
}
};
edit
If you want to avoid the switch, you can do it this way:
html
<button ng-repeat="{{item in items}}" ng-click="item.action()">{{item.name}}</button>
javascript
var firstItemFunction = function(){
...
};
var secondItemFunction = function(){
...
};
$scope.items = [{
name: 'firstItem',
action: firstItemFunction
}, {
name: 'secondItem',
action: secondItemFunction
}];
I would avoid creating unnecessary functions that call others.

AngularJS - ng-click does not remove previous click's modifications when clicked again

So I am trying to acomplish this example: http://jsfiddle.net/pkozlowski_opensource/WXJ3p/15/
However, for some reason, when I click on one div, and then on another, it does not remove the "active" class from the previous div so it highlights both , hence all my divs end up with the class active if I click all of them. I want to make it to where it will actually remove the class if I click anywhere else on the body and also if I click on any other div, like the fiddle example.
My jsFIddle
http://jsfiddle.net/GeorgiAngelov/jUj56/4/
<div ng-controller="MyCtrl">
<button class="addQuestionButton btn btn-primary" ng-click="AddRootQuestion(questions)">Add node</button>
<div ng-repeat="question in questions" ng-include="question"> </div>
<script type="text/ng-template" id="question">
<!-- Single question starts here -->
<div ng-controller="QuestionController" ng-class="{active : isSelected(question)}">
<button class="btn btn-primary" ng-click="AddSubQuestion(question)">Add node</button>
<button class="btn btn-primary" ng-click = "editQuestion(question)">Edit Question</button>
</div>
<div ng-repeat="question in question.nodes" ng-include="question">
</div>
</script>
</div>
Since each single question has its own QuestionController, and QuestionControlleris where $scope.selected is being set, they don't interact with each other. That is to say, when you click edit, it sets selected for that individual controller.
The easy way to fix it would be to set a property on a parent scope (the containing controller) when clicking edit:
function MyCtrl($scope) {
$scope.questions = [];
$scope.AddRootQuestion = function(questions) {
questions.push({name: 'Question', nodes: []});
};
// added this method --v
$scope.setSelected = function(q) {
$scope.selected = q;
};
}
function QuestionController($scope) {
$scope.choices = [];
$scope.choice = {text: ''};
$scope.AddSubQuestion = function(question, $element) {
var newName = 'Name of question goes here';
question.nodes.push({name: newName, id: 'it goes in here', nodes: []});
};
// modified this method --v
$scope.editQuestion = function(question){
$scope.setSelected(question);
};
$scope.isSelected = function(question) {
return $scope.selected === question;
};
}
But this makes QuestionController dependent upon a parent controller. A much better design would be to move all the data and data manipulation methods into a service:
var myApp = angular.module('myApp',[]);
myApp.factory('Question', function() {
return {
questions: [],
addRootQuestion: function() {
this.questions.push({name: 'Question', nodes: []});
},
addSubQuestion: function(question, data) {
question.nodes.push(data);
},
setSelectedQuestion: function(question) {
this.selectedQuestion = question;
},
isSelectedQuestion: function(question) {
return this.selectedQuestion == question;
}
};
});
You can then inject the service into your controllers:
function MyCtrl($scope, Question) {
$scope.questions = Question.questions;
$scope.AddRootQuestion = function() {
Question.addRootQuestion();
};
}
function QuestionController($scope, Question) {
$scope.choices = [];
$scope.choice = {text: ''};
$scope.AddSubQuestion = function(question, $element) {
var newName = 'Name of question goes here';
Question.addSubQuestion(question, {
name: newName, id: 'it goes in here', nodes: []
});
};
$scope.editQuestion = function(question){
Question.setSelectedQuestion(question);
};
$scope.isSelected = function(question) {
return Question.isSelectedQuestion(question);
};
}
Example: http://jsfiddle.net/BinaryMuse/pZkBv/
If you wanted, you could build up a richer data model using JavaScript OOP principles; for example, you could have Question instances with methods for manipulating the questions, which themselves live inside an instance of QuestionContainer (or Survey or Test or whatever).

Resources