AngularJS - Refresh after POST - javascript

What is the correct way to refresh content after a http POST request in Angular?
//controller.js
var hudControllers = angular.module('hudControllers', []);
hudControllers.controller('PropertyDetailsCtrl',
['$scope','$window','$http', function ($scope,$window,$http) {
//I want to reload this once the newCommentForm below has been submitted
$http.get('/api/comments')
.success(function(data) {$scope.comments = {"data":data};}})
.error(function(data) {...);
$scope.newCommentForm = function(){
newComment=$scope.newComment;
requestUrl='/api/comments';
var request = $http({method: "post",url: requestUrl,data: {...}});
request.success(function(){
//How do I refresh/reload the comments?
$scope.comments.push({'comment':'test'}); //Returns error - "TypeError: undefined is not a function"
});
};
}]);
//template.ejs
<div class="comment">
<ul>
<li ng-repeat="comment in comments.data">{{comment.comment}}</li>
</ul>
</div>
Thanks.

There are many ways you can do it. still I want to show you simplest way (according to your needs).
lets say you have 'first.html' page and 'PropertyDetailsCtrl' is associated with it.
Now, in html you can write like this,
with very first-div
<div ng-controller="PropertyDetailsCtrl" ng-init="initFirst()">
.... Your page contents...
</div> (This will initialize your controller and you will have execution of your first method 'initFirst()'.
at your .js side....
var hudControllers = angular.module('hudControllers', []);
hudControllers.controller('PropertyDetailsCtrl',
['$scope','$window','$http', function ($scope,$window,$http) {
//I want to reload this once the newCommentForm below has been submitted
$scope.initFirst=function()
{
$http.get('/api/comments')
.success(function(data) {...})
.error(function(data) {...);
//You need to define your required $scope.....
$scope.myVariable=data;
};
Now at appropriate time (You know when) your below method gets called.
$scope.newCommentForm = function(){
newComment=$scope.newComment;
requestUrl='/api/comments';
var request = $http({method: "post",url: requestUrl,data: {...}});
request.success(function(data){
//How do I refresh/reload the comments?
//without calling anything else, you can update your $scope.myVariable here directly like this
$scope.myVariable=data
});
//or else you can call 'initFirst()' method whenever and wherever needed like this,
$scope.initFirst();
};
}]);
I hope this will help.

Not sure if there is ONE correct way, but you can call $location.path() on success.
request.success(function(){
$location.path('your path');
});
To view the added comment (which apparently is what you want), you could use :
request.success(function(response){
$scope.comments.push(response.data);
});
Your content would refresh automatically on the page, if you're using angular expressions and a ng-repeat.

Related

Change angularjs code to work for jquery pop-up

Hi I don't know angularJS yet I have a task in that. There is dashboard which is designed in angularJS. Now, in that there is link which will
open a pop-up. I have given a task to change that link to another pop-up but that pop-up is in jQuery. So, for me it is quite difficult to understand how to
change this.
I have a xml file where all the label in html are defined. This is the label from where the link is coming.
<subcolumn type="a" ngclick="decisioncomp();" styleName="proccheader labe1padding" text="Complete : " uniqueID="data31" />
Now, in the controller file of the same there is a function defined.
// complete link click
$scope.decisioncomp = function () {
if ($scope.data31 != "") {
ModalService.showModal({
templateUrl: "ChartpagePopup/Complete.html",
controller: "Complete"
}).then(function (modal) {
modal.close.then(function (result) {
$scope.customResult = "All good!";
});
});
}
else
alert($scope.waitingalert);
};
So, I came to know from here the link is going to html and controller. Now, I thought that if changed this ModalService.showModal to not to take controller
or if I send controller empty and replace my html with this html, my work should be done. So what I did is I changed in the Script file for that where this
funtion is defined.
I commented out line where it says that controller can't be empty.
self.showModal = function(options) {
// Create a deferred we'll resolve when the modal is ready.
var deferred = $q.defer();
// Validate the input parameters.
var controllerName = options.controller;
if (!controllerName) {
//deferred.reject("No controller has been specified.");
//return deferred.promise;
}
But then the link itself is not opening. I don't know where else to change. I am stuck here. Please someone help me.

AngularJS UI router's "resolve" fires twice

I am using angular ui router to handle some routing on my frontend. This is what my routing code looks like.
// angular config
$stateProvider.state('app', {
templateUrl: '/static/partials/home.html',
controller: 'NavCtrl'
});
$stateProvider.state('app.reader', {
url : '/reader/*path?start&end&column&page',
templateUrl: '/static/partials/reader.html',
resolve : {
panelContent : [
'$state', '$stateParams', '$http',
function ($state, $stateParams, $http) {
alert('resolving panel Content');
return []; // simplest thing possible to illustrate my point
}
]
},
controller: 'ReaderCtrl'
});
/// etc etc
$urlRouterProvider.otherwise('/reader/');
My html makes use of multiple nested views, I'll try and illustrate as best I can
index.html
<html>
<div ui-view></div> <!-- /static/partials/home.html gets injected here -->
</html>
/static/home.html
<html>
<!-- some side bar stuff -->
<!-- reader -->
<div ui-view></div> <!-- /static/partials/reader.html gets injected here -->
</html>
So I've got multiple levels of nesting going on
-- index.html
-- home.html
-- reader.html
Now, when I load the page for the first time, my alert message
alert('resolving panel Content');
fires just once.. that makes sense. However, let's say I click "next page" inside my pagination..
<!-- inside /static/partials/reader.html -->
<uib
pagination total-items= "totalItems"
ng-model= "pageNumber"
ng-change= "pageUpdate"
max-size= "maxPageNumbersDisplayed"
></uib>
this eventually fires a function inside my "ReaderCtrl"
$scope.pageUpdate(page) {
$state.go( '.', {page: page});
}
This updates the url, from going to something like this
/#/reader/<my path>
to something like this
/#/reader/<my_path>?page=2
Now for the part that has me tearing my hair out.
I get back to the "resolve" code block in the reader section of my routing.
The alert message happens twice.
By doing a bit of debugging in the web console, I discovered that the order goes
1) alert message in resolve
2) travel through the entirety of ReaderCtrl
3) lots and lots of angular calls
4) alert message (2nd time)
5) travel through entirety of ReaderCtrl a second time.
You might be inclined to know what is going on in NavCtrl, but I am not making any calls there. All that is in NavCtrl are functions that ReaderCtrl can inherit, in order to update the scope for /static/partials/home.html
So really, it appears as though I am stuck on step 3 here.
Does anyone have any ideas as to why my resolve block appears to be firing twice?
edit:
after a bit more debugging, I have seemed to figure out that the order goes something like this, starting right after the "updatePage" function executes.
1) first "resolving message"
-- the url has not yet changed
2) second "resolving message"
-- the url appears to have changed very shortly before this message
So, I guess my question now is...
why does
$state.go('.', args);
NOT change the url before the first alert fires, but DOES change the url at/near the second alert?
edit 2: could not end up fixing my issue, so I sort of hacked around it for the time being... I essentially made a function that did what I assume $state.go() was doing behind the scenes, and constructed the url.
function _mk_url(args) {
var url = "/reader";
var pageNumber = args.pageNumber || 1;
url += "?page=" + pageNumber;
var columns = args.columns || [];
columns.forEach(function(d) {
url += "&column=" + d;
});
//etc..
return url;
}
var args = {"columns" : ["a", "b", "c"], "pageNumber" : 2};
var url = _mk_url(args);
$location.url(url);
I was having this problem and found out it was because I was calling my resolve function manually somewhere else in the code. Search your code for panelContent() and you may find where it's getting triggered again.
I'd got this problem. The reason was in my html template. I used ui-sref directive in both child and parent elements
<li ui-sref="{{r.name}}" ng-class="vm.isCurrent(r)" ng-repeat="r in vm.settingsRoutes">
<span ui-sref="{{r.name}}" ng-bind-html="r.title"></span>
</li>
so when I clicked on span, I fired stateChange twice.
I've had the same bug.
And I found that I was changed $stateParams in one of the resolve functions.
The solution is make a copy from this object and then do what you want with a copy.
resolve: {
/** #ngInject */
searchParams: function ($stateParams) {
let params = angular.copy($stateParams); // good
// good:
if (params.pending === undefined) {
params.pending = true;
}
// bad:
if ($stateParams.redirect === 'true') {
$stateParams.pending = false; // this line changing the URL
}
return params;
},
}

Showing/Hiding a div based on data, without loading non-displayed data in AngularJS

In my app I have a search page which loads in data based on a user's query. Each result (comes in waves of 10) has two fields I want to dynamically switch between: "imgs" and "smallImgs".
In short, if a result's smallImgs.length == true then I want to load up those images. If it has no length then I want to load up its imgs.
I'm aware of ng-hide and ng-show, and I could do a simple true/false statement in the template to show or hide the imgs/smallImgs. But from what I can tell, even if I have ng-hide enabled on an element it still loads in that data (and would hence download the images to the client).
The only way I've thought of achieving this is in my controller, on the $resource call that grabs my data:
$scope.itemSearch.get({query:$routeParams.query, page:$routeParams.page}, function(data){
$scope.posts = data.posts;
for(var i = 0; i<$scope.posts.length; i++){
$scope.posts[i].imgs = testImgs($scope.posts[i]);
}
});
var testImgs = function(data){
if (data.smallImgs.length){
return data.smallImgs;
} else{
return data.imgs;
}
}
Unfortunately I haven't gotten this working yet. Is this the best way to achieve this? Is there an Angular way to take care of this in the view?
What I'd do is add a method to your controller for this:
$scope.getImages = function () {
// add the logic to get the correct array here
};
Then just use that in the view, e.g.
<div ng-repeat="image in getImages()" />
Here's an example jsfiddle.
$scope.itemSearch.get({query:$routeParams.query, page:$routeParams.page}) - if this returns promise you can do so
$scope.getImages = $scope.itemSearch.get({query:$routeParams.query, page:$routeParams.page});
<div ng-repeat="image in getImages" />

Angularjs ng-show based on callback

I'm new to angular so maybe I am missing something.
On my registration form I need a location from the user. Based on whether they allow/support navigator.geolocation, I want to show a drop down to choose a location.
Controller.
$scope.showLoc = true;
if(navigator && navigator.geolocation){
navigator.geolocation.getCurrentPosition(function(pos){
$scope.showLoc = false;
},function(err){
$scope.showLoc = true;
});
}
and my form:
<form class="form-horizontal" name="register" ng-controller="RegisterCtrl" ng-submit="registerUser()"> ....
<div ng-show="showLoc" accesskey="">
test
</div>
....
</form>
This approach is not working for me. Any insight would be appreciated.
Whenever you do some form of operation outside of angularjs, such as doing an ajax call with jquery, or grabbing the geolocation like your example here you need to let angular know to update itself. I looked at your jsfiddle and changed some of your code around to look like this:
var app = angular.module('myApp', []);
app.controller('RegisterCtrl',function($scope){
$scope.showLoc = false;
navigator.geolocation.getCurrentPosition(function(pos){
$scope.showLoc = true;
$scope.$apply();
},function(err){
$scope.showLoc = false;
$scope.$apply();
});
});
And now showLoc changes to true on the update. Here's the documation on using the $apply() method http://docs.angularjs.org/api/ng.$rootScope.Scope#$apply
jsFiddle http://jsfiddle.net/yLFNP/6/
Edit: My answer was edited, but I don't agree with the edit. While you could wrap the $apply method around $scope.showLoc = false to make it "shorter" You're really only saving 1 character (the semi colon). Also, I tend to like the $apply method after a bunch of logic instead of wrapping everything in it. If there were more things I was doing to the scope, you'd either have to write each additional one like so:
$scope.$apply($scope.var1 = newValue1);
$scope.$apply($scope.var2 = newValue2);
$scope.$apply($scope.var2 = newValue3);
Which I find overkill, or you could use the function method:
$scope.$apply(function(){
$scope.var1 = newValue1;
$scope.var2 = newValue2;
$scope.var3 = newValue3;
});
Or directly after code that needs "applying":
$scope.var1 = newValue1;
$scope.var2 = newValue2;
$scope.var3 = newValue3;
$scope.$apply();
By doing it this method all the time, you're code is easily transferable and very readable. Also less lines is not always the best method.

Accessing a collection from outside of the $scope

Ok.. I've tried angular.js. It is awesome. I'm impressed. I can get bindings and stuff.. Cool.
Now what if I need to access to my data from outside of the $scope? Let's say I have a signalR hub that sends some data and function that intercepts that and should add a new item or modify existing. How do I do that? Can you show me on this example how can I access $scope.twitterResult from click handle?
<script>
angular.module('Twitter', ['ngResource'])
function TwitterCtrl($scope, $resource){
$scope.twitter = $resource('http://search.twitter.com/:action',
{action: 'search.json', q: 'obama', callback:'JSON_CALLBACK'},
{get:{method:'JSONP'}});
$scope.doSearch = function(){
$scope.twitterResult = $scope.twitter.get();
}
}
$(function(){
$('#addItem').click(function(){
// add a row to $scope.twitterResult
});
});
</script>
<body>
<div data-loading></div>
<div ng-controller='TwitterCtrl' ng-init="doSearch()">
<ul>
<li ng-repeat='tweet in twitterResult.results'><p> {{tweet.text}}</p></li>
</ul>
</div>
</body>
A better way would be to wrap your "signal hub" in an AngularJS service. Take a look on my blog post about using web sockets with AngularJS, specifically "Interacting with Socket.IO."
Why did you write:
$(function(){
$('#addItem').click(function(){
// add a row to $scope.twitterResult
});
});
And not just use ng-click? Is this some 3rd party code or widget? Pending on these this, I'll try to better advise you and write up some example code.
If you have to register an event handler, you should do so through a directive. Otherwise things will get complicated when you start managing the lifecycles of these outside-of-angular event bindings.
General answer is: you don't simply mess with the scopes from the outside.
But the requirement you have is a genuine one.
So in order to do what you want you need to establish a communication between outside of the scope and the scope itself.
The easiest way is to export the $scope to window and just mess with it, breaching into the scope from outside. You should NEVER do this. There be dragons.
The scope should maintain it's internal state.
I'm not exactly familiar with angular but you can do something to the effect of:
function TwitterCtrl($scope, $resource) {
// ...
$('body').bind('newTweetsArrived', data) {
// data contains the new tweets
// the decision to accept or not new tweets is made within the control
if (in_the_mood_to_accept_new_tweets) {
// add new tweets to the $scope.twitterResult
}
// optionally notify other components that new tweets are accepted
// so that they can adjust or whatever
$('body').trigger('afterNewTweetsArrived');
}
}
// you add new tweets by triggering global custom event
$(function(){
$('#addItem').click(function(){
$('body').trigger('newTweetsArrived', { ...here_are_the_tweets... });
});
});
You could probably do something like this, but I'm not sure if it's the best idea:
var myTwitterScope;
angular.module('Twitter', ['ngResource'])
function TwitterCtrl($scope, $resource){
$scope.twitter = $resource('http://search.twitter.com/:action',
{action: 'search.json', q: 'obama', callback:'JSON_CALLBACK'},
{get:{method:'JSONP'}});
$scope.doSearch = function(){
myTwitterScope = $scope;
$scope.twitterResult = $scope.twitter.get();
}
}
$(function(){
$('#addItem').click(function(){
// add a row to $scope.twitterResult
myTwitterScope.twitterResult.push(...); // or however you would do this.
});
});
As others have mentioned, this is not the cleanest solution.

Categories