I use the following factory to fetch data from API and store it to local variable called apiData.
app.factory('MyFactory', function($resource, $q) {
var apiData = [];
var service = {};
var resource = $resource('http://example.com/api/sampledata/');
service.getApiData=function() {
var itemsDefer=$q.defer();
if(apiData.length >0) {
itemsDefer.resolve(apiData);
}
else
{
resource.query({},function(data) {
apiData=data;
itemsDefer.resolve(data)
});
}
return itemsDefer.promise;
};
service.add=function(newItem){
if(apiData.length > 0){
apiData.push(newItem);
}
};
service.remove=function(itemToRemove){
if(apiData.length > 0){
apiData.splice(apiData.indexOf(itemToRemove), 1);
}
};
return service;
});
I inject the apiData into my controllers the following way:
$routeProvider
.when('/myview', {
templateUrl: 'views/myview.html',
controller: 'MyController',
resolve: {
queryset: function(MyFactory){
return MyFactory.getApiData();
}
}
})
app.controller('MyController', ['$scope', 'queryset',
function ($scope, queryset) {
$scope.queryset = queryset;
}
]);
Is it a good way to share a promise between different controllers or I better to use local storage or even cacheFactory?
How can I rewrite MyFactory add() and remove() methods so that I can keep my shared variable in a current state (no API update/create/delete calls needed)?
Thank you!
You should use $resource to get $promise eg: resource.query().$promise instead of $q.defer() for cleaner code. Otherwise you are good to go. You could use $cacheFactory, but you can also use local var.
Isn't your shared variable in a current state with current code?
I recommend you take a look at ui-router and its nested and abstract views, where resolved values are available to child controllers.
Related
I have an angular application that reads some data from different APIs and I wrote multiple factories to catch them each factory should use a parameter to retrieve the data which is being provided by a factory. something like this:
var eqDetail = angular.module('eqDetail', []);
eqDetail.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}]);
eqDetail.factory('eqInfoFactory', function($location, $http) {
return {
eqInfo: getEqInfo()
}
function getEqInfo() {
//routines for acquiring data and sanitize data
});
return tmp // and object contaning sanitized data
}
});
eqDetail.factory('lastInspectionDetail', ['eqInfoFactory', function($http,
eqInfoFactory) {
return {
insInfo: getInsInfo()
}
function getInsInfo() {
var eq = eqInfoFactory.eqInfo;
// get second set of data base on 'eq'
return tmp
}
}]);
eqDetail.controller('eqInfo', function($scope, eqInfoFactory) {
$scope.eq = {};
$scope.eq = eqInfoFactory.eqInfo;
console.log($scope.eq);
});
eqDetail.controller('inspectionResult', function($scope, lastInspectionDetail) {
$scope.insResult = lastInspectionDetail.insInfo;
console.log($scope.insResult)
})
the problem is that eqInfoFactory.eqInfo in the second factory cames out as undefined.
Am I using factories in the right way? and how I can inject them into each other?
Angular's dependency injection needs, if it is used with the array notation (which it definately should for at least the sake of being minification safe), every dependency - so you are missing Angular's $http Service:
//should be ['$http', 'eqInfoFactory', fn(x)...]
eqDetail.factory('lastInspectionDetail', ['eqInfoFactory', function($http,
eqInfoFactory) {
return {
insInfo: getInsInfo()
}
function getInsInfo() {
var eq = eqInfoFactory.eqInfo;
// get second set of data base on 'eq'
return tmp
}
}]);
You need to fix this line by adding the $http to minified list :
eqDetail.factory('lastInspectionDetail', ['eqInfoFactory', function($http,eqInfoFactory) {
change to this line:
eqDetail.factory('lastInspectionDetail', ['$http','eqInfoFactory', function($http,eqInfoFactory) {
I have my data factory below, which returns a promise
.factory('DataService', function($http, $document, CreatorService) {
promise = null;
jsonData = null;
return {
getJsonDataFromApi: function () {
promise = $http.get('/test');
return promise;
}
}
getJsonData: function () {
return jsonData;
},
setJsonData: function (data) {
jsonData = data;
}
}
My controller makes use of this service as follows
.controller('MainCtrl', function ($scope, $http, $uibModal, DataService, StyleService, CreatorService) {
$scope.dataService = DataService;
$scope.dataService.getJsonDataFromApi().success(function (data) {
$scope.dataService.setJsonData(data['content']);
$scope.jsonData = $scope.dataService.getJsonData();
As you can see, I'm trying to bind $scope.jsonData to the data service jsonData object via $scope.jsonData = $scope.dataService.getJsonData(); but this doesn't seem to work.
If I update the value of $scope.jsonData(), the value returned by $scope.dataService.getJsonData() doesn't change.
For example, if I do
$scope.jsonData = {};
console.log($scope.jsonData);
console.log($scope.dataService.getJsonData());
The output is
{}
Object{...}
I would have expected them to be the same. I can't seem to get my service object to update if I change my controller scope object, nor can I get my controller scope object to update if I make a change to the service object. I want to avoid using watches.
Two way data binding is for binding with your $scope and views. Not for controller and services. So if you really need something like this, then you need to $watch and set the data in service everytime it changes.
I've read in other SO answers that code that doesn't manipulate the view should be accessed via services. However, I have a function that I want to share over several Angular controllers, which accesses both $scope, $rootScope and $location:
$scope.selectBatch = function (id) {
if (!id) {
$scope.batchSelected = false;
$rootScope.job = false;
$scope.data = allData;
$location.path('/', false);
} else {
$scope.batchSelected = id;
$rootScope.job = {'BatchId': id};
var arr = [];
for (var i = 0; i < allData.length; i++) {
if (String(allData[i].BatchId) === String(id)) {
arr.push(allData[i]);
}
}
$scope.data = arr;
$rootScope.go(id, 'batch');
}
};
Ideally, in each controller I'd like to do something like:
$scope.selectBatch = services.selectBatch($scope, $rootscope, $location);
to load in this function from a service, although this feels "non-angular".
What's the "Angular" / MVC way of injecting this sort of function into multiple controllers?
From the comments on this question it appears the correct answer is to do it like this:
1. Create a service that returns a function
angular.module('myApp.services', []).service('shared', ['$location', function ($location) {
this.selectBatch = function($rootScope, $scope){
return function(id){
// Do stuff with $location, id, $rootScope and $scope here
}
}
}]);
2. Inject the service and associated functions into your controllers
.controller('myCtrl', ['shared', '$scope', '$rootScope'
function (shared, $scope, $rootScope) {
$scope.selectBatch = shared.selectBatch($rootScope, $scope);
}]);
You can then call this function using $scope.selectBatch(id) and it works as expected.
Happy to consider other answers if there are "better" ways to achieve this.
Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
You can use services to organize and share code across your app
Be aware that sending $scope as parameter to a service is not a good idea. Instead you could send the parameters the function needs in order to process something. This way your service could be more reusable.
Check this SO Question: Injecting $scope into an angular service function()
Ideally you could write a shared service like this:
app.factory('sharedService', ['$location', function($location)
{
var sharedService = {};
sharedService.selectBatch = function(batchSelected, job, data, id)
{
//Do something with batchSelected, job, data, id parameters
};
return sharedService;
}]);
Then all your controllers could look like this
app.controller('myCtrl', ['sharedService', function(sharedService)
{
$scope.batchSelected;
$scope.job;
$scope.data;
$scope.id;
$scope.selectBatch = sharedService.selectBatch($scope.batchSelected, $scope.job, $scope.data, $scope.id);
}]);
NOTE
You don't have to send the $location parameter either, since you could inject it in your shared service.
I am building an app to track movies and their info, I am new to Angular, and I cant not really sure how to pass a variable to this service.
I want the url to be a variable instead of hardcoded. whats the best way to do it?
tmdb.service('tmdbService', function($http, $q){
var deferred = $q.defer();
$http.get('https://api.themoviedb.org/3/movie/popular?api_key=jkhkjhkjhkjh').then(function(data){
deferred.resolve(data);
});
this.getMovies = function(){
return deferred.promise;
}
});
tmdb.controller("tmdbController", function($scope, tmdbService){
var promise = tmdbService.getMovies();
promise.then(function(data){
$scope.movies = data;
// console.log($scope.movies);
})
});
There is no need (in this case) to use $q.defer() because $http already returns a promise. So, your service code can be simplified to:
tmdb.service('tmdbService', function($http){
this.getMovies = function(){
return $http.get('https://api.themoviedb.org/3/movie/popular?api_key=jkhkjhkjhkjh');
}
});
Then, if you want to send a parameter you can do this:
tmdb.service('tmdbService', function($http){
this.getMovies = function(movieId){
return $http.get('https://api.themoviedb.org/' + movieId + '/movie/popular?api_key=jkhkjhkjhkjh');
}
});
In your controller, you can now send in the movieId:
tmdb.controller("tmdbController", function($scope, tmdbService){
tmdbService.getMovies(3).then(function(response){
$scope.movies = response.data;
// console.log($scope.movies);
})
});
I usually do this in the following way which I feel is neat and more readable:
tmdb.service('tmdbService', [function($http) {
return { //simple mapping of functions which are declared later
fn1: fn1,
fn2: fn3
}
function f1(param) { //param can be the url in your case
//fn code example
return $http.post(param).success(function(response) {
return response.data;
})
}
function f2(param) {
}
}]
And in your controller, using the service:
tmdb.controller('tmdbController', ['$scope', 'tmdbService', function($scope, tmdbService) {
tmdbService.f1(url).then(function(data) {
//handle the data here
})
}])
There are several ways you can go about achieving this goal. In my opinion there is really no right/wrong way; what is right is absolutely dependent on your need and this may change as you application grows.
Especially for large applications, you can define a module to manage urls and inject this module into your index application.
Another way is to define a service to manage your urls. In this case you also have to inject this service into any other services/controllers etc where you may need it.
The disadvantage to this is that this service is only available to the angular module its defined within or at most must be accessed via that module.
So using the service style here is how it may be implemented.
tmdb.service('urlService', function () {
this.urls = {
url1: 'https://api.themoviedb.org/3/movie/popular?api_key=jkhkjhkjhkjh',
url2: 'anotherUrl'
};
});
tmdb.service('tmdbService', ['$http', '$q', 'urlService', function ($http, $q, urlService) {
var deferred = $q.defer();
$http.get(urlService.url.url1).then(function (data) {
deferred.resolve(data);
});
this.getMovies = function () {
return deferred.promise;
}
}]);
Again there is no absolute right/wrong way; it depends.
I hope you find this helpful.
Cheers!
I have the worlds simplest controller which i want access to $scope but it is "undefined" and I cannot, for the life of me work out why, however all the functions are called corectly, the DataService, etc is working perfectly, just i have no access to $scope?!
controller code is below
app = angular.module("windscreens", []);
app.controller('DamageCtrl', function($scope, DataService) {
$scope.setDamageLocation = function(location) {
DataService.getDamage().setLocation(location);
};
$scope.isLocation = function(requiredLocation) {
return DataService.getDamage().getLocation() === requiredLocation;
};
$scope.progress = function() {
};
});
To access a property on the scope from your HTML template you only need to use the property name, you don't need to write $scope with it.
Example:
<button ng-click="progress()"></button>
In your javascript you will only have access to the $scope inside the controller and its functions. If you call an external resource, for example: DataService module, it won't have access to the $scope unless you pass it as an argument explicitly.
I managed to get it working using the alternative syntax. As detailed below, still not sure why it wasn't working but hey hum
app.controller('DamageCtrl', ['$scope', 'DataService',
function($scope, DataService) {
$scope.setDamageLocation = function(location) {
DataService.getDamage().setLocation(location);
};
$scope.isLocation = function(requiredLocation) {
return DataService.getDamage().getLocation() === requiredLocation;
};
$scope.progress = function() {
console.log($scope);
};
}
]);