angularjs fire promise and redirect to external URL before it resolves [duplicate] - angularjs

I'm writing a small script to capture link clicks and save the link's URL into a database table in order to track how many times each link on a particular page is clicked. The links are to external sites.
So, I capture the click event in my JavaScript function, use jQuery to post to a PHP page that saves the data in MySQL, and then the JavaScript function redirects the user to the URL of the link they clicked on.
The problem I'm running into is that it seems like the post never completes because the of redirect. I've worked around this by calling the redirect inside the post callback, but that adds a few second delay because it doesn't get called until after the post completes. I'm looking for a way to just post the data and redirect the user immediately, regardless of whether or not the post succeeds or fails.
This is the current code with the workaround. It works fine, but adds the delay:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
},
function( response )
{
window.location = clicked;
}
);
event.preventDefault();
}
And this is what I'd like to do, but when I try it never completes:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
}
);
window.location = clicked;
event.preventDefault();
}

jQuery doesn't provide a callback for what you're looking for. Here are the available ready states:
Value State Description
0 UNSENT open()has not been called yet.
1 OPENED send()has not been called yet.
2 HEADERS_RECEIVED send() has been called, and headers and status are available.
3 LOADING Downloading; responseText holds partial data.
4 DONE The operation is complete.
You're looking for readystate 2, as that's the earliest you're aware of the fact that the server received the message.
This should get you off the ground:
var xhr = new XMLHttpRequest();
xhr.open("POST", clicked);
xhr.onreadystatechange = function() {
if (xhr.readyState >= 2) window.location = clicked;
};
xhr.send($('#track-click-post-url').attr('value'));
https://developer.mozilla.org/en/XMLHttpRequest for further reading.

Why do you post using Javascript when you are going to load a page any way?
Just update the db with the link clicked on the new page.
Perhaps using the referrer URL to track on what page the click was.
Or some other solution to get on what page the click was (e.g. url param) or some other way.

When you leave a page, all pending requests are killed, and the new page loads. The 1st way is the correct way. Yes, there will be a delay when a link is clicked, that's because the POST request is running.
You can't run a request in the background of a page, if the user is not on that page.
Maybe you can save the link URL in a cookie, and put it into the DB when the next page loads.

Related

AngularJS reloading multiple tab in the same time if any change

I have an add form that if the user file the form and submit, in the same time, user can see the data in the same page in table with ui-view and this is not an issue!!
Problem is: if i open more than one tab in the same browser, that post or change reflection doesn't show in other tab without refreshing or reloading. i want to see change in other tab of browser or other browser without reload
I want to see the change without refresh or reloading.
if i reload in other tab, it works but if i dont reload in other tab, the change doesn't effect
here you go for my post method:
$scope.formModel = {};
$scope.onSubmit = function () {
$http.post('http://127.0.0.1:8000/api/v1/contact/create/', $scope.formModel)
.then(function(response) { //if success, below fuction will execute
$scope.successPost = 'You have successfully submitted your Contact';
$timeout(function() {
$scope.successPost = '';
}, 4000);
//below $scope will push data in client site if the request is success
$scope.contacts.push(response.data);
//if any error occurs, below function will execute
}, function(response) {
// below variable will get predefined message that will explain exactly what happened
var errorData = response.data;
$scope.errorPost = Object.values(errorData)[0][0];
$timeout(function(){
$scope.errorPost = '';
}, 4000);
});
$scope.formModel = {}; //It means, after submit, the form field will clear
$scope.addContactForm.$setPristine();
};
I want to see the change in the other tab without refresh or reload
well browser tabs are per definition separated from each other and can't communicate directly between each other.
there are certain possibilities to handle that: for example you can have a separate external service and always poll that service for latest changes and if something changes automatically call reload.
or you could use a service that provide a websocket that sends you to the frontend an update notification once something changed in your data.
Just want to point you in the right direction, the topic is to big to provide a finished solution. I recommend to do some research on how to communicate between tabs / how to build a service with an update notification for new data
Edit: Just to add #Yftach point from the comments: Theoretically localstorage is shared between tabs, so theoretically you could have a variable there and watch in an interval for changes. However the solution with a separate server is (at least in my eyes) way better

Run a function only the first time when AngularJS gets loaded

I'm using angular-seed template (AngularJS) and I have routeProvider setup and such, now I want to execute a function (which resides in a factory), but only the very first time when the page gets loaded. I found many solutions for this, but I don't want to execute the code each time the users switches between tabs (via routeProvider of course, page doesn't get reloaded) - the code must be executed only when the whole page gets (re)loaded.
How should I approach this? I tried to call the function from run and then broadcast the event when page gets loaded, but there are no event listeners - I guess that is because the run part gets executed before the controllers are setup, so there are no listeners attached at the time when the event gets broadcast.
So, any suggestions how to approach this?
UPDATE
Use case:
when user types the url in the page, the page gets loaded
when pages gets loaded, a $http.get request is performed, which gets a random content
this content can be changed only by clicking a button, to explicitly request a change of content.
if users clicks to a different page e.g. view2 (routeProvider) and then back to the view1, the content must not change
when users refreshes the page (F5), the content changes again (or as already stated, by a click of a button)
Use the run method:
app.run(function(yourService){
yourService.cacheRandomContent();
});
It runs only once after the app is bootstrapped and services are created.
To access the data in controller, just inject the same service:
app.controller('someCtrl',function($scope, yourService){
yourService.getCachedRandomContent().then(function(resp){
$scope.data = resp.data;
});
});
Your service would be something like:
app.service('yourService',function($http){
var promise;
return {
cacheRandomContent: function(){
promise = $http.get();
},
getCachedRandomContent : function(){
return promise;
}
}
});

Browser back button doesn't trigger reload after using Angular's $location.path() update

Situation:
On one of the views of my AngularJS app I have two pagination buttons that use $http to request the next and previous data. I'm using ng-click="getNext(url)" rather than an anchor tag with href="#/items/:itemId". The reason I'm doing this is so that I can quickly page through my content asynchronously w/o triggering a page reload. This works just fine, but using this method bypasses updating the page's URL so its possible to have your current content out of sync with your URL's id (i.e. path is #/items/3 but you're currently viewing item 9). This can be easily fixed by updating the URL in the JS using $location.path('items/' + rsp.id). Now I can fetch data in an async manner and still be able to refresh, bookmark, send links and have the correct/current item display.
Problem:
The problem with this is if a user hits getNext() a few times and then tries to go back using the browser's back button the URL updates like it should but for some reason the browser doesn't perform a refresh–it just sits there and updates the URL. This only occurs with when the item in history is from the same view and I have updated the ID with the location service.
Any ideas would be appreciated.
What I tried so far
Promises + Flags
I've been playing with window.onpopstate, but as of right now I don't have any way to have window.onpopstate differentiate between a browser click and a UI click that updates the URL with $location.path(); Right now it fires the event regardless of the source. So I tried setting a flag to assume that every time this event fires its a browser event, but when its a UI-based event I can disabled that flag because my _myRequestFn() will handle it. Even with this promise setup it still fires the window.onpopstate when _myRequestFn() is fired.
var flag = true; // Assume all requests are browser-based
window.onpopstate = function() {
if (flag) {
$route.reload();
}
console.log('onpopstate fired');
};
_myRequestFn = function(id) {
someService.getMyData(id)
.then(function(rsp) {
// Do a bunch of stuff including...
$location.path('items/' + rsp.id);
return rsp;
})
.then(function() {
// Everything is done reset flag
flag = true;
});
};
$scope.getNext(url) {
flag = false;
_myRequestFn(url);
};
Spoofing
Hitting back through #/item/5 > #/item/4 > #/item/3 just updates the URL and not the path, but if the history has a different param #/thing/2 > #/item/2 that triggers a page refresh. Since the browser back button works if the history is from a different param I wanted to see if I loaded a different param it would work. So I created an #/item-a and #/item-b route that loaded the same template and used the same controllers, just toggled from a/b with each request. I would never recommend this solution to someone, I was more just seeing if I could get the refresh to trigger.
Update
Lots of people on IRC are suggesting that I use UI-Router. I'm really trying to use the out of the box Angular solution. Refactoring my whole routing setup to use UI-Router is not an optimal solution.
app.config(['$locationProvider',
function($locationProvider) {
// Enable html5 mode
$locationProvider.html5Mode(true);
}]);
From the docs:
In HTML5 mode, the $location service getters and setters interact with the browser URL address through the HTML5 history API. This allows for use of regular URL path and search segments, instead of their hashbang equivalents.

How to Post Data before Pop-Up?

I have an application where the user can edit content, then preview that content. Before preview, I need to have the content saved ($http.post to the server). I'm using angular and because the save routine is run through a different controller the code to save looks like:
$scope.$broadcast('save');
var saveCompletedListener = $scope.$on('saveCompleted', function () {
// this call doesn't work (because it waits for data save):
window.open($scope.contentUrl);
});
// this call does work, but shows old data in the popup:
window.open($scope.contentUrl);
The problem is the window.open call is getting blocked in all browsers. If I just call the window.open without waiting for save, it opens fine (it just shows the content from the previous save). So, is there some way in angular to show the popup, but have it wait to load the url until save has completed?

AngularJS (Restangular): Making a promise block? Need to use it for validating a token

I have stumbled upon Restangular for making calls to a rest service. It works great and returns a promise. I need to be able to have the call block. The reason for this is on a fresh page reload I am technically not loggged in but I may have a token stored in a cookie. i would like to validate this token against a rest service. Problem is that I need it to block.
If a timeout occurs or if its not valid that i can treat teh user as not authenticated.
This is the reason for wanting to block is that i would like to redirect them using $location.path to a new URL it not a valid token.
This doesn't happen on a specific route so i can't use resolve which is blocking. It technically happens on every route - I use the $on.$routeChangeStart and check an internal variable got LoggedIn or not, if not logged in i check for the stored token.
This happens on each Page refresh but not while navigating inside the application.
The affect I am trying to get is how Gmail works.
Look forward to any insight anyone has on this
Thanks
Basically you need to ensure that some asynchronous action occurs prior to any route change occurring, and in this case the action is authenticating a user.
What you can do is use the $routeChangeStart event that's emitted in order to add a property to the resolve object on the route like so:
function authenticate() {
if ( user.isAuthenticated ) {
return;
}
// Just fake it, but in a real app this might be an ajax call or something
return $timeout(function() {
user.isAuthenticated = true;
}, 3000);
}
$rootScope.$on( "$routeChangeStart", function( e, next ) {
console.log( "$routeChangeStart" );
next.resolve = angular.extend( next.resolve || {}, {
__authenticating__: authenticate
});
});
Since angular will wait for any promises in the resolve object to be fulfilled before proceeding, you can just use a pseudo dependency as in the example. Using something like that, you should be able to guarantee that your user is authenticating prior to any routes successfully executing.
Example: http://jsfiddle.net/hLddM/
I think the best way to do this might be to push the user around with $location.path, You can use .then() to effectively force a wait by leaving the user on a loading page.
var currentPath = $location.path();
$location.path(loadingScreen);
//Assuming you have some sort of login function for ease.
Restangular.login(token).then(
function(result) {
$location.path(currentPath)
},
function(error) {
$location.path(logInScreen)
}
);
If you're using ui-router, you could move to another state with the same URL, where you'd use that Restangular.login with the then, and in case of success go back to the "logged in" state, otherwise, go to the "log in" state where the user must enter his username and password.
If you're not using ui-router, you could implement something like that with some ng-switch.
So, upon arrival to the screen, you do that Restangular.login and by default you show loading page by setting some boolean to true. Then, if it doesn't succedd, you send him to the login, otherwise, you set loading to false and show page.
Anyway, I'd strongly recommend using ui-router, it rocks :)
Hope this works!

Resources