I am new to AngularJS and am having trouble to get resource's promise to re-render the view when the data is loaded. This results in the {{}} getting replaced with blanks (from the promise) but then the view just stays that way.
I have seen a lot of examples of problems with promises being solved with $apply, $digest, $watch etc., and am open to these suggestions be would love to know the root problem with my approach. Here is my controller code
<div ng-controller='pook'>
<p>There are {{posts.length}} posts</p>
<div ng-repeat='post in posts'>
<h3>{{post.title}}</h3>
<div>{{post.text}}</div>
<a href='/readPost/{{post.id}}'>More</a>
| -
<a href='/editPost/{{post.id}}'>Edit</a>
| -
<a href='/deletePost/{{post.id}}'>Delete</a>
</div>
</div>
<script type='text/javascript'>function pook($scope, $http, $route, $routeParams, $resource)
{
/*Approach #1 does work
$http.post('/').success(function(data, status, headers, config)
{
$scope.posts = data.posts;
});
*/
var User = $resource('/');
/*Approach #2 does work
User.save('', function(data)
{
$scope.posts = data.posts;
})
*/
//Approach #3 does NOT work -> renders blanks and never re-renders on loaded data
$scope.posts = User.save().posts
}
</script>
My loaded scripts are
//ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js'
//ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular-resource.min.js'
Description of the promise behavior that I want is here
http://docs.angularjs.org/api/ngResource.$resource
Change your fetch to:
var User = $resource('/');
$scope.postsResponse = User.$save();
and your bind to:
<div ng-repeat='post in posts'>
<h3>{{postResponse.post.title}}</h3>
<div>{{postResponse.post.text}}</div>
</div>
As angular is based on promises, it is possible to bind the response of an $http or $resource action directly to the HTML, and when it gets resolved, angular changes the properties.
If possible, remove the wrapper from the server response ({posts: []} to []). If you do this, you'll need to hint the resource that the response is an array. Look at the docs for more info.
The error happens because when you call a method on the resource, it returns a hollow object, so there is no .posts at it. But if you bind it to your code, angular will wait until it gets there.
This is all you should do at front end, are you sure the server answers what is expected? Are you sure you need a post to retrieve the data?
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 am trying to fetch some data from server before controller get render.
I have found many answers for it with respect to routeProvider.
But my main issue is my controller does not bound with any route.
So is there any way to make this possible?
I have controller in following ways...
<!-- HERE I WANT TO BLOCK RENDERING TILL DATA GET LOAD -->
<AppController>
<ng-view>
</AppController>
It sounds like a resolve is what you are looking for, but if you are not using a routing table for this controller, you'll not have this option. Why not just resolve an asynchronous call in your controller, and set scope variables inside the callback. This is what I interpret your desire to await controller "rendering", whereas a resolve through a route table would await controller instantiation. Observe the following...
module.controller('ctrl', function($scope, $http) {
$http.get('/uri').then(function(response) {
// set $scope variables here
});
console.log('executed first');
});
You could also set a variable to prevent the associated view from rendering if your data call is lengthy. This would prevent the UI from "dancing." Observe the following changes to the above example...
<div ng-controller="ctrl" ng-show="resolved"></div>
module.controller('ctrl', function($scope, $http) {
$http.get('/uri').then(function(response) {
$scope.resolved = true; // show rendering
});
});
JSFiddle Link - simplified demo
JSFiddle Link - demo ng-if
One idea will work
in html controller:
<p ng-if="notLoadedContent">Wait</p>
<div ng-if="!notLoadedContent">Content fetched</div>
And in Controller all controller is inside one function will start all, and the controller will be :
fetch(init)
$scope.notLoaded = true;
function init(){
$scope.notLoaded=false;
}
hope it help you
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.
I have following 2 html pages
1. home.html
<div data-ng-controller="userComments">
<Will display all the comments>
</div>
2. comments.html
<div data-ng-controller="userComments">
<Have a comment box and submit button.
Submit button calls submit() function on ng-click>
</div>
where comments.html is pop-up which is initiated from the home page.
And controller
.controller('userComment',['$scope', function($scope){
$scope.title = 'User Comment';
$scope.comments = <db call>
$scope.cmt = '';
$scope.submit = function(){
console.log("comment just entered", $scope.cmt);
$scope.comments = $scope.comments.concat($scope.cmt);
console.log("Updated Comments", $scope.comments);
};
}])
New comments need to be updated automatically in the home.html as well. What should i do to accomplish that?
Thanks
Update:
when the comments are added in the comment.html page, ng-click triggers submit function, $scope.comments gets updated with the new comment, but what should i do to get the updated comments in the home.html too?
When you use the same controller on different views, different instances of the controller are created. You'll need a factory or service to store and share data between views.
So in your case, you'll want a comments factory, something like
myApp.factory('commentsService', function() {
return {
comments: []
};
});
Then in your controller:
.controller('userComment',['$scope', 'commentsService', function($scope, commentsService){
$scope.title = 'User Comment';
$scope.comments = commentsService.comments;
$scope.cmt = '';
$scope.submit = function(){
console.log("comment just entered", $scope.cmt);
$scope.comments = $scope.comments.concat($scope.cmt);
// store the comments for use across views
commentsService.comments = $scope.comments;
console.log("Updated Comments", $scope.comments);
};
}])
You can build out the comments service to also make the db call, as that is an angular best practice (don't fetch external data from controllers, do it from factory/service). You'd build a method called getComments() or something, then call that from the controller.
See:
https://docs.angularjs.org/guide/services
Angularjs - Updating multiple instances of the same controller
Angularjs provides two-way binding so inserting
<div data-ng-controller="userComments">
{{comments}}
</div>
would update comments.
To have same data in entire application( that one defined by ng-app directive ), define a service:
You can inject service to whatever controller
Service data is the same in entire application.
Create service using service method of module.
var app = angular.module('myApp',[]).service('myService', function() {
this.comments = [];
});
Injecting service to controller:
.controller('MyController',['myService',function(myService){
this.addComments = function(data){
myService.comments.push(data);
}
this.getComments = function(){
return myService.comments;
};
}]);
This would keep data same across aplication, and you can also inject this service to another controllers.
Invoke later controller, which uses service:
<div ng-controller="MyController as mc">
{{mc.getComments()}}
</div>
and in another view, set:
<div ng-controller="MyController as mc">
<input type="text" ng-model="myComm"/>
<button type="submit" ng-click="mc.addComment(myComm)" value="Add comment"></button>
</div>
It sets service with new comment. myComm is variable.
ng-model is set with input text, user entered, and ng-click attribute executes on user click.
As final word, there are services provided with angular.
There is $http for network calls, $timeout for invoking things after specific time. You can use them for specific operations and also you can have your own services.
You could also use the events bundled in AngularJS in order to communicate the two instances. So every time the comments array has changes, you can trigger an custom event that the other controller listens and then update the comments array of the other controller.
What is the better solution to hide template while loading data from server?
My solution is using $scope with boolean variable isLoading and using directive ng-hide, ex: <div ng-hide='isLoading'></div>
Does angular has another way to make it?
You can try an use the ngCloak directive.
Checkout this link http://docs.angularjs.org/api/ng.directive:ngCloak
The way you do it is perfectly fine (I prefer using state='loading' and keep things a little bit more flexible.)
Another way of approaching this problem are promises and $routeProvider resolve property.
Using it delays controller execution until a set of specified promises is resolved, eg. data loaded via resource services is ready and correct. Tabs in Gmail work in a similar way, ie. you're not redirected to a new view unless data has been fetched from the server successfully. In case of errors, you stay in the same view or are redirected to an error page, not the view, you were trying to load and failed.
You could configure routes like this:
angular.module('app', [])
.config([
'$routeProvider',
function($routeProvider){
$routeProvider.when('/test',{
templateUrl: 'partials/test.html'
controller: TestCtrl,
resolve: TestCtrl.resolve
})
}
])
And your controller like this:
TestCtrl = function ($scope, data) {
$scope.data = data; // returned from resolve
}
TestCtrl.resolve = {
data: function ($q, DataService){
var d = $q.defer();
var onOK = function(res){
d.resolve(res);
};
var onError = function(res){
d.reject()
};
DataService.query(onOK, onError);
return d.promise;
}
}
Links:
Resolve
Aaa! Just found an excellent (yet surprisingly similar) explanation of the problem on SO HERE
That's how I do:
$scope.dodgson= DodgsonSvc.get();
In the html:
<div x-ng-show="dodgson.$resolved">We've got Dodgson here: {{dodgson}}. Nice hat</div>
<div x-ng-hide="dodgson.$resolved">Latina music (loading)</div>