angularjs ng-show not displaying as expected - angularjs

I'm probably getting confused with mvc and angularjs and trying to set a boolean to control a scope variable to hide a div.
I have a list html page that includes this:
<tbody>{{isAuthorised}}
<tr ng-repeat="calendarEvent in items" id="event_{{calendarEvent.Id}}">
<td><strong>{{calendarEvent.EventTitle}}</strong><br/>{{calendarEvent.EventDescription}}</td>
<td>{{calendarEvent.EventDate | date:mediumDate}}</td>
<td><img src="{{calendarEvent.ThumbnailUrl}}" alt="" width="100" /></td>
<td>
<div ng-show="isAuthorised">
<i class="glyphicon glyphicon-edit"></i>
<a ng-click="delete()"><i class="glyphicon glyphicon-remove"></i></a>
</div>
</td>
</tr>
</tbody>
I'm outputting the value currently to try to figure out what is going on. So if I hit this page with setting the value the div shows my edit and delete buttons which I don't want. The value of the scope variable displays as {}.
I have this app.js code:
var ListCtrl = function ($scope, $location, CalendarEvent, SharedService) {
** lots of stuff removed as irrelevant **
$scope.isAuthorised = SharedService.get();
};
My login controller via a separate html content section that is setting the value (in the shared service)
var LoginCtrl = function ($scope, $location, $http, SharedService) {
$scope.login = function () {
$http.get("/AuthorisedUser/IsValidUser/" + $scope.item.ValidEmailAddress + "/")
.success(function (result) {
var isAuthorised = result.toLowerCase();
if (isAuthorised) {
SharedService.set(isAuthorised);
$location.path('/');
} else {
alert('you do not have the power!');
}
})
.error(function() {
alert('Email could not be Validated at this time');
});
}
};
the result is an MVC method returning a bool type. I thought maybe I needed to convert the bool to lower case because javascript would like it better, but maybe that's doing some implicit conversion to a string or something?! I'm not sure what I need to change in my list html to properly show that div only when the value is true. I'm coming from a .NET background with limited AngularJS understanding.
The value seems to being set, because if I put in a valid email address I'm seeing
true
in the html page where the scope variable is.
It seemed to work once in Chrome - but now that's not working, and just showing the stuff that should be hidden.
Sorry forgot to include the shared service:
EventsCalendarApp.factory('SharedService', function() {
var savedData = {}
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});

I think everything would be simplified in your controller, service, and UI if your service dealt with object references rather than a Boolean value (which is a primitive).
Your service:
EventsCalendarApp.factory('SharedService', function() {
var savedData = { isAuthorised: false }
function set(data) {
// overwrites savedData properties with data's properties,
// but preserves the reference
angular.copy(data, savedData);
}
function setAuthorised(authorised) {
savedData.isAuthorised = authorised;
}
function get() {
return savedData;
}
return {
set: set,
get: get,
setAuthorised: setAuthorised
}
});
Your Login controller:
var LoginCtrl = function ($scope, $location, $http, SharedService) {
// helper function to determine if str contains 'true'
function parseBoolean(str) {
return /^true$/i.test(str);
}
$scope.login = function () {
$http.get("/AuthorisedUser/IsValidUser/" + $scope.item.ValidEmailAddress + "/")
.success(function (result) {
var isAuthorised = parseBoolean(result);
if (isAuthorised) {
SharedService.set({ isAuthorised: isAuthorised });
// OR
SharedService.setAuthorised(isAuthorised);
$location.path('/');
} else {
alert('you do not have the power!');
}
})
.error(function() {
alert('Email could not be Validated at this time');
});
}
};
Your List Controller:
var ListCtrl = function ($scope, $location, CalendarEvent, SharedService) {
** lots of stuff removed as irrelevant **
$scope.savedData = SharedService.get();
};
HTML:
<tbody>{{savedData.isAuthorised}}
<tr ng-repeat="calendarEvent in items" id="event_{{calendarEvent.Id}}">
<td><strong>{{calendarEvent.EventTitle}}</strong><br/>{{calendarEvent.EventDescription}}</td>
<td>{{calendarEvent.EventDate | date:mediumDate}}</td>
<td><img ng-src="{{calendarEvent.ThumbnailUrl}}" alt="" width="100" /></td>
<td>
<div ng-show="savedData.isAuthorised">
<i class="glyphicon glyphicon-edit"></i>
<a ng-click="delete()"><i class="glyphicon glyphicon-remove"></i></a>
</div>
</td>
</tr>
</tbody>
When you use object references, then any changes to the reference from within your service is automatically propagated to the views; as do any changes to the reference that happen inside a controller. There is no real magic behind this - they are automatically updated because they are the same reference. In contrast, when you use primitives, then a copy of the value is passed around, and it becomes more challenging to keep them all in synch.
NOTE: on an unrelated note, you should use ng-src for image URLs that are binding expressions. This ensures that the image URL is only downloaded by the browser after the expression is evaluated and rendered.

var LoginCtrl = function ($scope, $location, $http, SharedService) {
$scope.login = function () {
$http.get("/AuthorisedUser/IsValidUser/" + $scope.item.ValidEmailAddress + "/")
.success(function (result) {
$scope.isAuthorised = result.toLowerCase();
})
.error(function() {
alert('Email could not be Validated at this time');
});
}
};
Keep one thing in mind you $scope works as a bridge between controller and view. if your controller update $scope, your view gets changed.
Don't use sharedservice here. its useless for what you want to do. try my above snippet.

So the answer was to update the ListCtrl to have this logic:
var ListCtrl = function ($scope, $location, CalendarEvent, SharedService) {
var authorised = SharedService.get();
if (authorised != "true")
$scope.isAuthorised = false;
else
$scope.isAuthorised = SharedService.get();
};
It now seems to be working! I'm still confused about the handling of booleans in javascript as I seem to have a mix of boolean and string going on in the various methods.

Related

Changing expression provided to ngShow attribute using controller

I am using ng-show and ng-hide to display/hide content. I would like to change the showme status from true to false within the controller. But when I use the code below, it doesn't work. I'm using the Controller As syntax. Any suggestions on how to get this working right?
HTML:
<h1 ng-show="showme">Confirm Order</h1>
<h4 ng-hide="showme">Contact Information</h4>
Javascript:
.controller('ContactFormCtrl',
function ($http, serviceF, $scope) {
var contactForm = this;
$scope.$watch(serviceF.get, function(valid)
{
if (valid === 'yes') {
contactForm.showme=true;
}
else
{
contactForm.showme=false;
}
});
});
Service:
.service('serviceF', function() {
var valid = 'true';
return {
get: function () {
return valid;
},
set: function (value) {
valid = value;
}
};
UI Router:
.state('payment', {
url: '/payment',
views: {
// . . .
'top': {
templateUrl: 'views/clientinfo.html',
controller: 'ContactFormCtrl as contactForm'
// . . .
}
})
I'm not sure what you're trying to do, but the Controller As syntax goes this way in HTML:
<div ng-controller="ContactFormCtrl as contactForm">
<h1 ng-show="contactForm.showme">Confirm Order</h1>
<h1 ng-show="contactForm.showme">Confirm Order</h1>
</div>
Note the 'as contactForm' thingy passed in the ng-controller directive
Now you know that showme is actually a property of contactForm which is essentially an "alias" of the ContactFormCtrl controller
From there, whenever the showme property changes in the controller, the view will behave accordingly.
// In your controller
var contactForm = this; // aliasing this
contactForm.showme = true; //or false
UPDATE:
Since you're using ui-router, you should be good without ng-controller in your view. I'm noticing you are not passing $scope to your controller, that could be a reason why $scope.$watch isn't working, thus not updating the view.
.controller('ContactFormCtrl', function ($scope, $http, serviceF) {
var contactForm = this;
$scope.$watch(serviceF.get, function(valid) {
if (valid === 'yes') {
contactForm.showme = true;
}else{
contactForm.showme = false;
}
});
});

$scope is not binding data in view

I've been developing an e-commerce website and I am stuck at a point. I am using Stripe payment and all is working fine except data biding after token creation. Here is my controller
app.controller('shoppingCartController', ['$scope', '$http', '$sce', 'stripe', '$window', function ($scope, $http, $sce, stripe, $window) {
$window.Stripe.setPublishableKey('pk_test_saiYYlyCNgO2yZq6Mu******');
$scope.createToken = function () {
var expire = $scope.master[0].expire.split('/');
if ($scope.userDetail.$valid === true) {
$window.Stripe.card.createToken({
number: $scope.master[0].card,
cvc: $scope.master[0].cvv,
exp_month: expire[0],
exp_year: expire[1],
}, $scope.makepayment);
}
}
$scope.makepayment = function (status, response) {
if (response.error) {
$scope.handleStripeCallback(response);
} else {
// response contains id and card, which contains additional card details
var data = {token: response.id, data: $scope.cartData};
$http.post('make_payment', data).success(function (data) {
if (data.status) {
$scope.stripePaymentMessage = data.message;
$scope.stripePaymentMessageClass = "success";
} else {
$scope.stripePaymentMessage = data.message;
$scope.stripePaymentMessageClass = "danger";
}
})
}
}
$scope.handleStripeCallback = function (response) {
//alert(response.error.message);
$scope.stripChargeRequest = true;
$scope.stripePaymentMessage = response.error.message;
$scope.stripePaymentMessageClass = "danger";
}
}]);
In my view I am trying to handle error or success message with this code
<div ng-show="stripChargeRequest ">
<div class="alert alert-{{stripePaymentMessageClass}}" role="alert" >
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
{{stripePaymentMessage}}
</div>
</div>
My question is: stripChargeRequest angular variable contain true / false with normal scope binding but when I am trying to create a token by calling $scope.createToken() it not works. I guess I am forgetting something in calling callback function $scope.makepayment(). Fortunately it is working in controller's scope. I can see error after stripe request in controller but it is not showing in view. Please suggest me the proper way of doing that. Thanks in advance.
The stripe callbacks are outside of angular so you need to use $apply to tell angular whenever you update the scope so it can run a digest to update the view
Example:
$scope.handleStripeCallback = function (response) {
//alert(response.error.message);
$scope.stripChargeRequest = true;
$scope.stripePaymentMessage = response.error.message;
$scope.stripePaymentMessageClass = "danger";
$scope.$apply(); // tell angular to update view
}

AngularJS - $rootScope.$apply not causing to refresh ng-model directives fed from descendant scopes

I have a function which is wrapped in $rootScope.$apply() by the wrapping call (SDK.api):
SDK.api('/me', function(response) {
//THIS callback, anonymous function, is wrapped by SDK in $rootScope.$apply
$scope.form = {
'first_name' : response.first_name,
'last_name' : response.last_name,
'email' : response.email
};
$scope.formEnabled = true;
$scope.formFetching = false;
});
Actually, this is a wrapper to Facebook JS SDK (and NOT the plain FB object) and such function is called in the context of $rootScope.$apply(). I can ensure that because using $scope.$apply() at the end of such function raises an "inprog" error (i.e. I cannot call $apply inside a call to $apply).
This $scope object in the code (and so: the code chunk I wrote here) belongs to a controller I created for an ngDialog plugin. The ng-dialog looks like this:
return ngDialog.open({
template: url + 'partials/dialog-form.html',
className: 'ngdialog-theme-plain',
scope: $scope,
closeByDocument: true,
closeByEscape: true,
showClose: true,
cache: false,
controller: ['$scope', '$http', function($scope, $http) {
/* ... more ... */
$scope.formFetching = true;
$scope.formEnabled = false;
$scope.success = false;
SDK.api('/me', function(response) {
$scope.form = {
'first_name' : response.first_name,
'last_name' : response.last_name,
'email' : response.email
};
$scope.formEnabled = true;
$scope.formFetching = false;
});
/* ... more ... */
}]
})
and the $scope in scope: $scope is the scope from the main controller (My app has only one controller - it's not too big).
So we could say: $rootScope is parent of $scope in main controller, which at the same time is parent of $scope of the ngDialog's $scope.
In the grandchild $scope, the form data is updated:
$scope.form = {
'first_name' : response.first_name,
'last_name' : response.last_name,
'email' : response.email
};
And there's the template url + 'partials/dialog-form.html' which actually exists and gets rendered. The content is as follows (I will omit irrelevant code):
<div id="pedido">
<form novalidate ng-submit="submitForm()">
<!-- more code -->
<table width="100%">
<!-- more code -->
<tbody>
<tr>
<td>Nombre:</td>
<td>
<input type="text" ng-model="form.first_name" />
<span ng-repeat="error in errors.first_name" class="error">{{ error }}</span>
</td>
</tr>
<tr>
<td>Apellido:</td>
<td>
<input type="text" ng-model="form.last_name" />
<span ng-repeat="error in errors.last_name" class="error">{{ error }}</span>
</td>
</tr>
<tr>
<td>Correo electrónico:</td>
<td>
<input type="text" ng-model="form.email" />
<span ng-repeat="error in errors.email" class="error">{{ error }}</span>
</td>
</tr>
<!-- more code -->
</tbody>
</table>
<!-- more code -->
</form>
</div>
Assume the value for ng-submit, ng-repeat exist.
My issue: fields with ng-model are not being populated from $scope.form.
My question: what am I doing wrong? The form works as it should, and the data in the server side is received as it should. My only pain in the *** is that these fields are not being reflected when $rootScope.$apply is called - I need such fields prepopulated from Facebook (I have no issue retrieving such data from Facebook: I can be sure the data arrives if I log it with $window.console.log).
Edit Appendix: API Call
var SDK = function($scope) {
this.$scope = $scope;
this._initialized = false;
this._calls = [];
};
/* ... */
SDK.prototype.api = function(path, method, params, callback) {
var c = this;
this._makeCall(function(){
FB.api(
c.wrap(path),
c.wrap(method),
c.wrap(params),
c.wrap(callback)
);
});
};
/* ... */
SDK.prototype.wrap = function(call) {
var c = this;
return (typeof call !== 'function') ? call : function(){
c.$scope.$apply(call);
};
};
/* ... */
FBModule.factory('AngularFB.SDK', ['$rootScope', sdk]);
I found the error. It was not related to $rootScope.$apply.
A wrapped function with this:
SDK.prototype.wrap = function(call) {
var c = this;
return (typeof call !== 'function') ? call : function(){
c.$scope.$apply(call);
};
};
Did not proxy any parameter. call was passed to $apply, which passes the first parameter being the $rootScope itself. So I had to use "another layer" of closuring, and passing explicitly any param received by the wrapping function:
SDK.prototype.wrap = function(call) {
var c = this;
return (typeof call !== 'function') ? call : function(){
/* copy the params into a new object */
var args = [];
angular.forEach(arguments, function(argument) {
args.push(c.wrap(argument));
});
/* the applied call takes the params and creates a 0-arity function to be applied, which takes the task of calling the target function with the passed params */
c.$scope.$apply(function(){
call.apply(null, args);
});
};
};
So -as I supposed- nothing was wrong with Angular, but took a lot to figure that the passed param was not the expected one.
Solution: proxy the call ensuring it is bridging the passed parameters.
I'm not sure if this will work, but try creating the form object before the api call.
$scope.success = false;
$scope.form = {}; //try creating the object here
SDK.api('/me', function(response) {
$scope.$apply(function () {
//and update it here
$scope.form.first_name = response.first_name;
$scope.form.last_name = response.last_name;
$scope.form.email = response.email;
$scope.formEnabled = true;
$scope.formFetching = false;
});
});

Angular Two way databinding and http.post issue

Objective: Two Way Databinding between database and view via scope and controller
I’m trying to post to a restful database using angular
When I click on the thumbs up or thumbs down the scope changes o.k and is reflected in the view
However how can this placed in real time to a restful database using http post ?
Here’s the HTML
<div ng-controller="ordersCtrl">
<div class="span0 well votingWidget">
<div class="votingButton" ng-click="upVoteOrder(order)">
<i class="icon-thumbs-up "></i>
</div>
<div class="badge ">
<div>{{order.upVoteCount}}</div>
</div>
<div class="votingButton" ng-click="downVoteOrder(order)">
<i class="icon-thumbs-down"></i>
</div>
Heres the Controller: My issue lies here in the http.post command
.controller("ordersCtrl", function ($scope, $http, ordersUrl) {
$scope.downVoteOrder = function(order) {
$scope.selectedOrder = order;
order.upVoteCount--;
$http.post(orderUrl, order.upVoteCount)
.success(function (data) {
$scope.data.orderupVoteCount = data.id;
})
};
});
Note : I can post form data to the restful database successfully using the following code
$scope.sendOrder = function (shippingDetails) {
var order = angular.copy(shippingDetails);
order.products = cart.getProducts();
$http.post(orderUrl, order)
.success(function (data) {
$scope.data.orderId = data.id;
cart.getProducts().length = 0;
})
.error(function (error) {
$scope.data.orderError = error;
}).finally(function () {
$location.path("/uploaded");
});
}
you should use a separate service to handle the http post .
var app = angular.module("myApp", []);
app.factory("OrderService", ["$http", function ($http) {
this.PostDownVote = function(orderUrl, upVoteCount) {
return $http.post(orderUrl, upVoteCount);
}
this.PostOrder = function(orderUrl, order) {
// do something
}
return this;
}]);
Now inject this OrderService to you controller and use it.
app.controller("ordersCtrl", function ($scope, $http, ordersUrl, OrderService) {
$scope.downVoteOrder = function(order) {
$scope.selectedOrder = order;
order.upVoteCount--;
OrderService.PostDownVote(ordersUrl, order.upVoteCount)
.success(data) {// do something}
.error(data) {// do something}
}
});

using ng-include for a template that has directives that use data retrieved by XHR

I am using ng-include in order to include a persistent menu, that exists in all of the views of my SPA.
The problem is that I want to display different options and content in this menu per each user type(admin, guest, user etc.), and this requires the service function authService.loadCurrentUser to be resolved first.
For the purpose of managing this content easily and comfortably, I have created a simple directive, that takes an attribute with the required access level, and at the compile phase
of the element, if the permissions of the given user are not sufficient, removes the element and it's children.
So after failing miserably at trying to make the ng-include go through the routeProvider function, I've tried to use ng-init, but nothing seems to work, the user role remain undefined at the time that I am logging it out.
I am thinking about trying a new approach, and making the entire menu a directive that includes the template that is suitable for each user type, but first I would like to try and solve this matter.
Directive:
'use strict';
/* Directives */
angular.module('myApp.directives', []).
directive('restrict', function(authService){
return{
restrict: 'A',
prioriry: 100000,
scope: {
// : '#'
},
link: function(){
// alert('ergo sum!');
},
compile: function(element, attr, linker){
var user = authService.getUser();
if(user.role != attr.access){
console.log(attr.access);
console.log(user.role);//Always returns undefined!
element.children().remove();
element.remove();
}
}
}
});
Service:
'use strict';
/* Services */
angular.module('myApp.services', []).
factory('authService', function ($http, $q) {
var authServ = {};
var that = this;
that.currentUser = {};
authServ.authUser = function () {
return $http.head('/users/me', {
withCredentials: true
});
},
authServ.getUser = function () {
return that.currentUser;
},
authServ.setCompany = function (companyId) {
that.currentUser.company = companyId;
},
authServ.loadCurrentUser = function () {
var defer = $q.defer();
$http.get('/users/me', {
withCredentials: true
}).
success(function (data, status, headers, config) {
console.log(data);
that.currentUser.company = {};
that.currentUser.company.id = that.currentUser.company.id ? that.currentUser.company.id : data.main_company;
that.currentUser.companies = [];
for (var i in data.roles) {
that.currentUser.companies[data.roles[i]['company']] = data.roles[i]['company_name'];
if (data.roles[i]['company'] == that.currentUser.company.id){
that.currentUser.role = data.roles[i]['role_type'];
that.currentUser.company.name = data.roles[i]['company_name'];
// console.log(that.currentUser.role);
}
}
// defer.resolve(data);
defer.resolve();
}).
error(function (data, status, headers, config) {
that.currentUser.role = 'guest';
that.currentUser.company = 1;
defer.reject("reject");
});
return defer.promise;
}
return authServ;
});
Menu controller:
angular.module('myApp.controllers', []).
controller('menuCtrl', function($scope, $route, $location, authService){
//TODO: Check if this assignment should be local to each $scope func in order to be compliant with 2-way data binding
$scope.user = authService.getUser();
console.log($scope.user);
// $scope.companies = $scope.user.companies;
$scope.companyOpts = function(){
// var user = authService.getUser();
if(typeof $scope.user.company == 'undefined')
return;
var companies = [];
companies[$scope.user.company.id] = $scope.user.company.name;
for(var i in $scope.user.companies){
if(i != $scope.user.company.id){
companies[i] = $scope.user.companies[i];
}
}
// console.log(companies);
// if(nonCurrentComapnies.length > 0){
console.log(companies);
return companies;
// }
}
$scope.$watch('user.company.name', function(company){
for(var i in $scope.user.companies)
if(company == $scope.user.companies[i].id)
authService.setCompany(i);
});
$scope.$watch(function(){return authService.getUser().company; }, function(company){
//Refresh the page on company change here, first time, and each time the user changes the select
// $scope.companyOpts();
// $scope.currentComapany = company;
})
;})
Main SPA HTML page:
<div ng-init="authservice.loadCurrentUser" ng-include src="'partials/menu.html'"></div>
menu element that should be visible only to the admin:
<ul class="left" restrict access="admin">
<li>You are the admin!</li>
</ul>
Thanks in advance for any assistance!
I personally would do the "reverse" way. Which mean: I will add the menu in when the user role is "admin", or "user", etc...
This way, you can do something like this in the "restrict" directive:
...
var roleListener = $scope.$watch('user.role', function (newVal, oldVal) {
if (newVal == $scope.access) {
// add the menu items
// supposed that loadcurrentuser be called only once
// we should clear the watch
roleListener();
} else {
// personally, I would remove the item here too
// so the menu would be added or removed when user.role update
}
});
...
One more thing, for just display menu base on the user role, you can use ngSwitch, something like this:
<ul class="left" ng-switch="user.role">
<li ng-switch-when="admin">You are the admin!</li>
<li ng-switch-when="user">You are the user!</li>
<li ng-switch-default><img src="some-thing-running.gif"/>Your menu is loading, please wait...</li>
</ul>
And let the magical AngularJS binding render up the menus for you!
The call to authServ.getUser should also return a promise by calling internally
authServ.loadCurrentUser
which should be modified a bit to check if the user context exists to avoid making another API call and always returning resolve with the user context:
defer.resolve(that.currentUser);
Loading the user context should also be done early on as this enables the authorization of the app. The app.run function can be used for this purpose.
hope it helps others.

Resources