AngularJS: weird behavior with the 2-way binding (ng-repeat) - javascript

I'm using AngularJS in a Firebase app and I have a function where I do some inner join to get some data. More details here. After getting the response from the firebase api I create an object and push it into an array (a scope variable). I see in the debug that the data has been retrieved and that the $scope variable is filled correctly. The problem is that it is not showing in the ng-repeat.
My function:
$scope.getMessagesByRegion = function(regionId){
console.log('Function start');
var rootRef = firebase.database().ref();
var regionMessagesRef = rootRef.child("region_messages/"+ regionId);
$scope.messages_by_region = []; // Here I reset the scope variable
regionMessagesRef.on('child_added', function(rmSnap) {
var messageRef = rootRef.child("messages/"+rmSnap.key);
messageRef.once('value').then(function(msgSnap){
var msg = {
key : msgSnap.key,
name : msgSnap.val().name,
type : $scope.getTypeName(msgSnap.val().type),
show_only_once : rmSnap.val().show_only_once,
pre_requisite_message : rmSnap.val().pre_requisite_message
}
console.log(msg); // here I see the object in the console. it is OK
$scope.messages_by_region.push(msg); // pushing the item
console.log('----------------');
console.log($scope.messages_by_region);
})
});
}
My HTML:
<table class="table">
<thead>
<tr>
<th>Message name</th>
<th>Type</th>
<th>Show only once</th>
<th>Pre requisite</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="msg in messages_by_region">
<td ng-bind="msg.name"></td>
<td ng-bind="msg.type"></td>
<td ng-bind="msg.show_only_once"></td>
<td ng-bind="msg.pre_requisite_message"></td>
</tr>
</tbody>
</table>
This is what I see in the console:
The problem is that even having an object in the array it is not shown in the view. It is like there was an empty array set to the $scope.messages_by_region variable
I'm having a hard time figuring out what I'm doing wrong. Can you see what's wrong with my function?
Thanks for any help.

try,
$scope.$apply(function(){
$scope.messages_by_region.push(msg);
});
or,
$scope.messages_by_region.push(msg);
$scope.$apply();

Since you're using async functions (Cosuming of firebase API) you should tell angular to refresh the HTML;
Use
$scope.$diggest()
More information you can find on https://www.sitepoint.com/understanding-angulars-apply-digest/

As you are performing Async calls you need to tell angular to refresh the changes in the value with $apply call you can do it with:
$scope.getMessagesByRegion = function(regionId) {
console.log('Function start');
var rootRef = firebase.database().ref();
var regionMessagesRef = rootRef.child("region_messages/" + regionId);
$scope.messages_by_region = []; // Here I reset the scope variable
regionMessagesRef.on('child_added', function(rmSnap) {
var messageRef = rootRef.child("messages/" + rmSnap.key);
messageRef.once('value').then(function(msgSnap) {
var msg = {
key: msgSnap.key,
name: msgSnap.val().name,
type: $scope.getTypeName(msgSnap.val().type),
show_only_once: rmSnap.val().show_only_once,
pre_requisite_message: rmSnap.val().pre_requisite_message
}
$scope.$apply(function() {
console.log(msg); // here I see the object in the console. it is OK
$scope.messages_by_region.push(msg); // pushing the item
console.log('----------------');
console.log($scope.messages_by_region);
});
});
});
}
For more information on this behavior you can also read article describing the problem here

Related

Execute method in one of ng-repeat child

I'm using angular's ng-repeat to populate table with data from GitHub api and I want one of the <td> to execute method that will return data. the problem is that this makes the function execute infinitely.
HTML:
<table>
<thead>
<tr>
<th>Name</th>
<th>Languages</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="repo in repos">
<td>{{repo.name}}</td>
<td></td> <!-- Get languages for each repo here. -->
</tr>
</tbody>
</table>
Angular:
(function(){
var app = angular.module("githubViewer", []);
var MainController = function($scope, $http){
var onUserComplete = function(response){
$scope.user = response.data;
$http.get($scope.user.repos_url)
.then(onRepoComplete, onError);
};
var onRepoComplete = function(response){
$scope.repos = response.data;
};
var onError = function(reson){
$scope.error = "Could not fetch the data";
};
//search for user
$scope.search = function(username){
$http.get("https://api.github.com/users/" + username)
.then(onUserComplete, onError);
};
// These two execute infinately if executed in the ng-repeat's <td>
$scope.findLangs = function(langsUrl){
$http.get(langsUrl)
.then(onLangComplete, onError);
}
var onLangComplete = function(response){
return response.data;
};
};
app.controller("MainController", ["$scope", "$http", MainController]);
})();
I've tried using {{ findLangs(repo.languages_url) }} in the <td> but it causes me to get this error:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
and appears to be infinitely executing.
I don't think you can use an expression there because you don't actually return a value from the findLangs() function. That said, I don't think that would cause an infinite loop. I think you'll need to actually grab the language data for each repo within the onRepoComplete callback, then just use that data in your template:
var onRepoComplete = function(response){
$scope.repos = response.data;
$scope.repos.forEach(function(repo) {
$http.get(repo.languages_url)
.then(function(response) {
// change this to process the language data however you need to...
repo.languages = JSON.stringify(response.data);
},onError);
});
};
Then in your template you can use the languages property of the repo:
<tr ng-repeat="repo in repos">
<td>{{repo.name}}</td>
<td>{{repo.languages}}</td>
</tr>

AngularJS: ng-hide not working

I am trying to hide a column in my table using ng-hide. Before the user logins the column should not been shown to the user. After they login the hidden column should be shown. But now after i used the ng-hide property the whole table is hidden if the user isnt login into the system. Can i know how to solve this problem.
This is my partial html code:
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th ng-show="noteEnabled">Note</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="movie in movies | pagination: currentPage * entryLimit | limitTo: entryLimit" data-ng-class="{'selected':selectedFilm.film_id===movie.film_id}" >
<td>
{{movie.title}}
</td>
<td>
{{movie.description}}
</td>
<td data-ng-click="selectFilmDetails($event,movie)" ng-show="movie.noteEnabled" >
{{movie.comment}}
</td>
</tr>
</tbody>
</table>
This is my controller.js code:
.controller('AllMovieController',
[
'$scope',
'dataService',
'$location',
function ($scope, dataService, $location){
$scope.noteEnabled = false;
$scope.movies = [ ];
$scope.movieCount = 0;
$scope.currentPage = 0; //current page
$scope.entryLimit = 20; //max no of items to display in a page
var getAllMovie = function () {
dataService.getAllMovie().then(
function (response) {
var userName=dataService.getSessionService('user');
$scope.movieCount = response.rowCount + ' movies';
if(userName){
$scope.movies = response.data;
$scope.userLogin = dataService.getSessionService('user');
$scope.userLoginEmail = dataService.getSessionService('userEmail');
$scope.showSuccessMessage = true;
$scope.successMessage = "All movie Success";
$scope.noteEnabled = true;
}
},
function (err){
$scope.status = 'Unable to load data ' + err;
}
); // end of getStudents().then
};
$scope.numberOfPages = function(){
return Math.ceil($scope.movies.length / $scope.entryLimit);
};
//------------------
$scope.selectFilmDetails = {};
$scope.selectFilmDetails = function ($event,movie) {
$scope.selectFilmDetails = movie;
$location.path('/filmDetails/' + movie.film_id);
}
getAllMovie();
}
]
)
At first i set the noteEnabled to false and check with the session if the user is logged in then the noteEnabled will become true. Thanks in advance.
Use ng-hide="$parent.noteEnabled" instead of ng-hide="noteEnabled".
To access $scope variable from the loop (ng-repeat) use $parent
Here is a good example of how to use ng-show and ng-hide and here is the official documentation as well . Hope it helps !
In your case you have use ng-show/ ng-hide only to tag in of the column you want to show/hide. So on any scenario whole table will not be hide unless there is no data so you may is it as hidden.
Anyway as per you code, seems you have misused ng-show/hide. On controller initially you set noteEnabled to false and after you check the logging you set noteEnabled to true. as you have used ng-show/hide as follows
<td data-ng-click="selectFilmDetails($event,movie)" ng-hide="noteEnabled" >
{{movie.comment}}
</td>
the result will be; initially column will be shown and after your dataService receive userName it will hide the column. The opposite of what you want!!!. So change the directive you use from ng-hide to ng-show or change the value set to noteEnabled.
The real cause behind problem is, ng-repeat does create child scope which is prototypically inherited from parent scope, while rendering each iteration element where ng-repeat directive has placed.
And the you have used noteEnabled variable as primitive datatype, so when you use noteEnabled variable inside a ng-repeat div it does gets added inside that ng-repeat div scope.
noteEnabled property to be maintained on each element level of movies collection. Then do toggle, whenever you want.
ng-show="movie.noteEnabled"
By default it will be hidden & toggle it whenever you want to show it.
Even better approach is to follow controllerAs pattern where you don't need to care about prototypal inheritance. While dealing with such a variable access thing on UI.
I solved the problem by myself. Here is the solution for it.
$scope.noteEnabled = false;
$scope.movies = [ ];
$scope.movieCount = 0;
$scope.currentPage = 0; //current page
var getAllMovie = function () {
dataService.getAllMovie().then(
function (response) {
var userName=dataService.getSessionService('user');
$scope.movieCount = response.rowCount + ' movies';
if(userName){
$scope.movies = response.data;
$scope.userLogin = dataService.getSessionService('user');
$scope.userLoginEmail = dataService.getSessionService('userEmail');
$scope.showSuccessMessage = true;
$scope.successMessage = "All movie Success";
$scope.noteEnabled = true;
}else{
$scope.movies = response.data;
$scope.noteEnabled = false;
}

How to keep track of Array index in angularJS?

Very new to Angularjs. So I have some JSON files that I am reading into my webpage that contain and array of objects that are cars. What I am trying to do is have my "button" when pressed alert me to the data specific to that button.
The ng-repeat is running 8 times so that is the length of the array, but in angularJs i'm not sure how to basically store the array index for each time the ng-repeat passes in my button function.
This is my a snippet of my .html:
<div class="carTable table-responsive text-center" ng-controller="ButtonController" >
<table class="table specTable">
<thead>
<tr>
<th>Make</th>
<th>Model</th>
<th>Year</th>
<th>Color</th>
<th>Milage</th>
<th>Doors</th>
<th class="reserve">Horsepower</th>
<th>Price</th>
<th class="reserve"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="cars in car | orderBy:'year'">
<td>{{cars.year}}</td>
<td>{{cars.model}}</td>
<td>{{cars.make}}</td>
<td>{{cars.color}}</td>
<td>{{cars.mileage | number}}</td>
<td>{{cars.doors}}</td>
<td>{{cars.horsepower}}</td>
<td>{{cars.price | number}}</td>
<td><div class="panel panel-default">Reserve</div></td>
</tr>
</tbody>
</table>
</div>
The portion in question is at the bottom where I have a Reserve "button"
I'm leaving out my JSON files, works properly there. I'm just not sure how to keep track of the array index as the ng-repeat does its thing.
Here is the angular:
(function(){
var app = angular.module("myReserveApp", []);
app.controller("ButtonController", ["$scope", "$window", function($scope, $window){
$scope.buttonPress = function(){
$window.alert(JSON.stringify($scope.car[0]));
}
}]);
var MainController = function($scope, $http, $window){
var onGatherBoatData = function(response){
$scope.boat = response.data;
};
var onError = function(reason){
$scope.error = "Could not fetch Boat Data";
};
var onGatherCarData = function(response){
$scope.car = response.data;
};
var onError = function(reason){
$scope.error = "Could not fetch Car Data";
};
var onGatherTruckData = function(response){
$scope.truck = response.data;
};
var onError = function(reason){
$scope.error = "Could not fetch Truck Data";
};
$scope.message = "Hello, Angular Here!";
};
app.controller("MainController", ["$scope", "$http", "$window", MainController]);
}());
Currently in the top portion of the code I just have it alerting object[0] but I want it to be specific to which button is pressed. Any help is appreciated, thank you.
$index refers to the index in ng-repeat. So if you want to pass your function the index in array on the button click, change buttonPress() to buttonPress($index)
you'll have to change your controller to something like the following:
$scope.buttonPress = function(index){
$window.alert(JSON.stringify($scope.car[index]));
}
To do the following, you can just pass the current data in the ngRepeat. Moreover,if you want the current index, the ngRepeat directive provide specials properties, as the $index, which is an iterator.
$scope.buttonPress = function(car, index){
//Retrieve current data of the ngRepeat loop
console.log(car);
//Current index of your data into the array
console.log(index);
}
Then you can call your function like this :
Reserve
First, thank you both for the quick responses. Both of these answers work. I found another way to do it as well before reading your posts.
<div class="panel panel-default">
Reserve:{{car.indexOf(cars)}}
</div>
Using (car.indexOf(cars)) gives me the same result
$scope.buttonPress = function(index){
$window.alert(JSON.stringify(index));
}
Now when I click on the "button" it sends me back the array index, so now I should be able to play with that data. Thank you again both, for your help.

Knockout.js 2.2.1 can't find observable array

Not sure what's going wrong here, but KnockoutJS is having some issues finding my observable array that's inside my MasterViewModel. Using 2.2.1 with jQuery 1.8.x as well as not my first KJS app. Here it is:
Initialize
$(function() {
window.vm = new MasterViewModel();
ko.applyBindings(vm);
});
ViewModel
function MasterViewModel(data) {
var self = this;
self.currentAppView = ko.observable();
// Users
self.userList = ko.observableArray([]);
self.templateListGetter = ko.computed(function() {
$.getJSON("/user/list"), function(data) {
var mapped = $.map(data, function(item) { return new userModel(item) });
self.userList(mapped);
};
});
self.goToAppView = function(appView) {
location.hash = '!/' + appView;
};
Sammy(function() {
this.get('#!/:appView', function() {
self.currentAppView(this.params.appView);
$('.appview').hide();
ko.applyBindings(new window[this.params.appView+'VM']());
});
this.notFound = function(){
location.hash = "!/dashboard";
}
//this.raise_errors = true;
}).run();
}
The View
<table class="table table-bordered table-striped">
<tbody data-bind="foreach: userList">
<tr>
<td data-bind="text: guid"></td>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
<td data-bind="text: email"></td>
<td data-bind="text: updated"></td>
<td data-bind="text: suspended"></td>
</tr>
</tbody>
</table>
I have a simple table that I am loading
Even after double-checking a couple things like adding defer="defer" to my JS tag and ensuring the userList exists, it simply cannot find the observableArray. It gives the error:
Message: ReferenceError: userList is not defined;
Bindings value: foreach: userList Error {}
Anyone have any idea what's going on?
Update
For those wondering what gets called every time the hash changes:
function usersVM() {
// Data
var self = this;
// Behaviours
$('#users').show();
}
It looks like you're initializing knockout with an undefined viewmodel?
ko.applyBindings(new window[this.params.appView+'VM']());, yet your actual viewmodel is window.vm. Case sensitivity ftw. Also, the viewmodel on window is already created / initialized. So you don't need the new operator.
So, change the applyBindings line to be
ko.applyBindings(window[this.params.appView+'vm']());
Updated Answer: By Poster
There was no necessity to keep running ko.applyBindings every time the route changed since it was already applying bindings on page load. So Sammy.js was changed to:
Sammy(function() {
this.get('#!/:appView', function() {
self.currentAppView(this.params.appView);
$('.appview').hide();
window[this.params.appView+'Route']();
});
this.notFound = function(){
location.hash = "!/dashboard";
}
//this.raise_errors = true;
}).run();
It does look like ko.computed or a regular function call to window.vm.getUserList() isn't running properly, but this will be saved for a different question.
function usersRoute() {
// Data
var self = this;
// Behaviours
$('#users').show();
window.vm.getUserList();
}

How do I use `console.log` in conjunction with knockout and subscribe with Knockoutjs?

I'm using Knockoutjs for the first time and I'm having trouble debugging because of my inability to log variables in console. I can see that my JS is loading properly in console, when I enter:
Home.TwitterFeedComponent I see an object returned. How do I use console.log in conjunction with knockout and subscribe?
var Home = Home || {};
var inheriting = inheriting || {};
Home.TwitterFeedComponent = function(attributes) {
if (arguments[0] === inheriting)
return;
Home.OnScreenComponent.call(this, attributes);
var component = this;
var recent_tweets = ko.observableArray();
var url = 'https://twitter.com/search.json?callback=?';
this.attributes.twitter_user_handle.subscribe(function(value) {
var twitter_parameters = {
include_entities: true,
include_rts: true,
from: value,
q: value,
count: '3'
}
result = function getTweets(){
$.getJSON(url,twitter_parameters,
function(json) {
console.log(json)
});
}
console.log(twitter_parameters);
});
};
Home.TwitterFeedComponent.prototype = new Home.OnScreenComponent(inheriting);
Home.TwitterFeedComponent.prototype.constructor = Home.TwitterFeedComponent;
I don't see the problem in your code, but if you want to log 'Observables', you have to log it as follows:
console.log(observableVar());
I'm a little unclear as to the exact scope of the question -- however, if like mine, this question is directed toward the use of console.log within your HTML.
Here is a little set of code that may help:
<div class="tab-content" data-bind="with: ClientSearch.selectedClient">
...
<table class="table table-striped table-condensed table-hover">
<thead></thead>
<tbody>
<!-- ko foreach: { data: _general, as: 'item' } -->
<tr>
<td data-bind="text: eval( 'console.log(\' le item \', item)' )"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
...
</div>
This code simply logs an item inside of a foreach to the console.
Hope this helps!

Categories

Resources