I have a problem to initialize controller in AngularJS.
Below is the process which I want to implement.
Get data from mongoDB by $http before DOM is ready.
By Using the data, some div element should be created using ng-repeat.
But the problem is that the view is rendered before controller gets data from $http.
So I searched all over the stack-overflow and google, and found about ui-router's resolve function.
Below is my ui-router code.
.state('floor', {
url: '/floor/:domainId',
templateUrl: 'modules/floor/views/floor.client.view.html',
controller: 'FloorController',
resolve: {
initData: ['$http', '$stateParams', function($http, $stateParams) {
return $http.get('/users/getFloor/' + $stateParams.domainId).success(function(user) {
return $http.get('/users/' + user._id + '/data/get').success(function(data) {
return data;
});
});
}]
}
})
The first $http is to get user id from domain id. (e.g. User can connect to /floor/my_personal_domain_address), and the second $http is what I need for initial data.
This is my Controller code.
angular.module('floor').controller('FloorController', ['$scope', 'initData',
function($scope, initData) {
console.log(initData);
}]);
Small tip or search keyword or anything will be very thankful for me.
I'm still learning AngularJS so just give me a small tip please.. Thank you!
UPDATE
This was my misunderstanding of how controller works. As some people commented, I didn't have to use resolve to retrieve data before controller initialized. The problem was occurred because I declared array variable used in ng-repeat as [] for the first time and client shows error. If I declare the variable after I get value from database, controller data-bind it to view properly.
So the problem is solved. Thank you all for valuable comments and answers.
UPDATE 2
Anyway, ui-router's resolve can return a value even though it is promise. I worked for it for some hours, and what I found out is that if I return $http promise in resolve, controller can get its data when successful. But if $http error is occurred in resolve, nothing can catch it. So if there's someone who wants to use resolve to send data to controller before it is initialized, I think it must be used with care.
Get data from mongoDB by $http before DOM is ready.
In this case probably the simpler solution would be not to make any tricky $http requests before Angular initialization but instead just to embed your data as JavaScript global variable into the main HMTL page just before loading of angular.min.js script.
I don't know if I get your question correctly, but this should help you:
(from the ui-router docs https://github.com/angular-ui/ui-router/wiki)
// Another promise example. If you need to do some
// processing of the result, use .then, and your
// promise is chained in for free. This is another
// typical use case of resolve.
promiseObj2: function($http){
return $http({method: 'GET', url: '/someUrl'})
.then (function (data) {
return doSomeStuffFirst(data);
});
},
So you'd have to use .then() instead of .success() and it should work.
Related
I have a service, which should save me the specific data that I load from a JSON file. I want the data to show on the page as soon as it's received.
I created a $scope variable to be equal to the data in the service, the data is not shown immediately on the page.
I only achieved my goal when using: angular.copy(response.data, this.animals),
but I do not understand why it is not working when I am using: this.animals = response.data. I would like to know why this is happening and what is the difference.
module.service("animalService", function($http) {
this.animals=[];
$http.get('animals.json').then(function(response) {
//this.animals = response.data ----> not working
angular.copy(response.data, this.animals)
});
});
module.controller("animalController", function($scope, animalService) {
//$scope.animals is what I use to create a table in html
$scope.animals = animalService.aniamsl;
});
You are not doing it right, try:
module.service("animalService", function($http) {
return $http.get('animals.json')
});
module.controller("animalController", function($scope, animalService) {
animalService.then(function(res){
$scope.animals = res.data
});
});
any http response returns promise, and what it means is that the data would come asynchronously. As per my understanding using angular.copy triggers a digest cycle and so the changes are detected but it's not at all a good practice. Prefer promise handling as I have shown in the above code
Update:
Since the variable is populated as a promise and it is to be used by other controller , I would suggest to use events such as $rootScope.emit and $rootScope.on so that the controllers are notified about the change in value after $http is completed
I want to build a multi step wizard with ajax calls in between:
I currently use ui.router for views of the wizard steps which works fine.
On the first page the users enters some data e.g. playerid.
On the second page i want to display some data pulled from the server corresponding to that playerid.
How should i structure that? Because i read that controllers should only write to the model, but i need to read playerid the user entered to make the ajax call..?
Here is a Plunk how i do it right now:
http://plnkr.co/edit/4ZEdYHUqovn2YfkUpp2y?p=info
I personally would have done it this way (plunker):
The routing :
$stateProvider
.state('view1', {
url: "/view1",
templateUrl: "view1.html",
controller:"WizardCtrlStep1"
})
.state('view2', {
url: "/view2",
templateUrl: "view2.html",
controller:"WizardCtrlStep2",
params:{
playerId:null
},
resolve:{
player:function($http, $stateParams){
//you can use the player id here
console.log($stateParams.playerId);
return $http.get("test.json");
}
}
})
I really really like to have a single controller per state. It avoid thing to get messy.
I also use a resolve to do the ajax call before the step2 view loading.
Here is the controller of the 2nd step
//I inject the "player" resolved value
controller('WizardCtrlStep2', function($scope, player) {
$scope.name = 'World';
//to access the result of the $http call use .data
$scope.player = player.data;
})
And finally the HTML
<input type="text" ng-model="main.playerId">
<button ui-sref="view2({playerId:main.playerId})">proceed</button>
Here i give ui-sref a param for "playerId" that will be used in the resolve function.
Hope it was clear, if you have any question feel free to ask.
I have a basic factory in my app that handles API calls. Currently I'm using the form:
.factory('apiFactory', function($http){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
return result;
});
},
getReport: function(id) {
return $http.get(url+'report/'+id).then(function(result) {
return result;
});
}
}
})
And in my controller I'm handling the promise like so:
.controller('exampleController', function($scope, apiFactory) {
apiFactory.getReports().then(
function(answer) {
if (answer.status==200){
if (answer.data.status == "error"){
// DISPLAY ERROR MESSAGE
console.log(answer.data.msg);
}
} else{
// THROW error
console.log('error: ', answer);
}
},
function(error){
console.log('error: ', answer);
}
);
}
}
})
It seems I could move the promise handling to my Factory instead of doing it in my controller, but I'm not sure if that would have any benefits others than a smaller controller.
Could somebody explain the best practices regarding this pattern?
It is ultimately up to you how much data you want to provide to the caller of the service. If needed, you could definitely return the HTTP response object to the caller, and have them process the response (which, btw, is always HTTP 2xx, if the promise is resolved rather than rejected).
But if you want to isolate the caller from the specifics of how the data got there (maybe it was cached, or supplied via another mechanism), and if you need to post-process the data, then it is advisable to handle the response in the service.
Here's an example:
.factory("apiService", function($http, $q){
var url = 'http://192.168.22.8:8001/api/v1/';
return {
getReports: function() {
return $http.get(url+'reports').then(function(result) {
var data = result.data;
if (data === "something I don't accept"){
return $q.reject("Invalid data");
}
var processedData = processData(data);
return processedData;
})
.catch(function(err){
// for example, "re-throw" to "hide" HTTP specifics
return $q.reject("Data not available");
})
},
// same idea for getReport
}
});
Then the controller wouldn't need to care about the underlying mechanism - all it gets is data or a rejection.
.controller('exampleController', function($scope, apiService) {
apiService.getReports()
.then(function(reports){
$scope.reports = reports; // actual reports data
});
})
Off-topic:
Notice how I changed the name of the service from "apiFactory" to "apiService". I wanted to point that out to remove a possible misconception. Whether you use .factory or .service or .value what you get as an injectable is always a service instance. .factory is just a mechanism of how this service is instantiated, so the name "apiFactory" is a misnomer. The only "factory" here is a function that you register with .factory (which could be anonymous, of course):
.factory("fooSvc", function fooSvcFactory(){
return {
getFoo: function(){...}
}
})
Better to keep all the data fetching inside the factory. This keeps the controller free from state, and it no longer cares how your factory works. If you change how you get data (e.g. not using $http) your controller shouldn't care, as it just calls getReport() and
A good explanation (see "Resolving Model data, no callback arg binding in Controllers"):
http://toddmotto.com/rethinking-angular-js-controllers/
Short answer: Handle promises in Factory.
Why?
Problems you face if you handle promises in Controller:
Let's say you have 5 Controllers that use the same Factory. Now let's say that you want to handle the errors when the promise does not get resolved correctly. So in the first controller, you write an error callback (or the catch(exception) more precisely, as you are dealing with promises), that shows you an alert message with the error. When the promise fails, this controller shows an alert with the error message. So far, so good? Right. But wait! What about the other 4 controllers? You haven't handled the errors in them. So now you end up copying the error handling code from the first controller & pasting it in the rest of the 4 controllers.
Now the fun starts. Imagine that you want to change your logic in the error state. Maybe you want to just log the error in console, or show a toaster message perhaps. So you go to the first controller & update the code. You think you are done? NO!!! There are 4 other controllers that are showing the alert message (remember that you pasted the code from the first controller previously???). So now, you try to update the rest of the controllers by pasting the new error message logic. Think about all the controllers that you need to update, if you have more!!! Tiring, isn't it???
Why to resolve promise in Factory:
Now let's say that you write your error handling logic in your service and all the controllers are using this service. You have written code to show an alert in case of a promise that fails in this service. All controllers will now start showing this alert, in case of a broken promise. In future, if you wish to update your error logic, you simply update this error logic in your service & all your controllers will start using this updated logic automatically, without any more effort on your part. This way, you have saved yourself tons of time.
So think about this:
1 change in Factory vs 1 change in all Controllers (which maybe 1 or more)
I would definitely vouch for 1 change in Factory as that helps reuse my logic with just a simple change and only once. All my controllers will start using the new logic. Controllers are supposed to be slim, lean & clean. Factories & Services are supposed to be reusable. For this reason of reusability, I strongly suggest you handle promise in Factory/Service.
I request some data from Firebase using AngularFire like below.
$rootScope.userTags = $firebase($fbCurrent.child('tags')).$asArray();
On logging this data in following ways I get the outputs as in the image below. So,
console.log($rootScope.userTags);
console.log($rootScope.userTags[0]);
$rootScope.userTags.$loaded().then(function () {
console.log($rootScope.userTags[0]);
});
In first log statement you can notice an $FirebaseArray object with five elements. In second log statement when I try to access one of the elements, I get undefined although you can clearly see those elements in the un$loaded array. Now in the last log statement, calling $loaded lets me access those elements without any issues.
Why does it behave this way? And is there a way I can access those elements, since those are already there, without calling $loaded on the array?
The $asArray() or $asObject() methods are asynchronous actions. The data has to be downloaded from Firebase before it can be accessed. This is just the nature of asynchronous data and not anything specific to Firebase.
The way we handle this issue of asynchrony is through the $loaded promise.
It seems though the real issue here ensuring data is loaded upon instantiation of the controller. You can solve this be resolving the data in the router.
Resolve require a promise to be returned. When the user routes to that page, Angular will not load the page until the promise has been resolved. The data resolved from the promise will injected into the controller.
.config(['$routeProvider',
function($routeProvider) {
$routeProvider
.when('/resolve', {
templateUrl: 'resolve.html',
controller: 'ResolveCtrl',
resolve: {
// data will be injected into the ResolveCtrl
data: function($firebase) {
// resolve requires us to return a promise
return $firebase(new Firebase('your-firebase')).$asArray().$loaded();
}
}
})
}
])
Now that we are resolving the data in the route we can access it in our controller.
.controller('ResolveCtrl', function($scope, data) {
$scope.userTags = data;
$scope.userTags[0]; // data has been resolved from the promise
});
I have an HTTP resource that returns a JSON list of top 10 entities from a database.
I call it this way:
var filter= "john";
var myApp = angular.module('myApp', []);
myApp.controller('SearchController', ['$scope','$http', function ($scope, $http) {
$http.get('/api/Entity/Find/' + filter). //Get entities filtered
success(function (data, status, headers, config) {
$scope.entities = data;
}).
error(function () {
});
}]);
It works!
But... how can I change the filter variable in order to change the query?
Should I rewrite the whole controller to get this to work?
Update
Sorry for the lack of clarity in my question. When I asked this I couldn't undertand anything of AngularJS.
My original intent was to get the variable $http injected, without relying on creating a controller for that.
Thanks for everyone.
A likely better method
If you don't want to get it inside a controller, you could have it injected into a recipe (ex, provider, factory, service):
https://docs.angularjs.org/guide/providers
myApp.factory('getStuff', ['filter', '$http', function (filter, $http) {
//Blah
}]);
If you want to get an instance of $http outside of any angular struct, you can do what's shown below.
The method given by Dennis works; however, it does not work if called before angular has been bootstrapped. Also, it seems like Derek has an error with Dennis' method because he does not have jquery.
The solution that Exlord mentioned is better, as it does not have that problem, and is more proper:
$http = angular.injector(["ng"]).get("$http");
Explained:
The angular injector is an:
object that can be used for retrieving services as well as for dependency injection
https://docs.angularjs.org/api/ng/function/angular.injector
The function angular.injector takes the modules as a parameter and returns an instance of the injector.
So in this case you retrieve an injector for the ng module (angular's), and then retrieve the service $http.
Note:
One thing to keep in mind when using injector like this is that in my own findings it seems you need to make sure you include modules in the inject which what you are "getting" will need. For example:
angular.injector(['ng', 'ngCordova']).get('$cordovaFileTransfer')
Regarding to your question "... call $http.get from outside controller" you can do the following:
... ANYWHERE IN YOUR CODE
var $http = angular.element('html').injector().get('$http');
$http.get(...).success(...);
ANYWHERE IN YOUR CODE ...
See official docs from angular: angular $injector docs :
The get(name); method Returns an instance of the service.