Angular Basic Factory Binding not updating Controllers - angularjs

I'm trying to create an SPA website where I have multiple controllers that depend on an AuthService, this service is just in charge of telling all the controllers if the User is signedIn and who the user is. AFAIK since all the controllers share the same service object, the changes should propagate to them, but so far this is not happening.
My code goes as follows:
I have a NavbarController and a LoginController. The NavbarController is in charge of displaying the Login/Logout links depending on the Logged property of the AuthService. The LoginController is in charge of the login panel and calling the login on the server through the AuthService.
AuthService
(function () {
var auth = angular.module('Auth', []);
auth.factory('AuthService', ['$http', function ($http) {
var obj = {};
obj.User = {};
obj.Logged = true;
obj.GetUser = function () {
$http.get('api/Account/UserDetails')
.success(function (data) {
obj.User = data;
obj.Logged = true;
})
}
obj.Login = function (data) {
var func = $http.post('api/Account/Login', data);
func.success(function (data) {
if (data.StatusCode == 200) {
obj.GetUser();
}
});
return func;
}
obj.Logoff = function () {
$http.get('api/Account/Logoff')
.success(function () {
obj.User = {};
obj.Logged = false;
});
}
return obj;
}]);
})();
LoginController
(function () {
var login = angular.module('Login', ['LoginService','Auth']);
login.controller('LoginController', ['accountService','AuthService', function (accountService,authService) {
this.OnFocus = function(obj)
{
$(obj.target).closest(".textbox-wrap").addClass("focused");
}
this.OnBlur = function(obj)
{
$(obj.target).closest(".textbox-wrap").removeClass("focused");
}
this.loginCredentials = {};
this.Login = function () {
authService.Login(this.loginCredentials)
.success(function (data) {
alert(data.StatusCode);
})
.error(function () {
alert("error");
});
}
}]);
login.directive('hcCheckbox', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.iCheck({
checkboxClass: 'icheckbox_square-blue',
increaseArea: '20%' // optional
});
}
}
});
})();
NavbarController
(function () {
var app = angular.module('main');
app.controller('NavbarController', ['AuthService', function (AuthService) {
var self = this;
self.Logged = AuthService.Logged;
self.User = AuthService.User;
self.Logoff = function()
{
AuthService.Logoff();
}
}]);
})();
Navbar html template
<nav class="navbar navbar-default" ng-controller="NavbarController as NavbarCtrl">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="navegacion">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">HACSYS</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="navegacion">
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>Details</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li ng-hide="NavbarCtrl.Logged">
Login
</li>
<li ng-show="NavbarCtrl.Logged">
<a ng-click="NavbarCtrl.Logoff()" href=""> Logoff</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
I know I could use $watch to propagate changes to other controllers, but this way seems cleaner to me, I have seen other examples where this works, and I still can't figure out whats going wrong with my code

In order to ensure that changes are propagated correctly across all the nested scopes, you need to bind to an object by reference. If you bind to a primitive, it creates a copy of the value on scope, effectively breaking the linkage between the scope variables. To resolve this, bind by reference:
app.controller('NavbarController', ['AuthService', '$scope', function (AuthService, $scope) {
var self = this;
$scope.AuthService = AuthService;
self.Logoff = function()
{
$scope.AuthService.Logoff();
}
}]);
When you bind by reference, any controller where you inject AuthService will bind and propagate as expected.
app.controller('otherController', function($scope, AuthService) {
$scope.AuthService = AuthService;
});
HTML
<div ng-controller="otherController">
<div ng-show="AuthService.Logged">
User is logged in!
</div>
</div>

Related

Closing uibModal automatically after array length equals zero

Our team is developing an attachment widget in ServiceNow. The widget allows a user to attach documents, view them, and delete them as necessary. In order to view the attachments, we're leveraging modal windows through uibModal. If a user deletes all of the attachments and the array length reaches zero, we want the modal window to close automatically, but we can't seem to get that working.
<label>
<sp-attachment-button></sp-attachment-button>
</label>
<span ng-if="attachments.length>0" class="badge" ng-click="c.openModal()">{{attachments.length}}</span>
<script type="text/ng-template" id="modalTemplate">
<div class="panel panel-default">
<div class="panel-heading flex">
<h4 class="panel-title">Attachments</h4>
<i type="button" class="fa fa-times" style="margin-left:auto;" ng-click="c.closeModal()"></i>
</div>
<div class="panel-body">
<now-attachments-list template="sp_attachment_single_line"></now-attachments-list>
</div>
<!--<div class="panel-footer text-right">
<button class="btn btn-primary" ng-click="c.closeModal()">${Close Modal}</button>
</div>-->
</div>
</script>
Our controller looks like this:
function ($uibModal, spModal, cabrillo, $scope, $http, spUtil, nowAttachmentHandler, $rootScope, $sanitize, $window, $sce) {
var c = this;
$scope.attachments=[];
$scope.m = $scope.data.msgs;
$scope.submitButtonMsg = $scope.m.submitMsg;
if(c.options.case_sysid){
$scope.data._attachmentGUID = c.options.case_sysid;
}
var ah = $scope.attachmentHandler = new nowAttachmentHandler(setAttachments, function() {});
ah.setParams('sn_hr_core_case_workforce_admin', $scope.data._attachmentGUID, 1024 * 1024 * 24);
function setAttachments(attachments, action) {
$scope.attachments = attachments;
}
$scope.attachmentHandler.getAttachmentList();
$scope.confirmDeleteAttachment = function(attachment) {
if (cabrillo.isNative()) {
if (confirm($scope.data.msgs.delete_attachment)) {
$scope.attachmentHandler.deleteAttachment(attachment);
}
} else {
spModal.confirm($scope.data.msgs.delete_attachment).then(function() {
$scope.attachmentHandler.deleteAttachment(attachment);
if($scope.attachments.length==0){
c.modalInstance.close();
}
});
}
}
c.openModal = function() {
c.modalInstance = $uibModal.open({
templateUrl: 'modalTemplate',
scope: $scope
});
}
c.closeModal = function() {
c.modalInstance.close();
}
console.log('custom-attachments');
console.log($scope);
}
Any suggestions on how to get the modal to close automatically?
We figured it out. We have to watch the array, then close the modal after array hits zero:
$scope.$watch(function () {
return $scope.attachments;
}, function (value) {
if(value.length==0){
if(c.modalInstance){
c.modalInstance.close();
}
}
});

How do i can show form data after submit on other page using isolated scope directive?

I am new to angularjs,i make a sample app ,now i want to display data on next page when form is submitted using isolated scope directive .
my index.html:
<body ng-controller="mainController">
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Angular Routing Example</a>
</div>
<ul class="nav navbar-nav navbar-right">
<li><i class="fa fa-home"></i> Home</li>
<li><i class="fa fa-shield"></i> About</li>
<li><i class="fa fa-comment"></i> Contact </li>
</ul>
</div>
</nav>
<div id="main">
<!-- angular templating -->
<!-- this is where content will be injected -->
<div ui-view></div>
</div>
</body>
Directive:
var scotchApp = angular.module('scotchApp');
scotchApp.directive('homeData', function() {
return {
scope: {
info: '=fo'
},
templateUrl: 'homeDirective.html'
}
scotchApp.controller('MyFormCtrl', [function() {
this.user = {
name: '',
email: ''
};
this.register = function() {
// console.log('User clicked register', this.user);
alert(this.user.name + "-- " + this.user.email);
$scope.name = this.user.name;
};
}]);
})
how can i make possible this when someone submit the form the about page is displayed after clicking submit button and all form data i want to display there.
here is my plunker: http://plnkr.co/edit/D5S2c6rgycWFfsyfhN9J?p=preview
So based off of what you put into plunker and looking at your code I noticed a few issues.
First: You had your controller inside your directive causing none of the code within the directive to be run.
Your Code
var scotchApp = angular.module('scotchApp');
scotchApp.directive('homeData', function() {
return {
scope:{
info:'=fo'
},
templateUrl: 'homeDirective.html'
}
scotchApp.controller('MyFormCtrl', [function() {
this.user = {
name: '',
email: ''
};
this.register = function() {
// console.log('User clicked register', this.user);
alert(this.user.name+ "-- " +this.user.email);
$scope.name=this.user.name;
};
}]);
})
Update Code:
var scotchApp = angular.module('scotchApp');
scotchApp.directive('homeData', function() {
return {
scope:{
info:'=fo'
},
templateUrl: 'homeDirective.html'
};
});
scotchApp.controller('MyFormCtrl', [function() {
this.user = {
name: '',
email: ''
};
this.register = function() {
// console.log('User clicked register', this.user);
alert(this.user.name+ "-- " +this.user.email);
$scope.name=this.user.name;
};
}]);
Second: You need to add some way allowing your variables to talk between controllers. I used a service and passed it into both controllers
homeDirective.js
scotchApp.controller('MyFormCtrl', ['$scope', 'userService', function($scope, userService) {
$scope.userService = userService;
$scope.user = {
name: '',
email: ''
};
$scope.register = function() {
//sets the variables within the service
$scope.userService.setUserName($scope.user.name);
$scope.userService.setUserEmail($scope.user.email);
alert($scope.userService.getUserName() + "-- " + $scope.userService.getUserEmail());
};
}]);
//Service to be passes to the controllers
scotchApp.service('userService', function(){
var userService = {
user: {
'name': '',
'email': ''
},
getUser: function(){
return userService.user;
},
getUserName: function(){
return userService.user.name;
},
getUserEmail: function(){
return userService.user.email;
},
setUserName: function(name){
userService.user.name = name;
},
setUserEmail: function(email){
userService.user.email = email;
},
};
return userService;
});
script.js
scotchApp.controller('aboutController', ['$scope', 'userService', function($scope, userService) {
$scope.userService = userService;
$scope.user = $scope.userService.user;
$scope.message = 'Look! I am an about page.';
}]);
Third: Your HTML page had to be able to call the register function and move over to the about page.
homeDirective.html
<div class="jumbotron text-center" >
<h1>Home Page</h1>
<p>{{ message }}</p>
<form name="myForm" ng-controller="MyFormCtrl as ctrl">
Name: <input type="text" class="form-control" ng-model="user.name">
Email: <input type="email" class="form-control" ng-model="user.email">
<br/>
<a type="submit" ng-href="#about">
<button ng-click="register();" type="submit">Register</button>
</a>
</form>
</div>
Last: You should put your whole angular app in a self calling function like so
(function(){
var app = angular.module('app'[]);
//more code
})();
Here is the link to the plunk fork that I did for your code. Hopefully that all made sense.Link to Plunker
Good Luck!
Use Angular Services to share data between controllers.You can use services to organize and share code across your app.

AngularJS - Change value from one view to antoher

I have the following layout:
<nav ng-include="'views/nav.html'"></nav>
<div class="container-fluid">
<div ng-view></div>
</div>
Inside nav.html I want to display a dropdown link, once someone has entered a page, and has access. This page is rendered in the ng-view div
Right now I am using a service to update the shared value from one controller to the nav controller. However, the value is never updated in the nav controller. It is set in the service, but not updated.
<nav class="navbar navbar-inverse" role="navigation" ng-controller="LoginController as login">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li>Show option '{{login.showOption}}' <span class="glyphicon glyphicon-info-sign"></span></li>
<li ng-if="login.showOption" dropdown>
<a href class="dropdown-toggle" dropdown-toggle>
Options <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a ng-href>1</a></li>
<li><a ng-href>2</a></li>
</ul>
</li>
</ul>
</div>
</nav>
LoginController.js
var LoginController = function (Option) {
var model = this;
model.showOption = Option.getValue();
module.controller("LoginController", ['Option', LoginController]);
}(angular.module("myApp")));
Option.js
'use strict';
(function (module) {
var option = function () {
var value = false;
return {
getValue: function() {
return value;
},
setValue: function(newVal) {
value = newVal;
}
};
};
module.service("Option", option);
}(angular.module("civApp")));
I am updating the Option.getValue() from another controller in another view, however the nav isn't getting updated.
GameController.js
var GameController = function (Option) {
Option.setValue(true);
module.controller("GameController", ['Option', GameController]);
}(angular.module("myApp")));
When I call this code, the view in the nav.html is not updated. I can see that the Option service is called, but I was hoping the value would have been updated in the view also.
GameController
LoginController
Nav.html
I can clearly see in GameController#watch that the Option.value:show is correctly set to true
That can be sorted using by angular dot rule
just update your code to :
Option Service
(function (module) {
var option = function () {
//value.show instead value
var value = {
show: false
};
return {
value: value
};
};
module.service("Option", option);
}(angular.module("civApp")));
and after that in your controllers
Login
var LoginController = function (Option) {
var model = this;
model.showOption = Option.value;
module.controller("LoginController", ['Option', LoginController]);
}(angular.module("myApp")));
GameController
var GameController = function (Option) {
Option.value = {
show: true
};
module.controller("GameController", ['Option', GameController]);
}(angular.module("myApp")));
and finally in your view
<li>Show option '{{login.showOption.show}}' <span class="glyphicon glyphicon-info-sign"></span>

Unable to get the data to next page in AngularJS

I am new to angularJS. I am currently building a small mobile store where I display the data of all the products in the front page and on clicking the product, i wanted to show the description of the product in the next page.
When I click on the product, I was able to redirect to the next page but not the contents of the page.
My index.html
<div class="container">
<header>
<h1>My Mobile Store</h1>
</header>
<hr class="header-seperator">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">My Mobile Store</a>
</div>
<div>
<ul class="nav navbar-nav">
<li class="active">Mobiles</li>
<li>Electronics</li>
<li>Fashions</li>
<li>Footwears</li>
</ul>
</div>
</div>
</nav>
<div ng-view></div>
</div>
My app.js
(function() {
var storeConfig = function($routeProvider) {
$routeProvider
.when('/', {
controller: 'StoreController',
templateUrl: 'views/products-list.html',
controllerAs: 'StreCtrl'
})
.when('/product', {
controller: 'StoreController',
templateUrl: 'views/product.html',
controllerAs: 'prodCtrl'
});
};
var app= angular.module('mystore',['ngRoute']).config(storeConfig);
app.service('sharedProperties', function () {
var product = [];
return {
getProduct: function () {
console.log(product);
return product;
},
setProduct: function(value) {
product = value;
//console.log(product);
}
};
});
app.controller('StoreController',[ '$http', 'sharedProperties', function($http,sharedProperties) {
var store = this;
store.products = [];
store.error = "false";
store.item = [];
$http.get('data/products.json').success(function(data) {
store.products = data;
}).error(function() {
store.error = "true";
});
store.showProduct = function(product) {
sharedProperties.setProduct(product);
};
store.item = sharedProperties.getProduct;
console.log("Store Item");
console.log(store.item);
}]);
})();
I couldn't get the data in store.item. What is the mistake I am doing. Any help will be grateful.
Thanks in Advance!!
You are assigning a method reference to your scope object, Instead of you need to call method.
Change From
store.item = sharedProperties.getProduct;
TO
store.item = sharedProperties.getProduct();

AngularJS - animation callback / sequence

I'm just finding my way with Angular, and more importantly trying to find my way without jQuery!
I'd like to have a view that shows a loading spinner while data is fetched from the server, and when it arrives (the model will have a property of "Populated") I want the spinner to fade out, and the content to fade in.
I'm using a directive for the loading bit, and ng-show seems simple enough to switch the sections in the view.
View:
<div ng-hide="model.Populated" loading-spinner></div>
<div ng-show="model.Populated"><!-- Content goes here --> </div>
Directive:
module App.Directives {
declare var Spinner: any;
export class LoadingSpinner {
constructor() {
var directive: ng.IDirective = {};
directive.restrict = 'A';
directive.scope = { loading: '=myLoadingSpinner'},
directive.template = '<div class="loading-spinner-container"></div>';
directive.link = function (scope, element, attrs) {
var spinner = new Spinner().spin();
var loadingContainer = element.find('.loading-spinner-container')[0];
loadingContainer.appendChild(spinner.el);
};
return directive;
}
}
It's the animation I'm not sure about. In particular, I want the content to fade in once the spinner has completely faded out, and I'm not sure how to do this with a callback.
Should I attempt all the animation with CSS or expand on my directive and use javascript?
(I'm using TypeScript for anyone wondering about the syntax)
I did a quick spike for my app yesterday and this is how it can be easily done.
This uses ui.bootstrap modal dialog.
When you have a long running process such as remote service call you "raise" an event via $emit. This will bubble up to your outer most scope. Here is a sample from typeahead search functionality I spiked it against.
function autoCompleteClientName(searchValue, searchType) {
var self = this;
self.$emit("WorkStarted", "Loading Clients...");
//return promise;
if (searchType === 'name') {
return $scope.clientSearchDataService.getClientsByName(searchValue)
.then(function (response) {
self.$emit("WorkCompleted", "");
return response.results;
}, function(error) {
self.$emit("WorkCompleted", "");
console.warn("Error: " + error);
});
} else if (searchType === 'number') {
return $scope.clientSearchDataService.getClientsByNumber(searchValue)
.then(function (response) {
self.$emit("WorkCompleted", "");;
return response.results;
}, function (error) {
self.$emit("WorkCompleted", "");
console.warn("Error: " + error);
});
}
};
Then we have a "shell" controller that is the controller for outermost view, the one that hosts ng-view.
(function () {
'use strict';
// Controller name is handy for logging
var controllerId = 'shellCtrl';
// Define the controller on the module.
// Inject the dependencies.
// Point to the controller definition function.
angular.module('app').controller(controllerId,
['$scope', '$modal', shellCtrl]);
function shellCtrl($scope,$modal) {
// Bindable properties and functions are placed on vm.
$scope.title = 'shellCtrl';
$scope.$on("WorkStarted", function(event, message) {
$scope.modalInstance = $modal.open({ templateUrl: "app/common/busyModal/busyModal.html" });
});
$scope.$on("WorkCompleted", function (event, message) {
$scope.modalInstance.close();
});
}
})();
Here is the modal template
<div class="modal-dialog">
<div class="modal-content">
<img src="/images/loading.gif"width="55" height="55"/>
<h3>Loading data...</h3>
</div>
<!-- /.modal-content -->
</div><!-- /.modal-dialog -->
For this to appear you have to override some styles
<style>
.modal
{
display: block;
}
.modal-body:before,
.modal-body:after
{
display: table;
content: " ";
}
.modal-header:before,
.modal-header:after
{
display: table;
content: " ";
}
</style>
and if you need full template for modal
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
Keep in mind that this is just a spike that took me about 30 min to wire together. For more robust solution, you need to be able to keep track of number of processes started and completed etc, if you are executing multiple calls to remove service.

Resources