Everything works fine but I have problem getting source from service.
I have SERVICE code below.
app.factory('myService',
function($rootScope, ResourceData) {
var result = {};
var data = ResourceData.query();
result.getData = function() {
// problem here
}
return result;
});
And CONTROLLER contain code.
app.controller('myController', ['$scope', 'myService',
function myController($scope, myService) {
$scope.data = myService.getData();
});
My problem is if I have function in my SERVICE like this
result.getData = function() {
return data;
}
Everything works fine but I need to filter that data before I get it
If I change body like this I get an empty array the problem seems like it is from AngularJS.
If I create static array it works.
result.getData = function() {
var arr = [];
angular.forEach(data, function(item, key) {
// simple filter
if(item.ID > 10) {
return;
}
else {
arr.push(item);
}
});
return arr;
}
The result of "ResourceData.query()" is asynchronous:
When the data is returned from the server then the object is an instance of the resource class... It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
When the page first loads, I'm guessing that the controller runs before the data is returned from the server, so the first time getData() is called, data is probably not yet populated with any data, so you get an empty array.
One solution to your problem would be to filter the data in the view/HTML with an Angular filter, rather than in the service.
<div ng-repeat="item in data | filter:myFilter">
{{item.ID}}: {{item...}}
</div>
Then in your controller:
$scope.myFilter = function(item) {
return item.ID <= 10;
}
Related
As the title says, I have a problem with reference switching.
My html:
div ng-repeat="data in parseurl">
{{data.url}}
</div>
In my JS code, I'm trying to do two things. The first step is to grab the data off a server and put it into an array (called allsongs). Afterwards, I parse the data and put it into another array (parseurl).
var app = angular.module("write", []);
app.controller("Ctrl", ['$scope', '$http', function($scope, $http){
$scope.allsongs = [];
$scope.parseurl = [];
$scope.getstuff = function(){
$http.get("my link here").then(function(response){
$scope.allsongs = response.data;
}); //step one -> this works!
$scope.parser(); //step two
};
$scope.parser = function()
{
for(i=0;i<$scope.allsongs.length;i++) {
for(var key in $scope.allsongs[i]) {
var value = $scope.allsongs[i][key];
var object = {Url : value.Url}; //this does contain the correct info I want
$scope.parseurl.push(object);
}
$scope.getstuff();
}]);
So what is happening is that, if I ng-repeat on allsongs, then I do get a bunch of un-parsed urls. But if I ng-repeat on parseurl, then I get nothing. Obviously the reference isn't changing, but how can I do it?
$scope.parser() needs to be called after the data was recived. Put it into your promise callback function like in the following example. Please note that $http is an asynchronous function. In that way $scope.parser() was executed before your request has been finished.
$scope.getstuff = function(){
$http.get("my link here").then(function(response){
$scope.allsongs = response.data;
$scope.parser();
});
};
I have started recently on Ionic and it requires a good grasp of AngularJS, which has some things I do not understand yet.
Basically, I have a search field, where I bind the property formdata.searchText and trigger the search function each time the value is changed. The callback is a collection of artists that fill my list.
<input type="search" ng-model="formdata.searchText" placeholder="Search"
ng-change="search()">
In my controller, this property is defined and on change, the method search gets called. Search reaches out to the factory for an API call.
.controller('SearchController', function($scope, SpotifyFactory) {
$scope.formdata = [{
searchText: ""
}];
$scope.search = function(){
$scope.searchResults = SpotifyFactory.searchArtist($scope.formdata.searchText);
};
})
This works well so far. The call is being made to the Spotify API and it returns the results of found artists based on my searchterm.
My data:
When I console.log the $scope.searchResults in my controller, it contains my desired values. But I believe that happens because console.log is being executed after a the promise is done, which makes it possible for the data to be displayed. If I console.log the object in my controller, and say for example
console.log($scope.searchResults.artists);
It returns an undefined, while Im still able to access the other properties.
Below is how I make the API call in the factory.
angular.module('starter.services', [])
.factory('SpotifyFactory', function($http){
var foundArtists = function($searchTerm) {
var baseUrl = "https://api.spotify.com/v1/search?query=" + $searchTerm + "&type=artist&offset=0&limit=20";
var searchResults = [];
searchResults.$promise = $http.get(baseUrl).then(function(response){
angular.copy(response.data, searchResults);
return searchResults;
});
return searchResults;
}
return {
searchArtist : foundArtists
}
}
)
My question is, how can I ensure that the data gets returned, after the call of the API is done.
Use a promise
.factory('SpotifyFactory', function($http){
var foundArtists = function ($searchTerm) { return new Promise(function(resolve, reject) {
var baseUrl = "https://api.spotify.com/v1/search?query=" + $searchTerm + "&type=artist&offset=0&limit=20";
var searchResults = [];
searchResults.$promise = $http.get(baseUrl).then(function(response){
angular.copy(response.data, searchResults);
resolve(searchResults);
});
}});
return {
searchArtist : foundArtists
}
}
Then call it from where you want
SpotifyFactory.searchArtist('madonna').then((data) {
$log.info('Data retrieved!', data);
// do things you want to do after results are found
}).catch( ... error function);
I'm looking for some information on the best way to retrieve data from a local JSON file and handle the response. After browsing through Stack Overflow, I have some mixed thoughts as I've seen multiple ways of doing the same thing (although no explanation on why one may or may not be preferred).
Essentially, I have an Angular app that is utilising a factory to retrieve data from a JSON file; I'm then waiting for the response to resolve in my controller before using it in my html file, similar to the below:
Option 1
Factory:
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile);
}
}
}]);
Controller:
comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {
Info.retrieveInfo().then(function(response) {
$scope.info = response.data;
});
}]);
My main point of contention is figuring out when it's best to wait for the response to resolve, or if it even matters. I'm toying with the idea of having the factory return the fulfilled promise, and wait for the controller to retrieve the data also. In my view, it's best to abstract all data retrieval out of the controller and into the factory, but I'm not sure if this extends to waiting for the actual data to be returned within the factory itself. With this in mind, I'm confused about whether to opt for option 1 or option 2 and would really appreciate some feedback from more experienced/qualified developers!
Option 2
Factory:
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile).then(function(response) {
return response.data;
});
}
}
}]);
Controller:
comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {
Info.retrieveInfo().then(function(response) {
$scope.info = response;
});
}]);
Thank you for any input/suggestions in advance!
It depends on what your controller is expecting and how you set up your application. Generally, I always go with the second option. Its because I usually have global error or success handlers in all api requests and I have a shared api service. Something like below.
var app = angular.module('app', []);
app.service('ApiService', ['$http', function($http) {
var get = function(url, params) {
$http.get(url, { params: params })
.then(handleSuccess, handleError);
};
// handle your global errors here
// implementation will vary based upon how you handle error
var handleError = function(response) {
return $q.reject(response);
};
// handle your success here
// you can return response.data or response based upon what you want
var handleSuccess = function(response) {
return response.data;
};
}]);
app.service('InfoService', ['ApiService', function(ApiService) {
var retrieveInfo = function() {
return ApiService.get(retrievalFile);
/**
// or return custom object that your controller is expecting
return ApiService.get.then(function(data) {
return new Person(data);
});
**//
};
// I prefer returning public functions this way
// as I can just scroll down to the bottom of service
// to see all public functions at one place rather than
// to scroll through the large file
return { retrieveInfo: retrieveInfo };
}]);
app.controller('InfoController', ['InfoService', function(InfoService) {
InfoService.retrieveInfo().then(function(info) {
$scope.info = info;
});
}])
Or if you are using router you can resolve the data into the controller. Both ngRouter and uiRouter support resolves:
$stateProvider.state({
name: 'info',
url: '/info',
controller: 'InfoController',
template: 'some template',
resolve: {
// this injects a variable called info in your controller
// with a resolved promise that you return here
info: ['InfoService', function(InfoService) {
return InfoService.retrieveInfo();
}]
}
});
// and your controller will be like
// much cleaner right
app.controller('InfoController', ['info', function(info) {
$scope.info = info;
}]);
It's really just preference. I like to think of it in terms of API. What is the API you want to expose? Do you want your controller to receive the entire response or do you want your controller to just have the data the response wraps? If you're only ever going to use response.data then option 2 works great as you never have to deal with anything but the data you're interested in.
A good example is the app we just wrote where I work. We have two apps: a back-end API and our front-end Angular application. We created an API wrapper service in the front-end application. In the service itself we place a .catch for any of the API endpoints that have documented error codes (we used Swagger to document and define our API). In that .catch we handle those error codes and return a proper error. When our controllers/directives consume the service they get back a much stricter set of data. If an error occurs then the UI is usually safe to just display the error message sent from the wrapper service and won't have to worry about looking at error codes.
Likewise for successful responses we do much of what you're doing in option 2. In many cases we refine the data down to what is minimally useful in the actual app. In this way we keep a lot of the data churning and formatting in the service and the rest of the app has a lot less to do. For instance, if we need to create an object based on that data we'll just do that in return the object to the promise chain so that controllers aren't doing that all over the place.
I would choose option two, as it your options are really mostly the same. But let see when we add a model structure like a Person suppose.
comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';
return {
retrieveInfo: function() {
return $http.get(retrievalFile).then(function(response) {
//we will return a Person...
var data = response.data;
return new Person(data.name, data.age, data.gender);
});
}
}
}]);
This is really simple, but if you have to map more complex data into object models (you retrieve a list of people with their own items... etc), that's when things get more complicated, you will probably want to add a service to handle the mapping between data and models. Well you have another service DataMapper(example), if you choose your first option you will have to inject DataMapper into your controller and you will have to make your request through your factory, and map the response with the injected service. And then you probably say, Should I have all this code here? ... Well probably no.
That is an hypothetical case, something that count a lot is how you feel structuring your code, won't architecture it in a way you won't understand. And at the end take a look at this: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) and research more information about this principles but focused to javascript.
Good question. A couple of points:
Controllers should be view centric versus data centric therefore you
want remove data logic from the controller and rather have it focus
on business logic.
Models (M in MVC) are a data representation of your application and
will house the data logic. In Angular case this would be a service
or factory class as you rightfully pointed out. Why is that well for
example:
2.1 AccountsController (might have multiple data models injected)
2.1.1 UserModel
2.1.2 AuthModel
2.1.3 SubscriptionModel
2.1.4 SettingsModel
There are numerous ways to approach the data model approach, but I would say your service class should be the data REST model i.e. getting, storing, caching, validating, etc. I've included a basic example, but suggest you investigate JavaScript OOP as that will help point you in the right direction as to how to build data models, collections, etc.
Below is an example of service class to manage your data.Note I have not tested this code but it should give you a start.
EXAMPLE:
(function () {
'use strict';
ArticleController.$inject = ['$scope', 'Article'];
function ArticleController($scope, Article) {
var vm = this,
getArticles = function () {
return Article.getArticles()
.then(function (result) {
if (result) {
return vm.articles = result;
}
});
};
vm.getArticles = getArticles;
vm.articles = {};
// OR replace vm.articles with $scope if you prefer e.g.
$scope.articles = {};
$scope.userNgClickToInit = function () {
vm.getArticles();
};
// OR an init on document ready
// BUT to honest I would put all init logic in service class so all in calling is init in ctrl and model does the rest
function initArticles() {
vm.getArticles();
// OR chain
vm.getArticles()
.then(getCategories); // doesn't here, just an example
}
initArticles();
}
ArticleModel.$inject = ['$scope', '$http', '$q'];
function ArticleModel($scope, $http, $q) {
var model = this,
URLS = {
FETCH: 'data/articles.json'
},
articles;
function extract(result) {
return result.data;
}
function cacheArticles(result) {
articles = extract(result);
return articles;
}
function findArticle(id) {
return _.find(articles, function (article) {
return article.id === parseInt(id, 10);
})
}
model.getArticles = function () {
return (articles) ? $q.when(articles) : $http.get(URLS.FETCH).then(cacheArticles);
};
model.getArticleById = function (id) {
var deferred = $q.defer();
if (articles) {
deferred.resolve(findArticle(id))
} else {
model.getBookmarks().then(function () {
deferred.resolve(findArticle(id))
})
}
return deferred.promise;
};
model.createArticle = function (article) {
article.id = articles.length;
articles.push(article);
};
model.updateArticle = function (bookmark) {
var index = _.findIndex(articles, function (a) {
return a.id == article.id
});
articles[index] = article;
};
model.deleteArticle = function (article) {
_.remove(articles, function (a) {
return a.id == article.id;
});
};
}
angular.module('app.article.model', [])
.controller('ArticleController', ArticleController)
.service('Article', ArticleModel);
})()
I am working on an app that is supposed to filter and sort out data from two json files. The app will have two tables that compare and contrast this data using ngRepeat myData. So far, the top table is already requesting a json file:
app.controller('tableTopController', function ($scope, $http) {
$http.get('first.json').success(function(response){
$scope.myData = response; });
The bottom table is supposed to read data from my second json file: second.json.
Try using $q.all() to resolve both promises and execute a callback function when both are successful. For more info, see the docs.
var promises = [];
promises.push(getFirstJson());
promises.push(getSecondJson());
$q.all(promises).then(function (results) {
var firstJson = results[0];
var secondJson = results[1];
});
function getFirstJson() {
return $http.get(...);
}
function getSecondJson() {
return $http.get(...);
}
If you want to wait for the first call to complete before you get the second file and if you want to ensure everything is loaded before comparing and contrasting:
app.controller('theController', function ($scope, $http) {
$http.get('first.json').success(function(response){
$scope.firstData = response;
$http.get('second.json').success(function(response1){
$scope.secondData = response1;
//add any other logic you need to do here to compare & contrast
//or add functions to $scope and call those functions from gui
});
});
});
Or, call them sequentially but then you need to ensure your comparing and contrasting can't start until both are loaded:
app.controller('theController', function ($scope, $http) {
$http.get('first.json').success(function(response){
$scope.firstData = response;
});
$http.get('second.json').success(function(response1){
$scope.secondData = response1;
});
//add any other logic you need in functions here to compare & contrast
//and add those functions to $scope and call those functions from gui
//only enabling once both firstData and secondData have content
});
I added this filter to my angular app to remove certain strings from loaded data:
.filter('cleanteam', function () {
return function (input) {
return input.replace('AFC', '').replace('FC', '');
}
});
<h2 class="secondary-title">{{teamDetails.name | cleanteam }}</h2>
You can see the error here:
http://alexanderlloyd.info/epl/#/teams/61
my controller looks a bit like this:
.controller('teamController', function($scope, $routeParams, footballdataAPIservice) {
$scope.id = $routeParams.id;
$scope.team = [];
$scope.teamDetails = [];
//$scope.pageClass = '';
$scope.$on('$viewContentLoaded', function(){
$scope.loadedClass = 'page-team';
});
footballdataAPIservice.getTeam($scope.id).success(function (response) {
$scope.team = response;
});
footballdataAPIservice.getTeamDetails($scope.id).success(function (response) {
$scope.teamDetails = response;
});
})
Any reason why this might happen? Is it because teamDetails.name is not declared within an ng-repeat loop?
By looking at your code it seems that you didn't handle the case of undefined, while your teamDetails.name can be undefined undefined until it fetch data from service.
Because when you trying to fetch data form service through ajax, your input variable is undefined, when filter code tries to apply .replace method on undefined object, it will never work ( .replace() only works on string)
Checking if your teamDetails.name object is defined or not is good
idea, because filter runs on every digest cycle.
Filter
.filter('cleanteam', function () {
return function (input) {
return angular.isDefined(input) && input != null ? //better error handling
input.replace('AFC', '').replace('FC', ''):'';
}
});
Hope this could help you, Thanks.
Looks to me like the filter is trying to execute before your async call has finished.
Try setting teamDetails to null when you initialize the controller and use an ng-if to prevent the DOM elements from loading before your data arrives:
$scope.id = $routeParams.id;
$scope.team = [];
$scope.teamDetails = null;
<h2 class="secondary-title" ng-if="teamDetails">{{teamDetails.name | cleanteam }}</h2>
This will ensure that the filter won't execute before the async call has populated the teamDetails object.
More on ng-if: https://docs.angularjs.org/api/ng/directive/ngIf