My AngularJS Service not Updating when User Click link with different $routeParams - angularjs

I'm using a service in angularJS which gets a JSON object and passes it to a controller whenever a user clicks a specific link, /match/id-:matchId', it uses the $routeParams of the :matchId to select which JSON object to request.
The problem it once the user clicks one /match/id-:matchId' link and then tries going to another match with a different ID in the URL, the JSON object does not change, it remains the same. If the user refreshed the page, then they will get the correct JSON object on the page.
Here's the code:
var app = angular.module('app', ['ngRoute']); // TODO: 'ngAnimate', 'ui.bootstrap'
app.config(['$routeProvider','$locationProvider', function($routeProvider, $locationProvider){
$routeProvider
.when('/', {
templateUrl: '/app/static/home.html',
controller: 'mainController as mainCtrl'
})
.when('/match/id-:matchId', {
templateUrl: '/app/components/match/matchView.html',
controller: 'matchController as matchCtrl'
});
// use the HTML5 History API
$locationProvider.html5Mode(true);
}]);
app.controller('matchController', ['$routeParams', 'matchService', function ($routeParams, matchService) {
var matchCtrl = {};
var promise = matchService.getMatch($routeParams.matchId);
promise.then(function (data)
{
matchCtrl.match = data;
});
}])
app.service("matchService", function ($http, $q)
{
var deferred = $q.defer();
function getMatch(matchId) {
var url = 'https://jsonplaceholder.typicode.com/posts/' + matchId;
return $http({
method: 'GET',
// cache: true,
url: url,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
}).
then(function(response) {
//your code when success
// lgTblCtrl.amateurTable = data;
deferred.resolve(response);
console.log('SUCCESS!');
}, function(response) {
//your code when fails
console.log('ERROR!');
deferred.reject(response);
});
return deferred.promise;
}
this.getMatch = getMatch;
})
There are no console errors but when I put breakpoints in the chrome source panel I can see that when the user refreshes the page the code get's called in a different order. This is the order specific lines of code are run depending on how the user landed on a page, by clicking a button or refreshing the page:
Browser Refresh
var promise = matchService.getMatch($routeParams.matchId);
return deferred.promise;
deferred.resolve(response);
matchCtrl.match = data;
Click On A Link
var promise = matchService.getMatch($routeParams.matchId);
return deferred.promise;
matchCtrl.match = data;
deferred.resolve(response);
I'm new to AngularJS, What am I missing here?

This looks like an issue with your deceleration of your 'deferred' variable. The promise is returned correctly the first time, but then it resolves right away whenever the function is called again as the promise is resolved the first time.
Try the following to see if it fixes your issue, moving the declaration of the promise into the function:
app.service("matchService", function ($http, $q) {
function getMatch(matchId) {
var deferred = $q.defer();
var url = 'https://jsonplaceholder.typicode.com/posts/' + matchId;
return $http({
method: 'GET',
// cache: true,

Related

ANgularJS sync server data with local data on request

There is one thing I can't understand in Angular - how better to sync API data with local data.
My structure object is very big, I just give you simple example.
I have 4 api links.
api_links: {
allCars: '/cars/',
cabrio: '/cars/cabrio',
sedan: '/cars/sedan',
mercedes: 'cars/sedan/mercedes'
}
And this is my local object to keep data locally
$rootScope.apiData = {
cars{
cabrio:{},
sedan: {}
}
}
In my single page app I want to reduce count of requests.
So on common page I'm recieving all Cars and put data to $rootScope.apiData
When I go to sedans page, I check if there any data in $rootScope.apiData.cars.sedan. If it's exist, I don't send request.
But if I start from sedans page, I recieve only sedans. Then when I go to common page - HOW can I check am I need to load more categories.
It's quick abstract example. But I try to find any good solutions or plugins for this and cannot.
Please help me!
UPDATE:
Here is my factory:
var api = angular.module('app.api', []);
api.factory('GetData', ['$http', '$q', '$rootScope', '$location'
function($http, $q, $rootScope, $location) {
var apiUrl = '/api/js/';
var apiMixes = {
dashBoard: 'api/dashboard',
accounts: 'api/accounts',
top: 'top',
logout: 'client/logout',
...
}
var methods = {};
methods.getApi = function(u, params) {
var url = apiMixes[u] + '?' + $.param( params );
var deferred = $q.defer();
// Here I need to check if I have cached data
cachedData(data) = ___TODO___;
if( cachedData(data) ){
deferred.resolve(cachedData(data));
// Turn watching on
$rootScope.$emit("receivedApiUpdate", cachedData(data));
return deferred.promise;
}
// If no - get gata from server and put to cache
$http.get(url, {cache: true}).success(function (res, status, headers, config) {
if(res && res.data){
// Turn watching on
$rootScope.$emit("receivedApiUpdate", res.data);
//LocalData.put(res.data);
deferred.resolve(res.data);
}else{
deferred.reject(status);
}
}).error(function (res, status, headers, config) {
(status==401) && $location.path('login/lock');
deferred.reject(status);
});
return deferred.promise;
};
methods.sendData = function(u, data, o) {
data = data || {};
o = o || {};
var deferred = $q.defer();
var url = '/api/js/'+ apiMixes[u];
$http.post(url, JSON.stringify(data)).success(function(res, status) {
if(res.success && res.data){
// Here I need to update my cache with some new value
o.localUpdate && ___PUT_TO_CACHE_TODO___;
deferred.resolve(res.data);
}
});
return deferred.promise;
};
return methods;
}]);
Here is my Controller
app.controller('MyAccountsCtrl', ["$rootScope", "$scope",
function ($rootScope, $scope) {
// Watcher for api updates
$rootScope.$on('receivedApiUpdate',function(event, response){
if(!response || !response.accounts) return;
renderData(response.accounts);
});
function renderData(accounts){
// This renders to template
$scope.accountList = accounts;
}
}]);
Here is mainCtrl, whick make common request, after it I need to update data in several templates.
app.controller('AppCtrl', ['$rootScope', '$scope', 'GetData',
function ($rootScope, $scope, GetData) {
// Here I fire different request for each page, I keep requests in router.
GetData.getApi( 'accounts' ).then(function(data){
// Show content when page is loaded
$('.main-content').removeClass('hidden');
},function(res){
log('GET DATA FAIL', res);
});
}]);
You need to create a service/factory for this, not use the $rootScope. Also, unless you need the data to be constantly updated, you can use the cache: true option inside your $http call.
angular
.module('app')
.factory('carFactory', carFactory);
function carFactory() {
var factory = {
getData: getData
}
return factory;
function getData(callback) {
$http({
method: 'GET',
url: '/cars',
cache: true
}).success(callback);
}
}
And then inside your controller/directives just inject the carFactory and use carFactory.getData(function(cars) { ... }) when you need the data. If the data doesn't exist, it will $http call for it and afterwards execute the callback function. If it does exist, it will return the data directly to the callback, without an $http call.

how to block users that are not authenticated from specific pages?

i want to implement an authentication/authorization system in angularjs, i found a couple of tutorials, and a lot of them using $routeChangeStart event and testing for a specific pages, and then ask a service to test if this user is authenticated by sending the token to the api.
and that's what i have done, but i didn't get the results that i need.
app.js
var app = angular.module("KhbyraApp", ['ngRoute', 'ngCookies']);
app.config(function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider
.when("/Register", {
controller: "RegisterController",
templateUrl: "/app/views/register.html",
authenticate : false
})
.when("/Login", {
controller: "LoginController",
templateUrl: "app/views/login.html",
authenticate: false,
})
.when("/Articles", {
controller: "ArticlesController",
templateUrl: "app/views/article.html",
authenticate: true
})
.otherwise({ redirectTo: '/Login' });
$locationProvider.html5Mode(true);
});
app.run(function ($rootScope, $location, $cookieStore, AuthService) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
if (next.authenticate) {
if (!AuthService.isAuthenticated()) {
$location.url("/Login");
};
};
});
AuthService.js
app.factory('AuthService', function ($http, $q, $window, $cookieStore) {
var factory = {};
var loginUrl = 'http://localhost:2399/Token';
var authUrl = 'http://localhost:2399/Authenticate';
var email;
var token;
factory.Authenticate = function (email, password) {
console.log("AuthService -" + email);
var deferred = $q.defer();
var user = {
email: window.btoa(email),
password: window.btoa(password)
};
$http({
method: 'POST',
url: loginUrl,
data: user
}).success(function (data) {
console.log("AuthService - " + data);
token = data.replace('"', '').replace('"', '');
email = user.email;
deferred.resolve(token);
$cookieStore.put('token', token);
$cookieStore.put('email',user.email);
}).error(function () {
deferred.reject();
});
return deferred.promise;
};
factory.Email = email;
factory.Token = token;
factory.isAuthenticated = function () {
var request = $http({
method: 'GET',
url: authUrl,
headers: { 'Authorization': 'Token '+ $cookieStore.get('email') + ":"+ $cookieStore.get('token') }
}).then(function () {
return true;
}
,function () {
return false;
});
};
return factory;
});
the problem here is in the routeChangeStart even if the AuthService.isAuthenticated() returns true, in the if statements something goes wrong, i think it's about $http returns a promise.
It's normal that the route is loaded even if you're condition fails : Angular doesn't rollback and go back to the previous page. The RouteChangeStart event is actually called just after the URL is changed, when angular detects it.. but the redirection has already been made (there is actually no BeforeRouteChange event)
So you'll have to handle it yourself depending on your needs. For example, in this case, you'll typically force a redirection to the login page. YOu could also display a login popup on top of your page and wait for the login to be successfull to re-execute the previously failed request (which should now works because you're are now logged again). This behavior is exaclty the one of the http-auth-interceptor.
See also angular-app which implements something similar (based on http-auth-interceptor also)

Why promise doesnt work as expected in AngularJS

In my AngularJS application on every request to change the page i run :
$rootScope.$on('$locationChangeStart', function (event, next, current) {
var user;
$http.get('/api/Authentication/UserAuthenticated').then(function (data) {
console.log("call");
user = data.data;
});
console.log("end of call");
});
When i run application and test what is happening i see in console that "end of call" is returned before console.log("call"); which means that user is not set. Which means that if i want to check if user is logged in on change of route user will be undefined.
How do i make Angular run-> http request and only then keep going?
I misunderstood the question a bit. You can let the $routeProvider resolve the $http promise:
var app = angular.module("myApp");
app.config(["$routeProvider", function($routeProvider) {
$routeProvider.when("/",{
templateUrl: "myTemplate.html",
controller: "MyCtrl",
resolve: {
user: ["$http", "$q", function($http, $q) {
var deferred = $q.defer();
$http.get('/api/Authentication/UserAuthenticated').success(function(data){
deferred.resolve(data.data);
}).error(function(error) {
deferred.resolve(false);
});
return deferred.promise;
}]
}
});
}]);
If the code to fetch the user data is too complex, you could create a service for it, and inject that service in the $routeProvider's resolve function.
In your controller, you just inject the promise (which will be resolved):
app.controller("MyCtrl",["$scope","user", function($scope, user) {
if (!user) {
alert("User not found");
}
...
}]);
use async:false. It is working for me
Try this code, instead of your code
$rootScope.$on('$locationChangeStart', function (event, next, current) {
$http({method: 'GET',
url: '/api/Authentication/UserAuthenticated',
async: false
}).success(function (data) {
console.log("call");
user = data.data;
}
console.log("end of call");
});

How can I execute some code asynchronously before methods in my service are called?

I am having some problems with executing some tasks before my service is initialised and it's methods used. Some context:
I am producing an application which uses REST to communicate with 2 different backend systems (the reason for this is that if our client upgrades in the future it will still work). These backend systems have slightly different paths for the same REST calls.
To know which calls to use I thought a solution might be to call one test endpoint which exists in one, but not the other, and depending on the response code received, set a variable which is the beginning of the URL. e.g. /rest/services/StartpointService/.
All the REST calls are in a single factory and I tried something like this:
angular.module('myApp.services', [])
.factory('myServices', [
'$http',
'$q',
function($http, $q) {
//test function, if success we are using 1 backend, if fails, we use the other
var loginTest = function() {
var deferred = $q.defer();
$http( {
method: 'POST',
url: '/um/login?um_no_redirect=true'
})
.success(function(data, status, headers, config) {
deferred.resolve(status);
})
.error(function(data, status, headers, config) {
deferred.reject(status);
});
return deferred.promise;
};
var url;
loginTest()
.then( function(response) { //if backend1
if(responseCode === 200) {
url = '/rest/services/etc/etc' //set the URL
}
},
function(errorCode) { //If backend2
if(errorCode === 404) {
url = '/LCConnector/rest/services/etc/etc';
}
});
var service = {
realCall : function() {
//use 'url' variable in the $http call
}
}
return service;
}]);
Obviously as the loginTest is asyncronous, the service is injected into my controller and is called before url is set.
I have looked into running in a config or run block when the app is first initialised but can't quite understand how to get the variable out.
If i can give anything further details, please let me know!
Regards,
Augier
If this check is required before the application is initialized you can manually bootstrap your application after the Ajax call. Inside of your fail() or done() call backs you can alter the module config to reflect the url you need. From there you can easily inject your config into any service that requires this url.
example on jsfiddle
<div ng-controller="MyCtrl">{{url}}</div>
//if you chanée this url to /echo/fail and url will now be url1
var urlToCheck = '/echo/json/';
var myApp = angular.module('myApp', []);
myApp.controller("MyCtrl", ["$scope", "config", function ($scope, config) {
$scope.url = config.url;
}]);
$.ajax({
url: urlToCheck
}).fail(function () {
myApp.constant('config', {
url: '/fail-url'
});
}).done(function () {
myApp.constant('config', {
url: '/done-url'
});
}).always(function () {
angular.bootstrap(document, ['myApp']);
});
You could take advantage of $routeProvider, which allows you to delay your controller instantiation until your promise has been resolved.
$routeProvider exposes a method called resolve for that purpose. See the AngularJS doc:
http://docs.angularjs.org/api/ngRoute.$routeProvider
Additional information in this excellent SO question:
AngularJS : Initialize service with asynchronous data
Highly recommended.

Share async data between controllers without making multiple requests

I'm trying to make a single $http request to get one of my JSON files and use the data across all my controllers.
I saw on egghead.io how to share data across multiple controllers, and I've also read this StackOverflow question: "Sharing a variable between controllers in angular.js".
However, the answers there don't use the $http module. When using $http, the controllers don't have the data to work on, and by the time the response is received it's already too late.
I then found the method $q.defer and this question on StackOverflow: "AngularJS share asynchronous service data between controllers"
The solution posted there works fine, BUT it has two issues:
Each controller will trigger the $http request to obtain the same data already used in another controller; and,
If I try to manipulate the data received I have a then function.
Below you can see my code:
controllers.js
'use strict';
/* Controllers */
function appInstallerListCtrl($scope, Data) {
$scope.apps = Data;
}
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
$scope.apps = Data;
console.log($scope.apps); // <-- then function
console.log(Data); // <-- then function with $vv data returned but I can't access it
for (var i in $scope.apps) // <--- no way, baby!
console.log(i);
}
app.js
var app = angular.module('appInstaller', []);
app.factory('Data', function($http, $q) {
var defer = $q.defer();
$http.get('apps.json').then(function(result) {
defer.resolve(result.data.versions.version);
});
return defer.promise;
});
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/app', {templateUrl: 'partials/app-list.html', controller: appInstallerListCtrl}).
when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}).
otherwise({redirectTo: '/app'});
}]);
What I'd like to have is that when launching the app, the $http request will be performed and the response will be used throughout the app across all controllers.
Thanks
I like to store my data in the service, and return a promise to the controllers, because usually you need to deal with any errors there.
app.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('apps.json')
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
.controller('appInstallerListCtrl', ['$scope','Data',
function($scope, Data) {
Data.getApps()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
}]);
Any callbacks that are registered after a promise has been resolved/rejected will be resolved/rejected immediately with the same result/failure_reason. Once resolved/rejected, a promise can't change (its state). So the first controller to call getApps() will create the promise. Any other controllers that call getApps() will immediately get the promise returned instead.
Since you are using a promise, to access the data returned by promise use the callback syntax
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
Data.then(function(returnedData) {
$scope.apps=returnedData;
console.log($scope.apps);
for (var i in $scope.apps)
console.log(i)
});
}
Make sure this
defer.resolve(result.data.versions.version);
resolve returns array, for the above code to work. Or else see what is there in data and ajust the controller code.
I found the way not sure weather it is a best approach to do it or not.
In HTML
<body ng-app="myApp">
<div ng-controller="ctrl">{{user.title}}</div>
<hr>
<div ng-controller="ctrl2">{{user.title}}</div>
</body>
In Javascript
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.controller('ctrl2', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.factory('userService', function($http, $q) {
var promise;
var deferred = $q.defer();
return {
getUser: function() {
if(!promise){
promise = $http({
method: "GET",
url: "https://jsonplaceholder.typicode.com/posts/1"
}).success(function(res) {
data = res.data;
deferred.resolve(res);
})
.error(function(err, status) {
deferred.reject(err)
});
return deferred.promise;
}
return deferred.promise;
}
}
});
This will exactly make only 1 HTTP request.
My issue was that I didn't want to wait for resolve before loading another controller because it would show a "lag" between controllers if the network is slow. My working solution is passing a promise between controllers via ui-router's params and the data from promise can be loaded asynchronously in the second controller as such:
app.route.js - setting the available params to be passed to SearchController, which shows the search results
.state('search', {
url: '/search',
templateUrl: baseDir + 'search/templates/index.html',
controller: 'SearchController',
params: {
searchPromise: null
}
})
landing.controller.js - controller where the user adds search input and submits
let promise = SearchService.search(form);
$state.go('search', {
searchPromise: promise
});
search.service.js - a service that returns a promise from the user input
function search(params) {
return new Promise(function (resolve, reject) {
$timeout(function() {
resolve([]) // mimic a slow query but illustrates a point
}, 3000)
})
}
search.controller.js - where search controller
let promise = $state.params.searchPromise;
promise.then(r => {
console.log('search result',r);
})

Resources