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) {
Related
How can I access $scope data from view to my factory in angularjs? I can access $scope.items from my controller, but when I need to use it in my factory to use the data and generate a pdf I cannot access it.
angular.module('myApp', [])
.controller('myCtrl', function($scope, $http, testFactory) {
$scope.link = "http://localhost:3450/loading.html";
testFactory.all().then(
function(res){
$scope.link = res;
},
function(err){
console.log(err);
}
);
})
.factory('testFactory', function($q){
var pdfInfo = {
content: [
//data should be here...
]
};
var link = {};
function _all(){
var d = $q.defer();
pdfMake.createPdf(pdfInfo).getDataUrl(function(outputDoc){
d.resolve(outputDoc);
});
return d.promise;
}
link.all = _all;
return link;
});
I used factory when I click the generate button from my view, it will wait until the pdf is generated. Coz when I did not do it this way before, I need to click the button twice just to get the pdf generated.
You can just pass the data to your factory as a
function parameter.
angular.module('myApp', [])
.controller('myCtrl', function($scope, $http, testFactory) {
var pdfInfo = {
content: $scope.items
};
$scope.link = "http://localhost:3450/loading.html";
testFactory.all(pdfInfo).then(
function(res) {
$scope.link = res;
},
function(err) {
console.log(err);
}
);
})
.factory('testFactory', function($q) {
var link = {};
function _all(pdfInfo) {
var d = $q.defer();
pdfMake.createPdf(pdfInfo).getDataUrl(function(outputDoc) {
d.resolve(outputDoc);
});
return d.promise;
}
link.all = _all;
return link;
});
I did it. I forgot to send the $scope.items to my factory. So what i did is I added testFactory.all($scope.items) in my controller instead of just plain testFactory.all().
Then in my factory,
I used function _all(value), so I can used the values passed by the views through controller. I am not sure if this is the proper way, but it works. Please suggest good practice if you have.
It is a bad practice to move around $scope to other services, as they may change it and effect your controller logic. It will make a coupling between controllers to other services.
If your factory requires data from the controller, it is better to just pass those parameters to the factory's function.
EDIT: I see you managed to do that, and yes - passing $scope.items is the preferred way (and not, for example, passing $scope).
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 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.
I am using a directive to populate a chart using the AmCharts API. The function that is currently being used lists out the data that is populating the form in JSON format as a parameter in the function. I want to be able to store this as a variable so I can have a separate file for the JSON. I know you probably have to use $http to get the json, but I am unsure how you would connect this to the directive.
var myapp = angular.module('tmn_portfolio', []);
myapp.directive('loadPortfolio',
function () {
return {
restrict: 'E',
replace:true,
template: '<div id="chartdiv" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
chart = AmCharts.makeChart("chartdiv", I WANT THIS TO BE A VARIABLE TO JSON FILE);
};
initChart();
}//end watch
}
}) ;
A better solution than scope: true would be to pass the data to the directive in an attribute. The markup will look like this:
<load-portfolio my-data="jsonData" ></load-portfolio>
That element will be replaced by your template. In the directive:
myapp.directive('loadPortfolio',
function () {
return {
restrict: 'E',
replace:true,
scope: {
myData: '='
},
template: '<div id="chartdiv" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
chart = AmCharts.makeChart("chartdiv", scope.myData);
};
initChart();
}//end watch
}
});
The equals sign is a two way binding between the property myData on the directive scope, and the property jsonData on the scope of your controller (the "model" in angular terms). Any time the data changes in the controller, it will also change in the directive, and your UI will update to reflect that.
Now you just have to get the json data, right? You are right, you should probably use $http to do this, and what that will look like will depend on your specific implementation, but the important part is that once you populate jsonData, your directive will be updated to reflect that. I typically initialize models that will be populated asynchronously in my controller. A very simple version might look something like this:
myapp.controller('myController', ['$scope', '$http', function($http, $scope) {
$scope.jsonData = {}; // or '' - depending on whether you actually want the JSON string here.
$http({
url: '/data.json',
method: 'GET'
}).
then(function(r) {
$scope.jsonData = r.data;
});
}]);
I think that to do it properly you should look into routing and the resolve property of routes which allows you to retrieve the data before your controller loads, and pass it as an argument to the controller, but that is really up to you.
Update: Like the other answer, I do recommend using a factory/service for calls to your server or API, instead of using $http directly in the controller like in my example. Just trying to keep it simple here. Angular code is more fun when well-organized.
You need a factory or service to first request your JSON file and store it as a variable:
var app = angular.module('app', []);
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]);
app.factory('data', function($http) {
var data = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('file.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return data;
});
app.controller('MainCtrl', function( data,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
data.async().then(function(d) {
$scope.jsonData = d; ;
});
});