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
Related
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){}
);
}]);
So far studying existing stackoverflow answers always helped me along, and I could always find an answer, but now I'm really stuck.
I'm building an app, which uses a directive to create calender month type boxes.
<app2directive class="column_50" jahr="2016" mon="November"></app2directive>
the directive code therefore isolates the scope and utilizes a templateUrl file to draw the calendar month
App.directive('app2directive',function( YEARS, MONTHS, DAYS){
return {
restrict:"ACE",
scope:{},
replace:true,
templateUrl:"templates/cal_directive3.html",
controller: function ( $scope, $attrs, $injector, $log, YEARS, MONTHS, DAYS) { var factoryName = "datumFactory" ;
var factoryTonnen = "jsonsrc" ;
var factoryInstance = $injector.get( factoryName ) ;
var tonnenInstance = $injector.get( factoryTonnen ) ;
var wtf = $scope.jsondata.get().then( function( promise){
console.log( "jsondata", promise ) ;
//$scope.tonnen = promise ;
}, function( error){
console.log( "error", error ) ;
}) ;
});
At the moment i use an $injector to inject a factory which runs a $http-request to read a json-file with data such as holidays or other static information specific to the chosen year and month(s).
App.factory( 'jsonsrc', function( $http, $q ){
return {
get: function(){
var deferred = $q.defer() ;
$http.get( 'termine_2016.json' )
.success(function(data, status, headers, config) {
deferred.resolve( data ) ;
}).error(function(data, status, headers, config) {
console.log( "[jsonsrc] request didnot work!" );
deferred.reject( data ) ;
}) ;
return deferred.promise ;
}
}
});
The effect of it is, that the same call to $http is run 12 times for a full year page load.
My concern is to refactor the file, so that I could preferably load the json data into the main-controller and the directive scope could inherit from the parent scope.
By its nature the call returns a promise. The directive would need means to wait for that promise to resolve before it should proceed, but right now I'm stuck on how to go about it. Thanks in advance for any pointers!
First $http is already returning a promise you can do :
return $http.get(...)
PS : you can chain promise so if you have some preprocessing to do you can do
return $http.get(..).then(function(response){
var data = response.data;
[..process..]
**return data;**
}, function(rejection){
// do some stuff
});
Second : Usually you bind data to your directive (ng-model for instance), and call services in controller (the view controller i mean). In order to handle the asynchronous loading of data you use the scope.$watch or attrs.$observe on the model to refresh your directive with the data loaded.
This enforces not to bind the directive with how your data loaded making them reusable, whatever the way you load your data or change it on your application.
Note what I put in bold, you musn't forget that or the next call to then won't have your processed data.
Finally : usually the link function provided by directive API you can just have :
link : function(scope, element, attrs){
attrs.$observe('myVariable', function(){
// refresh your internal state of the directive here
});
}
'myVariable' meanning in the call of your directive you have an attr my-variable :
and "myData" is loaded in the view controllerlike this :
jsonrc.get().then(function(response){
$scope.myData = response.data;
});
If you want to go further I suggest you to build a service for your holidays service so you load the data only at startup of your application :
App.service('MyService',['$http', function($http){
var promise = $http.get([URL]);
return function(){ // this is your service
var me = this;
promise.then(function(response){
this.data = response.data;
});
}
}]);
So now you can use in your main controller : scope.data = MyService.data; or even use it in your directive, or use some getter if you want, this is usually better but not always revelant.
If i forget anything tell me.
I think this could help.
First add the async call into a parent controller (directive's parent
controller).
Then isolate your directive's scope. And add a model to it.
scope: {
data: '=',
}
On your controller add a variable like: $scope.directiveData = {}
Assign the value of that variable with the result of the async call.
Obviously pass it to your directive: <mydirective data="directiveData"></mydirective>
Use this variable on your template with the dataname, or scope.dataon link.
Probably you will need mocked data for directiveData, or just add an ng-if to prevent crashing (when trying to show data the first time, and the directiveData is empty object).
Have your factory save the httpPromise and create it only once.
App.factory( 'jsonsrc', function( $http ){
var httpPromise;
function load () {
httpPromise = $http.get( 'termine_2016.json' )
.then(function onFullfilled(response) {
//return data for chaining
return response.data;
}).catch( function onRejected(response) {
console.log( "[jsonsrc] request didnot work!" );
//throw to reject promise
throw response.status;
});
return httpPromise;
};
function get () {
if (httpPromise) return httpPromise;
//Otherwise
return load();
};
return { load: load,
get: get
};
});
Notice that I removed the $q.defer and instead used the .then and .catch methods. The .success and .error methods have been deprecated; see the AngularJS $http Service API Reference -- deprecation notice.
You can then simplify your directive:
App.directive('app2directive',function(){
return {
restrict:"ACE",
scope:{},
replace:true,
templateUrl:"templates/cal_directive3.html",
controller: function ($scope, $attrs, jsonsrc, $log, YEARS, MONTHS, DAYS) {
jsonsrc.get().then (function (data) {
$scope.tonnen = data ;
}).catch ( function(error){
console.log( "error", error ) ;
});
})
}
});
Implemented this way, the jsonsrc factory executes the XHR GET only once, but each instantiation of the app2directive can retrieve the data for its own isolate scope.
I have an factory that gets data from my backend:
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams,$q) { //$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
$http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
data = d.abbData;
deffered.resolve();
});
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
});
A call my factory like this in my controller:
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
});
When I do a console.log($scope.abbData) outside my service call, just underneath, the result Is undifined. Why? Should not the $scope.abbData contain the data from my service after I call it?
EDIT:
You need to pass the data that should be returned into the resolve function like this:
deffered.resolve(data);
EDIT:
To get the data in the controller do this:
abbdata.async().then(function(data) {
$scope.abbData = data; //Contains data
});
Why don't you simply return that value from the async call in the first place?
You can chain promises so by attaching a success handler in your factory and returning a value from that you can simplify your code to:
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams) {
return {
async: function () {
return $http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
return d.data.abbData;
});
}
}
});
And then use it like
abbdata.async().then(function(data) {
$scope.abbData = data; //Contains data
});
if you console.log($scope.abbData) outside the service call it should show undefined, since the call is asynchronous.
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
});
console.log($scope.abbData) // this should show undefined
The console.log($scope.abbData) just after setting the abbData should show the data
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
console.log($scope.abbData) // this should show the data
});
EDIT
you can use abbData from your service call like for example
angular.module('myApp', []).controller('HomeCtrl', function($scope, abbdata){
var updateUI;
$scope.abbData = [];
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
updateUI();
});
updateUI = function(){
//do something with $scope.abbData
}
});
EDIT 2
On response to your query, I would do something like,
angular.module('myApp', [])
.controller('JobsCtrl', function($scope, $jobService) {
$scope.jobs = [];
$jobService.all().then(function(jobs) {
$scope.jobs = jobs;
});
})
.service('$jobService', function ($q, $http) {
return {
all: function () {
var deferred = $q.defer();
$http({
url: 'http://url',
method: "GET"
}).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject("connection issue");
});
return deferred.promise;
}
}
});
associated view
<body ng-app = "myApp">
<div ng-controller = "JobsCtrl">
<div ng-repeat="job in jobs track by job.id">
<a href="#/tab/jobs/{{job.id}}" class="item item-icon-right">
<h2>{{job.job_name}}</h2>
<p>DUE DATE: {{job.job_due_date}}</p>
</a>
</div>
<div>
</body>
Here the service an all function which returns a promise, i.e. it will notify when data is fetched.
in the controller the service is called and as soon the service call is resolved the $scope.jobs is assigned by the resolved data.
the $scope.jobs is used in the angular view. as soon as the jobs data are resolved, i.e. $scope.jobs is assigned, the view is updated.
hope this helps
I had a quick look, I have 2 ideas:
First theory: your service is returning undefined.
Second theory: you need to run $scope.$apply();
See this fiddler: https://jsfiddle.net/Lgfxtfm2/1/
'use strict';
var GetAbbData = function($q) {
//$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
setTimeout(function() {
//1: set dummy data
//data = [200, 201];
//2: do nothing
//
//3: set data as undefined
//data = undefined;
deffered.resolve();
}, 100);
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
};
var abbdata = GetAbbData(Q)
abbdata.async().then(function() {
console.log(abbdata.data()); //Contains data
});
I have stripped away a lot of dependencies and replaced $q with Q just for my own ease.
In the above example, I first attempted to run the code with dummy data, the console output the expected data, then I tried to not assign the data, and I get an empty array. This is why I assume that if you are seeing 'undefined' you must be explicitly setting the value to 'undefined'.
That aside, I also noticed that you were testing the result by reading directly from $scope. I know that when not inside the angular scope, doing operations on the $scope object does not necessarily happen in a timely manner, and typing $scope.$apply() usually fixes this. Usually, when using $http, angular keeps you in the appropriate scope, but you are creating your own promise using $q so this could be another potential issue.
Finally, the other two answers have pointed out that you are not using promises in the standard way. Although your code works fine, it is not normal to set your data directly onto your service and retrieve it from there. You can keep your service stateless by simply resolving your promise with the data that you want to process in the then method as shown by the answers by Anzeo and Markus.
I hope I was able to find the solution, good luck.
Dipun
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams,$q) { //$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
$http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
data = d.abbData;
deffered.resolve(data);
});
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
});
I'm trying to create a simple form with Angular, and need to render server-side validation errors. Our REST API handles validation errors with a 422 status code and a JSON array of errors in the response body.
My Controller:
.controller('MyController, function($scope, $http) {
$scope.save = function() {
var promise = $http.post('http://myapi.net/resources', $scope.data)
.success(function(data) {
// Success
})
.error(function(data, status) {
$scope.errors = data.errors;
});
$scope.errors = [];
return promise;
};
});
My Template:
<span ng-repeat="e in errors" class="error">
{{e.field}} - {{e.message}}
</span>
The errors render correctly after the first POST, but if a second POST fails, the errors sent by the server are appended in the DOM. The previous batch of errors remains, making it appear that no improvements to the data have been made. Stepping through the code, the $scope variable has the correct data, but the DOM does not. Is there a way to force ng-repeat to destroy all its previous DOM and build new ones? Or is there a better way to debug this so I can see what is going on?
As #tymeJV said it should just work. How about resetting the errors array using an empty array:
myApp.controller('MyController', function($scope, $http) {
$scope.save = function() {
// always a good idea
$scope.errors = [];
// ...
});
Here's also a plunk, maybe you can recreate it there.
Well I don't know what's going on with ng-repeat, but after 2 days I've discovered a workaround. Using a second scope variable and a watch seems to restore the correct behavior:
.controller('MyController, function($scope, $http) {
$scope._errors = [];
$scope.$watch('_errors', function(val) {
$scope.errors = val;
});
$scope.save = function() {
var promise = $http.post('http://myapi.net/resources', $scope.data)
.success(function(data) {
// Success
})
.error(function(data, status) {
$scope._errors = data.errors;
});
$scope.errors = [];
return promise;
};
});
Why this isn't necessary in the success callback is beyond me, but there you go.
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