I'm trying to update user data, but when I make the 'PUT' request, Angular is sending the entire user $scope rather than just the fields exposed by the form.
I'm using a Factory to get and put the data. Here's my edit function
$scope.editStudent = function () {
Student.edit ({id: $stateParams.studentId}, $scope.Student, function (data) {
$location.path('/');
});
};
This doesn't work for me because the server I'm sending to does it's own server-side validation, and if I'm sending the entire student scope there will be some fields that are blank, thus will not pass validation.
The server allows me to send just the field I need to update, so I'm looking for an Angular way of doing this.
Here's a screenshot to help explain my question:
You can write function to check the field defined in the form and then only submit the value. I think the code is self explanatory.
Code Snippet
app.controller('MainCtrl', function($scope, $http) {
$scope.user = {
"name": "Ali",
"company": "MSB",
getData: function(form) {
var data = {};
angular.forEach(this, function(fieldValue, fieldName) {
if (!angular.isFunction(fieldValue) && form.$isSubmit(fieldName)) {
data[fieldName] = fieldValue;
}
});
return data;
}
}
$scope.save = function() {
$scope.userForm.$isSubmit = function(fieldName) {
return !angular.isUndefined((this[fieldName]));
}
console.log($scope.user.getData($scope.userForm));
$scope.data = $scope.user.getData($scope.userForm);
//$http.put('url', $scope.data)
}
});
I created the plunkr - http://plnkr.co/edit/6g5myBvBmWZ2IvKMLuvL?p=preview
*Tips (or rules)
input name must same as the model name
change getData function as you like to process which data to return.
Related
I am trying to implement a login functionality that redirects a valid user to a personalized HTML view welcoming them with their name.
My first view is a login view. When the user clicks the button labeled "Log In" ng-click is supposed to call my controller's $scope.login which is supposed to validate the user, set the $scope.needsLoggingIn variable, and redirect to the second view (welcome page that shows the value of $scope.needsLoggingIn).
My problem is that the second view (which uses the same Angular Controller and Module) does not show the $scope.needsLoggingIn variable when I try to set it in the $scope.login function. It only shows the $scope.needsLoggingIn when I hard-coded the value like this in the controller, outside a scope function:
$scope.needsLoggingIn = "Betty";
This is the $scope.login function in my controller which tries to set the $scope.needsLogging in and is accepted in the first view but not accepted by the second view:
$scope.login = function (username, password) {
$http({
method: 'GET',
url: ('/login/' + username + '/' + password)
}).then(function successCallback(response) {
if (JSON.stringify(response.data) === '[]')
{
$scope.needsPasswordMessage = true;
if ($scope.needsPasswordMessage)
$scope.needsPasswordMessage = false;
console.log("No match");
} else {
$scope.needsLoggingIn = username;
$scope.issuccessMessage = true;
if ($scope.isfailureMessage)
$scope.isfailureMessage = false;
$window.location.href = '../indextest.html';
return response;
}
}, function errorCallback(response) {
});
};
Does anyone have any suggestions how I can get my $scope.needsLoggingIn recognized by both HTML views, without hard-coding it into the controller? I would really like to be able to have my $scope.login function set the variable value and have it recognized by all views that use this particular controller.
Data that you want to have live across different views and controller instances should be handled via a service.
Basically you would create a service something like:
function userService($http) {
let service = {}
service.needsLoggingIn = ""
service.login = login
return service
function login() {
return $http.... (your login code)
}
}
Then inject the service into your control, set properties as appropriate and since the service is a singleton, those property values will be available everywhere you inject the service.
I have an API call that's working great, but I'd like to use it on several controllers so I moved it to it's own service. I'm running into what looks like a classic Scope issue or a misunderstanding of Angular's digest cycle.
'use strict';
myApp.factory('Stuff',['$http', function ($http) {
var Stuff = {};
Stuff.data = {};
Stuff.api = 'http://localhost:8080/api/';
Stuff.getStuff = function() {
var http_stuff_config = {
method: 'GET',
url: Stuff.api + 'stuff/'
};
$http(http_stuff_config).then(function successCallback(response) {
Stuff.data = (response.data);
console.log(Stuff.data); // Returns populated object.
},function errorCallback(response) {
console.log(response.statusText);
});
};
Stuff.getStuff();
console.log(Stuff.data); // Returns empty object.
return Stuff;
}]);
myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
$scope.stuff = Stuff;
console.log($scope.stuff.data); // Returns empty object.
$scope.stuff.getJobs();
console.log($scope.stuff.data); // Returns empty object.
}]);
Here's the big clue. The essential output of above, in order is...
empty object (in service after calling method)
empty object (in controller before calling method)
empty object (in controller after calling method)
populated object (in method execution from service)
populated object (in method execution from controller)
So somewhere between the scope of the getStuff() method and Angular's order of operations, I'm doing something remarkably foolish. Thank you in advance.
You need to add returns on your service, or else the promise will not be returned to the controller. It is not good practice to just store the returns in your services AND NOT return the result to the controller.
This is considered bad practice because, any time you update the data on the service everyone will need to apply $scope.$watch to the service to look for updates. This can be very expensive in large scale apps.
The best Idea is to return the data to the calling controller (if you do not need to cache it, this we can talk about later) and let the controller access it via the promise service.getthing().then(function(result){});
myApp.factory('Stuff',['$http', function ($http) {
var Stuff = {};
Stuff.data = {};
Stuff.api = 'http://localhost:8080/api/';
Stuff.getStuff = function() {
var http_stuff_config = {
method: 'GET',
url: Stuff.api + 'stuff/'
};
return $http(http_stuff_config).then(function successCallback(response) {
return response.data;
console.log(Stuff.data); // Returns populated object.
},function errorCallback(response) {
console.log(response.statusText);
});
};
Stuff.getStuff();
console.log(Stuff.data); // Returns empty object.
return Stuff;
}]);
myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
$scope.stuff = Stuff;
console.log($scope.stuff.data); // Returns empty object.
$scope.stuff.getJobs().then(function(result) {$scope.stuff = result; console.log(result);});
console.log($scope.stuff.data); // Returns empty object.
}]);
I recommend you not to store the result inside the service itself (Stuff.data). Just return your data in the getStuff function and let the appController's scope store the data instead.
remember that $scope.stuff.getJobs() is async
(meaning you can't actually call console.log($scope.stuff.data) on the next line and get the data)
Now if you had a view, with something like <span ng-bind="stuff.data.property"> you could see it work just fine because the view will update by itself when the async function is done. (this is part of angular)
You need to understand that when you run $http, it is making an AJAX request. therefore it will not return an result immediately.
Therefore, if you attempt to use the data coming from $scope.stuff.getJobs(); immediate after invoking this function, you are likely to get nothing.
What you should do is to have your Stuff.getJobs() return a promise, and use promise.then(your own success handler) to correctly handle the returned response.
I have cleaned up your code a little bit. The following is a running sample of your code retrieving data from Yahoo Weather API.
You can play with it on CODEPEN.
html:
<div ng-app="myApp" ng-controller="appController">
<p>{{data}}</p>
</div>
JS:
var myApp = angular.module("myApp", []);
myApp.factory('Stuff',['$http', function ($http) {
var Stuff = {};
Stuff.data = {};
//sample yahoo weather api
Stuff.api = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys';
Stuff.getData = function() {
var http_stuff_config = {
method: 'GET',
url: Stuff.api + 'stuff/'
};
return $http(http_stuff_config);
};
return Stuff;
}]);
myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
$scope.data = "$http service not ran";
var uncompletedAjaxCall = Stuff.getData();
uncompletedAjaxCall.then(
function(responseData){
$scope.data = responseData;
},
function(errorMsg){}
);
}]);
I am new to angular and what I am willing to do is replace a piece of code I wrote in the past in jquery to angularjs.
The goal is to take a string from a span element, split it in two and pass the two strings as parameters in a GET request.
I am trying to learn best coding pratices and improving myself so any comments of any kind are always welcome.
Working Code in jquery:
//Get Song and Artists
setInterval(function () {
var data = $('#songPlaying').text();
var arr = data.split('-');
var artist = arr[0];
var songTitle = arr[1];
//Request Lyrics
$.get('lyricsRequester.php', { "song_author": artist, "song_name" : songTitle},
function(returnedData){
console.log(returnedData);
$('#refreshLyrics').html(returnedData);
});
},10000);
Code in Angular
var app = angular.module("myApp", []);
app.factory('lyricService', function($http) {
return {
getLyrics: function($scope) {
//$scope.songArr = $scope.currentSong.split('-'); <-- **undefined currentSong**
//$scope.artist = $scope.songArr[0];
//$scope.songTitle = $scope.songArr[1];
return
$http.get('/lyricsRequester.php', {
params: {
song_author: $scope.artist,
song_name: $scope.songTitle
}
}).then(function(result) {
return result.data;
});
}
}
});
app.controller('lyricsController', function($scope, lyricService, $interval) {
$interval(function(){
lyricService.getLyrics().then(function(lyrics) {
$scope.lyrics = lyrics; <-- **TypeError: Cannot read property 'then' of undefined**
console.log($scope.lyrics);
});
}, 10000);
});
index.html (just a part)
<div class="col-md-4" ng-controller="lyricsController">{{lyrics}}</div>
<div class="col-md-4"><h3><span id="currentSong" ng-model="currentSong"></span></h3><div>
You need to be careful with your return statement when used in conjunction with newlines, in these lines:
return
$http.get('/lyricsRequester.php',
If you don't, JS will automatically add a semicolon after your return, and the function will return undefined.
Move the $http.get statement to the same line as your return statement.
return $http.get('/lyricsRequester.php', ...
Refer to the following docs:
MDN return statement
Automatic Semicolon Insertion
As for your second issue, you $scope is not really something you inject into your services (like $http). Scopes are available for use in controllers.
You need to refactor your code a bit to make things work.
eg. Your getLyrics function can take a song as a parameter. Then in your controller, you call lyricsService.getLyrics(someSong). Scope access and manipulation are only done in your controller.
app.factory('lyricService', function($http) {
return {
getLyrics: function(song) {
var songArr = song.split('-');
var artist = songArr[0];
var songTitle = songArr[1];
return $http.get('/lyricsRequester.php', {
params: {
song_author: artist,
song_name: songTitle
}
}).then(function(result) {
return result.data;
});
}
}
});
app.controller('lyricsController', function($scope, lyricService) {
$scope.currentSong = 'Judas Priest - A Touch of Evil';
$interval(function(){
lyricService.getLyrics($scope.currentSong).then(function(lyrics) {
$scope.lyrics = lyrics;
console.log($scope.lyrics);
});
}, 10000);
});
You also have some other issues, like using ng-model on your span. ng-model is an angular directive that is used in conjunction with form elements (input, select etc.), not a span as you have. So you might want to change that into an input field.
$http does not use .then, it uses .success and .error. the line that you have where it says then is undefined, should be replaced with a success and error handler instead. Below is a sample from the docs:
// Simple GET request example :
$http.get('/someUrl').
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
See Link:
https://docs.angularjs.org/api/ng/service/$http
I have built a simple application in Angular consuming a simple API I created myself using Laravel. The application is hosted here. The API is hosted here. Now I can log in to the application at which point the API returns a simple auth_token which is sent as the URL parameter in every subsequent request that is sent to the server.
There is only one user in the system:
Email: admin#admin.com
Password: admin12345
You can log into the application using these credentials at which point the application will set a cookie using the $cookieStore service and will use the token in this cookie for every subsequent request. After using the application, a user can log out from the application, where a DELETE request is sent to the server and on the success method, the cookie is deleted from the browser.
Unfortunately there is some issue with the code I suppose. The DELETE request is working as expected and it deletes the auth_token on the server and returns 200 OK. But the success method is not called. I am not sure what I am doing wrong. It might be just a syntax problem.
app.js
function AppCtrl ($scope, $cookieStore, $location, Auth) {
$scope.setActive = function (type) {
$scope.destinationsActive = '';
$scope.flightsActive = '';
$scope.reservationsActive = '';
$scope[type + 'Active'] = 'active';
};
$scope.authenticate = function (credentials) {
Auth.save(credentials, function(data){
$cookieStore.put('auth_token', data.auth_token);
$scope.isLoggedIn = true;
$location.path('destinations');
$scope.message = null;
}, function(data){
$scope.message = "Email/Password combination incorrect!";
});
};
$scope.logout = function () {
//var auth_token = $cookieStore.get('auth_token');
Auth.delete({
'auth_token': $cookieStore.get('auth_token')
}, function(data){
$scope.isLoggedIn = false;
$cookieStore.remove('auth_token');
});
};
if($cookieStore.get('auth_token')){
$scope.isLoggedIn = true;
}else{
$scope.isLoggedIn = false;
}
}
The logout function is called when the log out button is pressed. What am I doing wrong here?
Note: The application is not working on Chrome for some reason (Use Firefox). If you can shed some light on that, it would be very helpful.
Both the repositories are public if you wish to have a look:
AngulAir Application: http://gitlab.learningtechasia.com:8901/rohan0793/angulair.git
AngulAirAPI: http://gitlab.learningtechasia.com:8901/rohan0793/angulairapi.git
Here is your solution
$scope.logout = function () {
//var auth_token = $cookieStore.get('auth_token');
Auth.delete(
{'auth_token': $cookieStore.get('auth_token')}, // parameters
{},//postData, which you don't need for this
function(data){
$scope.isLoggedIn = false;
$cookieStore.remove('auth_token');
},
// error callback
function (httpResponse) {
// do what you want for error handling here
}
);
};
Note:-> (Below points solved the problem)
Only the 2nd option(postdata) in $resource.delete API was missing. We should give it as a blank {} if it is not required for API.
And delete method should return 204 Status Code in order to execute success callback.
I have two ajax calls in a service; the first one, AjaxOne gets the data I want from fields (user entered), I then want to alter the data by passing it to another ajax call, AjaxTwo to get the results I need. Both ajax calls are completely different services and can be interacted with by multiple controllers so I've placed then in their own unique Angular factory methods (could be service).
Issue is I'm thinking traditional sequential code running akin to PHP in my little semi-sudo code below (which I know will not work but just for example of how I would solve it in PHP), but I know I need to be thinking in parallel but can't quite get my head around what I need to do for the controller to be able to pass the results from AjaxOne to AjaxTwo. Keep in mind that both factory methods don't need to know of each other existence (to create no coupling and make then highly reusable).
How would I go about doing what I need to with Angular?
app.controller('app', function( $http, $scope, AjaxOne, AjaxTwo ) {
$scope.fields = '';
$scope.ajaxOne = AjaxOne;
$scope.ajaxTwo = AjaxTwo;
$scope.results = [];
$scope.process = function () {
AjaxOne.getResults($scope.fields);
$scope.results = AjaxTwo.getResults(AjaxOne.results);
};
});
Thanks.
Seems like you need to adjust your AjaxOne service to accept a callback, which would asynchronously call AjaxTwo only when AjaxOne is done doing whatever it does:
// Inside AjaxOne:
$scope.getResults = function(things, cb) {
// do something with `things`. Let's assume you're using $http:
$http({
url: "http://example.appspot.com/rest/app",
method: "POST",
data: {
"foo": "bar"
}
}).success(function(data, status, headers, config) {
cb(data);
});
};
// In your original example:
app.controller('app', function($http, $scope, AjaxOne, AjaxTwo) {
$scope.fields = '';
$scope.ajaxOne = AjaxOne;
$scope.ajaxTwo = AjaxTwo;
$scope.results = [];
$scope.process = function() {
AjaxOne.getResults($scope.fields, function(resultsFromAjaxOne) {
$scope.results = AjaxTwo.getResults(resultsFromAjaxOne);
});
};
});
You should use the promises that you get with the $http service it will give something like this
app.controller('app', function( $http, $scope, AjaxOne, AjaxTwo ) {
$scope.fields = '';
$scope.results = [];
$scope.process = function () {
AjaxOne.getResults($scope.fields).success(function(results){
AjaxTwo.getResults(results).success(function(results2){
$scope.results = results2;
});
});
};
});
for more information you can check this url http://docs.angularjs.org/api/ng/service/$http