Switch between 2 ng-shows - angularjs

I have two elements with a ng-show in them,
%a.follow{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow
%a.unfollow{"ng-click" => "unfollowUser(user)", "ng-show" => "isFollowed(user.id)"} unfollow
It depends on the user.id which ng-show is being rendered in the template. So only one of the two ng-shows is displayed.
So for example a user wants to start following another user. Then the follow link is displayed.
%a.follow{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow
When a user clicks on it, I would like to hide the clicked ng-show, and show the unfollow ng-show so that the user can unfollow the just followed user.
The follow and unfollow user function,
$scope.followUser = function (user) {
followUser.create({
followed_id: user.id
}).then(init);
Notification.success(user.name + ' is toegevoegd als vriend.');
}
$scope.unfollowUser = function(user){
unfollowUser.unfollowUser(user).then(function(){
},function(){
}).then(init);
Notification.success(user.name + ' is verwijderd als vriend.');
}
And the isFollowed function,
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
console.log ($scope.users)
angular.forEach(response, function(user){
$scope.user = user
$scope.isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i=0; i<following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
})
})
I've tried building this,
<a ng-click="follow=false ;unfollow=true", ng-show="follow">Follow!</a>
<a ng-click="follow=true; unfollow=false", ng-show="unfollow">Unfollow!</a>
This does switch between the two ng-shows, but when I try to get the isFollowed(user.id), !isFollowed(user.id) in them the code crashes.

You should create single function to follow/unfollow, Here in the code snippet I have introduced a new property i.e. isFollowed to object user whose value is set using the isFollowed function.
Additionally, Don't overuse isFollowed(user.id) method, it will be huge performance hit.
HTML
<a ng-click="followUnfollowUser(user)"> {{ user.isFollowed : "Unfollow!" : "Follow!"}} </a>
Script
$scope.followUnfollowUser = function(user) {
//If followed - unfollow
if (user.isFollowed) {
unfollowUser.unfollowUser(user).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
}).then(init);
Notification.success(user.name + ' is verwijderd als vriend.');
} else {
followUser.create({
followed_id: user.id
}).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
}).then(init);
Notification.success(user.name + ' is toegevoegd als vriend.');
}
}
//Define method to check wheather current user is beign followed
var isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i = 0; i < following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
//Fetch Users
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
//Iterate and create isFollowed property
angular.forEach($scope.users, function(user) {
user.isFollowed = isFollowed(user.id);
})
})
Note: I'm not familiar with following syntax thus used standard HTML.
%a.follow{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow

Alrhgout Satpal did point me to the right direction and helped me with some code. His answer isn't complete. So I've decided that add the code I'm using for this function (made with the help of Satpal!).
I've created a followUnfollowUser function. But instead of having two .then(init) I have one init() at the end of the function. Having the two inits gave me some looping trouble.
$scope.followUnfollowUser = function(user) {
//If followed - unfollow
if (user.isFollowed) {
unfollowUser.unfollowUser(user).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
})
Notification.success(user.name + ' is verwijderd als vriend.');
} else {
followUser.create({
followed_id: user.id
}).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
})
Notification.success(user.name + ' is toegevoegd als vriend.');
}
init();
}
Then the init function,
var init = function () {
loadCurrent_user.loadCurrent_user().then(function(response) {
$scope.current_user = response.data;
});
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
//Iterate and create isFollowed property
angular.forEach($scope.users, function(user) {
user.isFollowed = isFollowed(user.id);
})
})
var isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i = 0; i < following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
}
First I load the current user so that the $scope.current_user gets updated when a user is being followed/unfollowed. And then I iterate through each user and create the isFollowed value using the isFollowed function.
And in my template I have,
%a{"ng-click" => "followUnfollowUser(user)"}
-# {{ user.isFollowed }}
{{ user.isFollowed ? "Unfollow user" : "Follow user"}}

Related

$rootScope:infdig error in angularjs 1.4.0

I have the following markup in a form mixed with some asp.net razor:
<div class="account-form__field-container" ng-show="postcodeRequired()" ng-cloak>
#Html.LabelFor(x => x.Postcode)
#Html.TextBoxFor(x => x.Postcode, new { #class = "account-form__field", placeholder = "Postcode here...", ng_required = "postcodeRequired()",ng_validpostcode="", ng_model = "postcode", ng_init = "postcode = '" + Model.Postcode + "'" })
#Html.ValidationMessageFor(x => x.Postcode, null, new { #class = "account-form__error-message" })
<span class="account-form__error-message" ng-show="registrationForm.$submitted && registrationForm.Postcode.$error.required" ng-cloak>
Please enter your postcode
</span>
<span class="account-form__error-message" ng-show="registrationForm.$submitted && !validPostCode()" ng-cloak>
Please enter valid postcode
</span>
</div>
I have a dropdown which will show hide the postcode field, so if uk selected the postcode field will show. The field is required but additionally I am doing a check in whether is a valid postcode via a webservice. The angular controller that deals with form submission looks like:
$scope.submitForm = function () {
$scope.registrationForm.$submitted = true;
if ($scope.enableSubmit()) {
registrationForm.submit();
}
};
$scope.postcodeRequired = function () {
return $scope.country === 'United Kingdom';
};
$scope.validPostCode = function () {
if ($scope.postcodeRequired()) {
if ($scope.postcode !== undefined && $scope.postcode.length > 5) {
postcodeService.ValidatePostCode($scope.postcode).success(function (response) {
return response;
});
} else {
return false;
}
}
return true;
};
$scope.enableSubmit = function () {
return $scope.registrationForm.$valid
&& $scope.passwordsMatch()
&& $scope.acceptTerms
&& $scope.validPostCode();
};
The postCodeService is just doing an http get to validate the post code that returns true or false. The issue i have is on submitting it validates the postcode but then goes into a loop and gives the following error:
angular.min.js:34 Uncaught Error: [$rootScope:infdig] http://errors.angularjs.org/1.4.0/$rootScope/infdig?p0=10&p1=%5B%5D
at angular.min.js:34
at m.$digest (angular.min.js:563)
at m.$apply (angular.min.js:571)
at l (angular.min.js:373)
at O (angular.min.js:388)
at XMLHttpRequest.N.onload (angular.min.js:392)
I have seen other people with this issue when doing an ng-repeat but as you can see I am not doing that.
Any ideas?
Without a plunkr to test against and verify its hard to tell exactly what is causing the infinite digest cycle loop. However I believe it might be cause by the amount of calls made towards your $scope.validPostCode function (which wasn't correctly returning its validity). Basically the change proposed is to only call the validate function when the postcode has been changed (trigged by ng-change on the field). The result of that function sets $scope.validPostCode variable to true or false, which is then what is checked for validity;
HTML (add ng-change to the input)
#Html.TextBoxFor(x => x.Postcode, new { <!-- other attributes -->, ng_change = "validatePostCode()" })
JavaScript
$scope.postcodeRequired = function () {
return $scope.country === 'United Kingdom';
};
// by default its not valid
$scope.validPostCode = false;
// our Validition check
$scope.validatePostCode = function () {
if ($scope.postcodeRequired()) {
if ($scope.postcode !== undefined && $scope.postcode.length > 5) {
postcodeService.ValidatePostCode($scope.postcode).success(function (response) {
$scope.validPostCode = response;
});
} else {
$scope.validPostCode = false;
}
} else {
$scope.validPostCode = true;
}
};
// call our function to properly set the initial validity state.
$scope.validatePostCode();
$scope.enableSubmit = function () {
return $scope.registrationForm.$valid
&& $scope.passwordsMatch()
&& $scope.acceptTerms
&& $scope.validPostCode;
};

Angular template won't load. Even with $loaded. Data resolves after Load

Using AngularFire, Angular, Firebase.
I load a list of users from a Firebase Database. I use $loaded to ensure it waits until data loads.
I take this list, compare it against another firebase database of groups and push the results into two arrays.
Based on the console.logs the data sorts correctly. However, inside my template I get a blank page (I think this is because the page loads before the data is sorted).
Thoughts?
let userLoggedIn = AuthFactory.getUser();
var allUsersArray = $firebaseArray(ConnectFactory.fbUserDb);
var x = firebase.database().ref('groups');
var friendArr = [];
var notFriendArr = [];
allUsersArray.$loaded().then(function(){
angular.forEach(allUsersArray, function(user, i) {
var haveIAdded = x.child(userLoggedIn).child(allUsersArray[i].uid).once('value').then(function (snap) {
if (snap.val() !== null) {
return true;
} else {
return false;
}
});
var haveTheyAdded = x.child(allUsersArray[i].uid).child(userLoggedIn).once('value').then(function (snap) {
if (snap.val() !== null) {
return true;
} else {
return false;
}
});
Promise.all([haveIAdded, haveTheyAdded]).then(function([you, they]) {
if (you && they) {
console.log('We Are Friends', allUsersArray[i]);
friendArr.push(allUsersArray[i]);
} else {
console.log('not a friend ', allUsersArray[i]);
notFriendArr.push(allUsersArray[i]);
}
});
});
$scope.friendList = friendArr;
$scope.notFriendList = notFriendArr;
});
Alright, this time I tried to actually read the question before attempting to answer. ;-)
When you set your $scope.friendList and $scope.notFriendList within the $loaded promise, your Promise.all may (and most likely) havn't resolved yet when those are called, since angular.forEach doesn't wait for the promises to finish before moving on to the next statement in the function. So you'll have to build an array of promises and wait for them all to resolve outside of the loop before attempting to set your $scope variables.
allUsersArray.$loaded().then(function(){
var promises = [];
var friendArr = [];
var notFriendArr = [];
angular.forEach(allUsersArray, function(user, i) {
... // Same as before
promises.push(
Promise.all([haveIAdded, haveTheyAdded]).then(function([you, they]) {
if (you && they) {
console.log('We Are Friends', allUsersArray[i]);
friendArr.push(allUsersArray[i]);
} else {
console.log('not a friend ', allUsersArray[i]);
notFriendArr.push(allUsersArray[i]);
}
})
);
});
Promise.all(promises).then(function(){
$scope.friendList = friendArr;
$scope.notFriendList = notFriendArr;
});
});

Compare two set of objects

I have scope called $scope.users which returns all the users in my app like so,
[{"id":1,"name":"Peter Boomsma"},
{"id":2,"name":"Jan Jansen"},
{"id":3,"name":"Kees Keesen"},
{"id":4,"name":"Piet Pietersen"}]
Then I have a scope called $scope.current_user which returns the current users, and the id's of the users he's following,
{"id":4,"name":"Piet Pietersen","following":[
{"id":1},{"id":2},{"id":4}]
}
I have a ng-repeat that shows all the users, and a followUser action per user,
%ul{"ng-repeat" => "user in users"}
%li
name: {{ user.name }}
%a{"ng-click" => "followUser(user)"} Follow user.
The problem is that I can't differentiate between a user that's already being followed, and a user that's not being followed. So the action followUser is always there. I would like to give the users the current user is following a different action, such as unfollowUser.
I got the id's of the users that the current user is following, but I don't know how to give those users a other button. Something like unfollowUser.
* update *
I've got it working (somewhat at least), by using Mihail his suggestion,
When I go to my user template I load the userCtrl,
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
angular.forEach(response, function(user){
$scope.user = user
$scope.isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i=0; i<following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
})
})
And in my template I have,
%ul{"ng-repeat" => "user in users"}
%li
name: {{ user.name }}
%a{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow
%a{"ng-click" => "unfollowUser(user)", "ng-show" => "isFollowed(user.id)"} unfollow
This works fine. When a user is followed then the unfollowUser function is used. The problem is that when I unfollow the user the view doesn't get updated. I have to refresh the page to see the effect of the unfollow action.
I've tried putting a init at the end of the unfollowUser action like so,
$scope.unfollowUser = function(user){
unfollowFriend.unfollowFriend(user).then(function(){
},function(){
}).then(init);
Notification.success(user.name + ' is verwijderd als vriend.');
}
The init,
var init = function(){
console.log ('renew')
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
angular.forEach(response, function(user){
$scope.user = user
$scope.isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i=0; i<following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
})
})
}
I get the renew message in my browser log, but the view isn't updated.
I am not good at haml, so I will write a simple html example:
<div ng-repeat="user in users">
<div ng-show="isFollowed(user.id)" ng-click="unfollowUser(user)">unfollow</div>
<div ng-show="!isFollowed(user.id)" ng-click="followUser(user)">follow</div>
</div>
$scope.isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i=0; i<following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
You can make a function that check if a given user is already followed like:
$scope.isFollowed = function (userID) {
//check if the userID is in$scope.current_user.following
//return a boolean
}
then in the HTML code you can check based on this function if to show followUser or unfollowUser.

Joining data between paths based on id using AngularFire

I am currently working on an app using firebase and angularJS (ionic). Basically this is a car management app, so you have people sharing their cars with others. I tried to structure the data as flat as possible to be efficient. My issue here is that if without problem I can display the list of the car_id of the different cars shared with the logged user, I can't find a way to display the list of cars shared with the user displaying the year and the model.
Thank you in advance for your help !
{
"rules": {
"users": {
".write": true,
"$uid": {
".read": "auth != null && auth.uid == $uid"
},
"cars": {
"car_id":true,
"role":true // Owner, borower...
}
},
"cars": {
"car_id":true,
"model":true,
"year":true
}
}
}
carapp.controller("carsController", function($scope, $firebaseObject, $ionicPopup, $ionicHistory) {
$ionicHistory.clearHistory();
$scope.list = function() {
frbAuth = frb.getAuth();
if(frbAuth) {
var userObject = $firebaseObject(frb.child("users/" + frbAuth.uid));
userObject.$bindTo($scope, "user");
$scope.cars = frb.child("cars");
}}
$scope.createCar = function() {
$ionicPopup.prompt({
model: 'Create a new car',
inputType: 'text'
})
.then(function(result) {
if(result !== "") {
var newCar = $scope.cars.push({
model: result
})
var newCarId = newCar.key();
$scope.user.cars.push({car_id: newCarId, role: "owner" });
} else {
console.log("Action not completed");
}
});
}
});
<div class="list">
<a ng-repeat="car in user.cars" >
<h2>{{car.car_id}}</h2> ----> works fine !
<h2>{{car.model}}</h2> ----> How to get this working ?
<h2>{{car.year}}</h2> ----> How to get this working ?
</a>
</div>
In the users/ path, begin by storing the list of cars by index, instead of in an array. So your structure would be:
{
"users": {
"kato": {
"cars": {
"DeLorean": true
}
}
},
"cars": {
"DeLorean": {
model: "DeLorean",
year: "1975"
}
}
}
To join this using AngularFire, you have several approaches available. An AngularFire-only solution might look like this, taking advantage of $extend:
app.factory('CarsByUser', function($firebaseArray) {
return $firebaseArray.$extend({
$$added: function(snap) {
return new Car(snap);
},
$$updated: function(snap) {
// nothing to do here; the value of the index is not used
},
$$removed: function(snap) {
this.$getRecord(snap.key()).destroy();
},
// these could be implemented in a manner consistent with the
// use case and above code, for simplicity, they are disabled here
$add: readOnly,
$save: readOnly
});
var carsRef = new Firebase(...).child('cars');
function Car(snap) {
// create a reference to the data for a specific car
this.$id = snap.key();
this.ref = carsRef.child(this.$id);
// listen for changes to the data
this.ref.on('value', this.updated, this);
}
Car.prototype.updated = function(snap) {
this.model = data.model;
this.year = data.year;
}
Car.prototype.destroy = function() {
this.ref.off('value', this.meta, this);
};
function readOnly() { throw new Error('This is a read only list'); }
});
app.controller('...', function($scope, CarsByUser, authData) {
// authenticate first, preferably with resolve
var ref = new Firebase(...).child(authData.uid);
$scope.cars = CarsByUser($scope);
});
For a more sophisticated and elegant approach, one could utilize NormalizedCollection and pass that ref into the AngularFire array:
app.controller('...', function($scope, $firebaseArray) {
var ref = new Firebase(...);
var nc = new Firebase.util.NormalizedCollection(
ref.child('users/' + authData.uid),
ref.child('cars')
)
.select('cars.model', 'cars.year')
.ref();
$scope.cars = $firebaseArray(nc);
});

accessing items in firebase

I'm trying to learn firebase/angularjs by extending an app to use firebase as the backend.
My forge looks like this
.
In my program I have binded firebaseio.com/projects to $scope.projects.
How do I access the children?
Why doesn't $scope.projects.getIndex() return the keys to the children?
I know the items are in $scope.projects because I can see them if I do console.log($scope.projects)
app.js
angular.module('todo', ['ionic', 'firebase'])
/**
* The Projects factory handles saving and loading projects
* from localStorage, and also lets us save and load the
* last active project index.
*/
.factory('Projects', function() {
return {
all: function () {
var projectString = window.localStorage['projects'];
if(projectString) {
return angular.fromJson(projectString);
}
return [];
},
// just saves all the projects everytime
save: function(projects) {
window.localStorage['projects'] = angular.toJson(projects);
},
newProject: function(projectTitle) {
// Add a new project
return {
title: projectTitle,
tasks: []
};
},
getLastActiveIndex: function () {
return parseInt(window.localStorage['lastActiveProject']) || 0;
},
setLastActiveIndex: function (index) {
window.localStorage['lastActiveProject'] = index;
}
}
})
.controller('TodoCtrl', function($scope, $timeout, $ionicModal, Projects, $firebase) {
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
var keys = $scope.projects.$getIndex();
console.log($scope.projects.$child('-JGTmBu4aeToOSGmgCo1'));
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("" + keys[0]);
});
// A utility function for creating a new project
// with the given projectTitle
var createProject = function(projectTitle) {
var newProject = Projects.newProject(projectTitle);
$scope.projects.$add(newProject);
Projects.save($scope.projects);
$scope.selectProject(newProject, $scope.projects.length-1);
};
// Called to create a new project
$scope.newProject = function() {
var projectTitle = prompt('Project name');
if(projectTitle) {
createProject(projectTitle);
}
};
// Called to select the given project
$scope.selectProject = function(project, index) {
$scope.activeProject = project;
Projects.setLastActiveIndex(index);
$scope.sideMenuController.close();
};
// Create our modal
$ionicModal.fromTemplateUrl('new-task.html', function(modal) {
$scope.taskModal = modal;
}, {
scope: $scope
});
$scope.createTask = function(task) {
if(!$scope.activeProject || !task) {
return;
}
console.log($scope.activeProject.task);
$scope.activeProject.task.$add({
title: task.title
});
$scope.taskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
task.title = "";
};
$scope.newTask = function() {
$scope.taskModal.show();
};
$scope.closeNewTask = function() {
$scope.taskModal.hide();
};
$scope.toggleProjects = function() {
$scope.sideMenuController.toggleLeft();
};
// Try to create the first project, make sure to defer
// this by using $timeout so everything is initialized
// properly
$timeout(function() {
if($scope.projects.length == 0) {
while(true) {
var projectTitle = prompt('Your first project title:');
if(projectTitle) {
createProject(projectTitle);
break;
}
}
}
});
});
I'm interested in the objects at the bottom
console.log($scope.projects)
Update
After digging around it seems I may be accessing the data incorrectly. https://www.firebase.com/docs/reading-data.html
Here's my new approach
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
console.log(snapshot.val()['-JGTdgGAfq7dqBpSk2ls']);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
I'm still not sure how to traverse the keys programmatically but I feel I'm getting close
It's an object containing more objects, loop it with for in:
for (var key in $scope.projects) {
if ($scope.projects.hasOwnProperty(key)) {
console.log("The key is: " + key);
console.log("The value is: " + $scope.projects[key]);
}
}
ok so val() returns an object. In order to traverse all the children of projects I do
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
var keys = Object.keys(snapshot.val());
console.log(snapshot.val()[keys[0]]);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
Note the var keys = Object.keys() gets all the keys at firebaseio.com/projects then you can get the first child by doing snapshot.val()[keys[0])

Resources