In my angular app, i have a message service to display info, loading and error messages for my app. It looks like that:
module.factory('msgSvc', function(){
var current_message = '';
return {
message: function(){
return current_message;
},
setMessage: function(msg){
console.log('setting message: '+ msg);
current_message = msg;
},
clear: function(){ current_message = null; current_style = null}
}
});
and in my view i have
<span class="pull-left label label-warning" >{{ msg.message() }}</span>
I have a login controller, when the user submits the form i want to show a "logging you in..." message while an ajax login is sent. and an error message if there was an error. here's my code:
function LoginCtrl($scope, $http, msgSvc) {
[...]
$scope.login = function(creds) {
console.log(creds);
msgSvc.setMessage('Logging in...');
$http.post('http://...',creds)
.success(function(data){
console.log(data);
[...]
msgSvc.clear();
$location.path('/');
})
.error(function(data, status){
console.log(status);
msgSvc.setMessage('Wrong username or password.');
});
};
}
login() is called by the submit form, Logging in... never shows even though the function is called (it appears in the console). but the error message appears.
am i doing something wrong?
edit: the login form
<form class="form">
<input type="text" placeholder="Username" ng-model="loginCreds.username" required />
<input type="password" placeholder="Password" ng-model="loginCreds.password" required />
<button ng-click="login(loginCreds)">Login</button>
</form>
edit 2
If it changes anything, there are many controllers setting messages in the service and in the actual code, the controller showing the message (where the $scope.msg variable is set) is different from the one setting the message.
function BodyCtrl($scope, msgSvc) {
$scope.msg = msgSvc;
}
There are couple problems with your implementation:
As the message is being set in a private variable, you would need use $watch for the message to be displayed;
A .factory is a singleton and therefore setMessage would have set the same message for all controllers.
The simplest solution is to pass the controller's $scope to the svcMsg:
app.factory("msgSvc", function () {
return function (scope) {
var priv_scope = scope;
this.setMessage = function (msg) {
console.log('setting message: '+ msg);
priv_scope.message = msg;
};
this.clear = function () {
priv_scope.message = "";
};
};
});
In you controller, you would then do:
var msg = new msgSvc($scope);
In case you do want to propagate the message to all controllers, use $rootScope:
app.service("msgSvc", function ($rootScope) {
var priv_scope = $rootScope;
this.setMessage = function (msg) {
console.log('setting message: '+ msg);
priv_scope.message = msg;
};
this.clear = function () {
priv_scope.message = "";
};
});
Check out this Plunker using $rootScope:
http://plnkr.co/edit/NYEABNvjrk8diNTwc3pP?p=preview
As $rootScope is really a global variable in Angular, you shouldn't abuse it. It can also be problematic if you accidentally set the $scope.message in controllers. An alternative is to use $watch to detect the change to the message:
// In your controller, do:
$scope.$watch(
function () {
return msgSvc.message;
},
function () {
$scope.message = msgSvc.message;
}
)
Here is an example using $watch:
http://plnkr.co/edit/vDV2mf?p=info
Set $scope.msg in each place you want to display the value on the view:
function LoginCtrl($scope, $http, msgSvc) {
[...]
$scope.msg = "";
$scope.login = function(creds) {
console.log(creds);
$scope.msg = msgSvc.setMessage('Logging in...');
$http.post('http://...',creds)
.success(function(data){
console.log(data);
[...]
$scope.msg = msgSvc.clear();
$location.path('/');
})
.error(function(data, status){
console.log(status);
$scope.msg = msgSvc.setMessage('Wrong username or password.');
});
};
}
I know you may be trying to avoid that but the changes in its value are not being propagated.
Add $apply to in error()
function LoginCtrl($scope, $http, msgSvc) {
[...]
$scope.login = function(creds) {
console.log(creds);
msgSvc.setMessage('Logging in...');
$scope.msg = msgSvc;
$http.post('http://...',creds)
.success(function(data){
console.log(data);
[...]
msgSvc.clear();
$location.path('/');
})
.error(function(data, status){
$scope.$apply(function() {
msgSvc.setMessage('Wrong username or password.');
});
});
};
}
SEE DEMO
Related
I have a simple mdDialog for editing form entries, that pops up when you double-click a grid row. The dialog appears and everything works, except I'd like to populate the fields in the dialog with the contents of the grid row. The problem is that I'm not sure where to actually do this, and every spot I've tried so far is accessed before the Dialog has actually been shown, so the HTML elements inside the dialog don't exist yet to be populated. Here's the method that calls the dialog:
$scope.showUpdateDialog = function(data) {
$mdDialog.show({
controller: UpdateDialogController,
scope: $scope.$new(),
templateUrl: 'js/admin/UpdateUsersDialog.html',
parent: angular.element(document.body),
clickOutsideToClose:true,
fullscreen:true
})
.then(function(answer) {
$scope.status = 'Updated User';
}, function() {
$scope.status = 'Update User Failed';
});
};
And here is the controller for it:
function UpdateDialogController($scope, $mdDialog) {
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
$scope.add = function(dialogdata) {
// For clarity I removed the contents of what happens with the add, as this part works fine.
};
}
Inside that templateUrl: 'js/admin/UpdateUsersDialog.html' are several elements, that all look like this:
<input type="text" class="form-control" id="updatelogin"
placeholder="Enter Name" data-ng-model="dialogdata.login" disabled />
I thought that the data-ng-model bit would take care of it (dialogdata.login etc. are all assigned variables before the dialog is kicked off) but it doesn't, so I was attempting to force it by doing something like this:
var ulogin = document.getElementById('updatelogin');
ulogin.setInnerHTML(content.login);
...but since the elements don't exist yet the 'ulogin' var keeps coming back as null. Is there a way to do this?
Okay after some babbaging I figured something out, maybe this can help others who hit a similar problem. The solution for me was to put everything into the onRowDoubleClicked method, which is called in the ag-grid when I double click a row in the grid:
function onRowDoubleClicked() {
var selectedRowNode = $scope.UserTableGrid.api.getSelectedNodes()[0];
var data = selectedRowNode.data;
$scope.login = data.login;
$scope.name = data.name;
$scope.email = data.email;
$scope.type = data.roles;
$scope.userId = $scope.getUserId($scope.login);
showUDialog(data);
function showUDialog(data) {
$scope.email = data.email;
$mdDialog.show({
controller: UpdateDialogController,
scope: $scope.$new(),
templateUrl: 'js/admin/UpdateUsersDialog.html',
parent: angular.element(document.body),
clickOutsideToClose:true,
fullscreen:true
})
.then(function(answer) {
$scope.status = 'Updated User';
}, function() {
$scope.status = 'Update User Failed';
});
}
function UpdateDialogController($scope, $mdDialog) {
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
$scope.add = function() {
//Close the Dialog
$scope.answer("Completed");
var userJson = {
"name" : $scope.name,
"login" : $scope.login,
"password" : "",
"roles" : $scope.type,
"email" : $scope.email,
"tpbUserId" : $scope.userId
};
var url = 'RobotDataInterface/User/updateUser';
var data = JSON.stringify(userJson);
$http.post(url, data).then(
function success(response) {
$scope.addUserCallback(response);
},
function failure(response) {
$scope.generalRestFailureCallback(response);
}
);
};
$scope.addUserCallback = function(data) {
console.log("User Updated!");
$scope.loadTableData();
}
}
}
This fixed what was a scoping issue, and allowed the fields in the templateUrl to get populated by tying them to those same values:
<input type="text" class="form-control" id="name"
placeholder="Enter Name" data-ng-model="name" />
I got the next controller:
.controller('LogInController', function(logInFactory, $scope, $location, $state){
$scope.logIn = function() {
$scope.dataLoading = true;
logInFactory.logIn($scope.email, $scope.password, function (response) {
if (response.success) {
$scope.userName = response.userName;
console.log('userName', $scope.userName);
logInFactory.setCredentials($scope.email, $scope.password);
$location.path('/users');
} else {
$scope.dataLoading = false;
}
});
};
$scope.clearCredentials = function(){
$state.go('login');
logInFactory.clearCredentials();
};
});//End controller
I want to use it in this view:
<div class="header" ng-controller = 'LogInController'>
<img src= "logo.jpg">
{{userName}}
<button ng-click = 'clearCredentials()'> Cerrar sesión</button>
</div>
But userName is not showing in the view but when I print it on the controller it is displayed correctly. That view is displayed after call the logIn() function.
This is the logIn function in my factory:
var logIn = function(email, password, callback){
var URL;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.logInMock;
return (
$timeout(function () {
var response;
getUser()
.then(function (user) {
console.log('USER', user);
if (user !== null) {
response = { success: true, userName: user.userName};
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
});
}, 1000)
);
}else{
URL = ENV.apiURL + ENV.logIn;
return (
$http.post(URL, {email : email, password : password})
.then(function onFulfilled(response){
var data = response.data;
userName = data.username;
userEmail = data.email;
userId = data.id;
profiles = data.profiles;
callback(response);
return data;
})
.catch(function onRejected(errorResponse){
console.log('Error in logInFactory');
console.log('Status: ', errorResponse.status);
callback(errorResponse);
return errorResponse;
})
);
}
};//End login
I trigger the logIn() function in this view
<form ng-submit = 'logIn()'>
<h1>Log In</h1>
Correo electrónico:
<input type="email" ng-model='email' required><br>
Contraseña
<input type="password" ng-model='password' required><br>
<input type="submit" value="Log in">
</form>
When I tigger logIn() I should go to that header and show the userName.
why are you triggering clearCredentials() ? whereas according to this code you should triggering login() instead.
The result of your logIn() function may be out of Angular scope.
Try wrapping the result of the logIn function into a $timeout (which calls $apply, a way to force Angular to refresh a controller scope):
$timeout(function() {
if (response.success) {
$scope.userName = response.userName;
console.log('userName', $scope.userName);
logInFactory.setCredentials($scope.email, $scope.password);
$location.path('/users');
} else {
$scope.dataLoading = false;
}
});
Do not forget to inject the dependency $timeout in your controller.
You have $scope.userName inside the success of the logIn method. It won't be available until that has happened.
If you put $scope.userName outside of the method and set it to something, it would appear.
.controller('LogInController', function(logInFactory, $scope, $location, $state) {
$scope.userName = 'test name';
$scope.logIn = function() { ...
Something like that.
we don't have your factory code but this line is very strange to me :
logInFactory.logIn($scope.email, $scope.password, function (response) { ..; } )
so your passing the fonction to the factory and it is not the factory who returning data to the controller.
it should be something like this :
logInFactory.logIn($scope.email, $scope.password).then(function (response) { ..; } );
EDIT :
You have to remove the callback function from your factory and make the factory return data and handle data like this logInFactory.logIn($scope.email, $scope.password).then(function (response) { ..; } );.
You have log in the console but the $scope is not shared between the factory and controller so the callback in your factory edit the $scope.userName but the controller cannot get this change.
My problem was that I was expecting to get data from a controller to two different views. And when I go from LogIn view to my header view, the controller refresh its data. So, I have to create in my factory:
var getUserName = function() {
return userName;
};
And in the controller
$scope.userName = logInFactory.getUserName();
Now my userName persists in the factory.
I've a following controller code with me :
var app = angular.module('app_name');
app.controller("manageUsersController", [ "config", "$scope", "$http", "$mdToast",
function(config, $scope, $http, $mdToast) {
$scope.add = function() {
var userData = {
email : $scope.manageUsers.email,
password : $scope.manageUsers.password,
schoolId : '1',
name : $scope.manageUsers.name,
mobileNumber : $scope.manageUsers.mobileNumber
};
$http.post(config.webServicesUrl.postAddUser, userData, headerConfig).success(
function(data) {
displayToastMessage("User added successfully", $mdToast);
}).error(function(error) {
console.log(error.error);
});
}
}]);
All the HTML fields are input fields and are accessed using $scope object.
I tried with $setPristine but it didn't work.
Somebody please help me in setting all the fields to empty upon successful submission of form only in my code.
Thanks.
If you want to reset your form upon completion, I think you have to reset the $scope.manageUsers object manually once your post request has resolve:
$http.post(config.webServicesUrl.postAddUser, userData, headerConfig).success(
function(data) {
// has I don't know if you have other properties
// I reset each property manually,
// but you could probably do $scope.manageUsers = {}
$scope.manageUsers.email = null;
$scope.manageUsers.password = null;
$scope.manageUsers.name = null;
$scope.manageUsers.mobileNumber = null;
displayToastMessage("User added successfully", $mdToast);
}).error(function(error) {
console.log(error.error);
});
You can use $setPristine() here:
$http.post(config.webServicesUrl.postAddUser, userData, headerConfig).success(
function(data) {
displayToastMessage("User added successfully", $mdToast);
$scope.form.$setPristine(); // <---------here.
}).error(function(error) {
console.log(error.error);
});
Plnkr demo in action.
It should work for you.
$http.post(config.webServicesUrl.postAddUser,userData,headerConfig)
.success(function(data) {
$scope.manageUsers={};
displayToastMessage("User added successfully.", $mdToast);
}).error(function(error) {
console.log(error.error);
});
I have a custom directive for soundcloud that requires the soundcloud url. The soundcloud url is fetched from the database through the $http service, however, the div for the soundcloud custom directive is loaded and requires the value of the soundcloud url before it is even defined.
The Plangular Directive Code I got is here:
https://github.com/jxnblk/plangular/blob/master/src/plangular.js *I did not develop this
This is my HTML code:
<div plangular="{{soundcloud}}">
<button ng-click="playPause()">Play/Pause</button>
<progress ng-value="currentTime / duration || 0">
{{ currentTime / duration || 0 }}
</progress>
</div>
And this is the Angular Code:
displaySong.controller('song', ['$scope', '$http', 'fetchSong', function($scope, $http, fetchSong) {
$scope.songID
$scope.songName;
//Controller properties
$scope.songPromise; //The song promise for fetching
$scope.init = function(songID, userID) {
$scope.songID = songID;
$scope.userID = userID;
$scope.songPromise = $http({
method: "post",
url: fetchSong,
data: {
song_id: $scope.songID
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function(successResponse) {
console.log('Successfully fetched song');
console.log(successResponse);
var song = successResponse.data;
$scope.songID = song.song_id;
$scope.songName = song.song_name;
$scope.songType = song.song_type;
$scope.songEmbed = song.song_embed;
$scope.soundcloud = song.song_embed;
}, function(errorResponse) {
console.log('Error fetching');
$scope.songID = null;
});
};
}]);
I know it's a problem with the asynchronous nature because when I add this line in the beginning of my song controller:
$scope.soundcloud = "https://soundcloud.com/jshigley/shine";
It works perfectly fine. I've also noticed that when I spam the play/pause button that DOES come up from the directive, I get multiple console errors of "HTTP 404 Not Found", which leads me to believe it's trying to find a track of undefined url
Since it's a div directive and not a function call I can't use promises such as chaining a then to my $scope.songPromise. I've thought of putting it into a controller and having the controller do something like $timeout for 5 seconds, but I don't think this delays the execution of the DOM.
The soundcloud URL DOES end up getting loaded, but it remains undefined in the eyes of the plangular directive (I've actually encountered lots of these problems with bad timing of loading scope and directives). Any Angular Wizards willing to teach me how to tame the asynchronous nature of AngularJS?
You can use $watch in the custom directive to watch when url attributes is changed.
In
link: function(scope, el, attr) {
change from
if (src) {
resolve({ url: src, client_id: client_id }, function(err, res) {
if (err) { console.error(err); }
scope.$apply(function() {
scope.track = createSrc(res);
if (Array.isArray(res)) {
scope.tracks = res.map(function(track) {
return createSrc(track);
});
} else if (res.tracks) {
scope.playlist = res;
scope.tracks = res.tracks.map(function(track) {
return createSrc(track);
});
}
});
});
}
to
scope.$watch('attr.plangular', function(newVal) {
resolve({ url: attr.plangular, client_id: client_id }, function(err, res) {
if (err) { console.error(err); }
scope.$apply(function() {
scope.track = createSrc(res);
if (Array.isArray(res)) {
scope.tracks = res.map(function(track) {
return createSrc(track);
});
} else if (res.tracks) {
scope.playlist = res;
scope.tracks = res.tracks.map(function(track) {
return createSrc(track);
});
}
});
});
}, true);
If you dont want to change the directive then you might want to use ng-if to load that plangular div only when you get the url.
<div plangular="{{soundcloud}}" ng-if="haveurl">
and in the angular code :
}).then(function(successResponse) {
console.log('Successfully fetched song');
console.log(successResponse);
$scope.haveurl = true;
Try using ng-show like this to only show the div once your $http request has been completed.
<div ng-show="httpRequestComplete" plangular="{{soundcloud}}">
<button ng-click="playPause()">Play/Pause</button>
<progress ng-value="currentTime / duration || 0">
{{ currentTime / duration || 0 }}
</progress>
</div>
displaySong.controller('song', ['$scope', '$q', '$http', 'fetchSong', function($scope, $http, fetchSong) {
/* add $q promise library */
$scope.songID
$scope.songName;
var httpRequest = function() {
var deferred = $q.defer();
$http({
method: "post",
url: fetchSong,
data: {
song_id: $scope.songID
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function(successResponse) {
deferred.resolve({response: successResponse});
console.log('Successfully fetched song', successResponse);
var song = successResponse.data;
$scope.songID = song.song_id;
$scope.songName = song.song_name;
$scope.songType = song.song_type;
$scope.songEmbed = song.song_embed;
$scope.soundcloud = song.song_embed;
}).error(function(error) {
console.log(error);
});
return deferred.promise;
};
httpRequest().then(function(response) {
$scope.httpRequestComplete = true;
console.log('div will show');
};
}]);
I would do something like this that delays the showing of the div until httpRequestComplete = true, or until your promise ($q) is fulfilled. This will make sure that your div isn't loaded until you have the information available.
Below I've got an angular app and controller where the controller have data access inside of it (bad idea, I know)
var app = angular.module('app',[]);
app.controller('HomeController',function($scope,$http){
$scope.people = null;
$scope.get = function() {
$http({
url: 'largeTestData.json',
method: 'GET'
}).then(function(data){
console.log('request successful, here is your data: ');
console.log(data['data']);
$scope.people = data['data'];
},function(reason){
console.log('this failed, this is the reason: ');
console.log(reason);
})
}
});
app.controller('ControllerWithService',function($scope, MyService){
$scope.get = MyService.get;
$scope.get(function(data){
console.log('you succeeded');
},function(reason){
console.log('you failed');
console.log(reason);
})
})
This will work in retrieving data and putting it onto the page. Knowing that having data Access in the controller is no bueno I tried to abstract that out into a service:
app.service('MyService',function($http,$q){
var get = function(){
var deferred = $q.defer();
var url = 'test.json';
$http.get(url).success(deferred.resolve).error(deferred.reject);
}
return {
get: get
}
})
Here my 'data layer' is a service that only has one method: get from the above listed URL.
app.service('MyService',function($http,$q){
var get = function(){
var deferred = $q.defer();
var url = 'test.json';
$http.get(url).success(deferred.resolve).error(deferred.reject);
}
return {
get: get
}
})
and my HTML
<body>
<script src="libs/angular-1.2.15.js"></script>
<script src="app/app.js"></script>
<script src="app/DocumentService.js"></script>
<script src="libs/jQuery-2.1.1.js"></script>
<div ng-controller="HomeController">
<button ng-click="get()" href="#">Get data</button>
<div>{{message}}</div>
<!--<div ng-repeat="p in people" >-->
<!--<b>Business Doc ID: </b><h1>{{p['busDocId']}}</h1>-->
<!--<b>DOC ID: </b>{{p['docId']}}-->
<!--<b>FILE NAME: </b><div style="color: green">{{p['fileName']}}</div>-->
<!--</div>-->
</div>
<div ng-controller="ControllerWithService">
{{message}}
<button ng-click="get()">get data</button>
<div>{{data}}</div>
</div>
</body>
I'm not getting any error messages, and the commented out out stuff in my HomeController works as expected. What am I doing wrong in trying to make my AJAX calls a service?
working solution changes:
app.service('MyService',function($http,$q){
this.get = function(){
return $http.get('test.json')
}
})
app.controller('ControllerWithService',function($scope, MyService){
$scope.data = null;
$scope.get = function() {
MyService.get().then(function (data) {
console.log('this is the success data: ');
console.log(data)
$scope.data = data;
}, function (reason) {
console.log('this is the fail reason');
console.log(reason);
$scope.data = reason;
})
}
})
It looks like it could be a couple different things. I'll post an example I have working in one of my projects right now. It should be extremely similar and simple with what you're goal is.
Service:
'use strict';
angular.module('srcApp')
.service('Getlanguage', function Getlanguage($location, $http, $log, $state, $rootScope) {
this.getContent = function() {
var language = $location.path().split('/'),
languageCulture = language[1];
if (!languageCulture) {
languageCulture = 'en';
}
$rootScope.cultureCode = languageCulture;
return $http({method: 'GET', url: '/languages/' + languageCulture + '.json'})
.error(function() {
// If service cannot find language json file, redirect to index
$state.go('lang', {lang: 'en'});
});
};
});
Controller Call to service:
After passing in the service as a dependency into the controller.
Getlanguage.getContent().then(function(res) {
$scope.content = res.data;
});
Hope this helps.