Ionic view not refreshing data from API - angularjs

We have an Ionic app which polls a node/express API.
When the app starts it fetches the JSON data correctly from the API. When we update the data and fetch it again from the Ionic app, we still see the old data from the time that the app was launched.
We've tried to clear both the Angular cache and the Ionic cache in a variety of ways, but that doesn't seem to make a difference.
Things we've tried are:
$ionicConfigProvider.views.maxCache(0);, cache-view="false" on the template, setting cache: false on the state, tried accessing the state via $state.go($state.currentState, {}, {reload : true});, $ionicHistory.clearHistory(); and $ionicHistory.clearHistory();, $route.reload and $window.location.reload.
Controller:
function contactsController(Contacts, $stateParams) {
var vm = this;
var params = $stateParams.id;
Contacts.getAllContacts.then(function success(response) {
vm.data = response.data;
vm.selectedContact = response.data[params];
});
}
Factory
function contactsFactory($http, $stateParams) {
return {
getAllContacts: $http.get('https://api-call'),
update: function(url) {
$http.patch('https://api-call/update', [$stateParams.id, url]);
},
};
}
Express Back end
app.get('/', function (req, res) {
ref.once("value", function(snapshot){
res.send(snapshot.val());
});
});
app.patch('/update', function(req, res) {
var id = req.body[0];
ref.child(id).update({"imageURL": req.body[1]});
});
Thanks

Modify your view like this
<ion-view cache-view="false" view-title="My Title!">

Try console.log(vm) and verify that the updated data are obtained. If its only the view thats not being updated despite updated data being logged, I would try the following along with cache-view="false".
$scope.apply(function (){
// update your view here
vm.data = response.data;
vm.selectedContact = response.data[params];
});

By using Firebase's once() you have effectively removed all callbacks attached to value besides the first one:
Listens for exactly one event of the specified event type, and then
stops listening
Try switching to on() instead.
app.get('/', function (req, res) {
ref.on("value", function(snapshot){
res.send(snapshot.val());
});
});
It may be that your issue is not related to caching at all...

When I install Ionic 2 Open layers map on android device, i can see Open layers map successfully on the android screen. But when i close it and open the app again, i cant see the map. To solve this problem , what i do is: i uninstall the app, restart my device, then reinstall the app, after that i can see the map.
Please provide me a permanent fix.

Placing this in app config worked for me with
$ionicConfigProvider.views.maxCache(0);
$ionicConfigProvider dependency in config.

Related

Angular: load environment properties before config/run

I'm developing a angular app, and this app has about a 10 configurable properties (depending on the environment and client).
I had those properties in json config files, but this is really troublesome: there must be specific builds per env/company. So I would like to retrieve those properties once from the backend on app load.
So in order to do this I created a Provider
var app = angular.module('myApp', [...]);
app.provider('environment', function() {
var self = this;
self.environment;
self.loadEnvironment = function (configuration, $http, $q) {
var def = $q.defer();
$http(...)
.success(function (data) {
self.environment = Environment.build(...);
def.resolve(self.environment);
}).error(function (err) {
...
});
return def.promise;
};
self.$get = function(configuration, $http, $q) {
if (!_.isUndefined(self.environment)) {
return $q.resolve(self.environment);
}
return self.loadEnvironment(configuration, $http, $q);
};
}
app.config(... 'environmentProvider', function(... environment) {
...
//The problem here is that I can't do environment.then(...) or something similar...
//Environment does exists though, with the available functions...
}
How to properly work with this Provider that executes a rest call to populate his environment variable?
Thanks in advance!
This is an excelent scenario to explore angularjs features.
Assuming that you really need the environment data loaded before the app loads, you can use angular tools to load the environment and then declare a value or a constant to store your environment configs before the app bootstraps.
So, instead of using ng-app to start your app, you must use angular.bootstrap to bootstrap it manually.
Observations: You mustn't use ng-app once you are bootstrapping the app manually, otherwise your app will load with the angular default system without respecting your environment loading. Also, make sure to bootstrap your application after declare all module components; i.e. declare all controllers, servieces, directives, etc. so then, you call angular.bootstrap
The below code implements the solution described previously:
(function() {
var App = angular.module("myApp", []);
// it will return a promisse of the $http request
function loadEnvironment () {
// loading angular injector to retrieve the $http service
var ngInjector = angular.injector(["ng"]);
var $http = ngInjector.get("$http");
return $http.get("/environment-x.json").then(function(response) {
// it could be a value as well
App.constant("environment ", response.data);
}, function(err) {
console.error("Error loading the application environment.", err)
});
}
// manually bootstrap the angularjs app in the root document
function bootstrapApplication() {
angular.element(document).ready(function() {
angular.bootstrap(document, ["myApp"]);
});
}
// load the environment and declare it to the app
// so then bootstraps the app starting the application
loadEnvironment().then(bootstrapApplication);
}());

Pass authentication information from MVC into angular

My project uses MVC to deliver the initial markup of my site
The MVC controller is super simple:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
I have my ng-app tag, bundling, and #RenderBody in a layout view:
<!DOCTYPE html>
<html ng-app="myAppName">
<head>
#Styles.Render("~/Content/css")
</head>
<body>
<div class="container body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/aBundle")
</body>
</html>
And my Index view is stripped down as simple as possible:
<ng-view></ng-view>
My angular app.ts file looks like this:
module app {
var main = angular.module("myAppName", ["ngRoute", "breeze.angular"]);
main.config(routeConfig);
routeConfig.$inject = ["$routeProvider"];
function routeConfig($routeProvider: ng.route.IRouteProvider): void {
$routeProvider
.when("/home",
{
templateUrl: "app/views/homeView.html",
controller: "HomeController as vm"
})
.when("/itemDetail/:itemId",
{
templateUrl: "app/views/itemDetailView.html",
controller: "ItemDetailController as vm"
})
.when("/addItem",
{
templateUrl: "app/views/addItemView.html",
controller: "AddItemController as vm"
})
.when("/login",
{
templateUrl: "app/views/loginView.html",
controller: "LoginController as vm"
})
.otherwise("/home");
}
}
I can inspect the Request sent by the user in the MVC controller, or in the Razor view using #Request.IsAuthenticated to see if the user is logged in, but what is the best way to pass this information to my angular app so that I can properly route the user to a login page when they first sign on, but skip the login page if they have an active session on the server?
The research I have done to try and figure this out has suggested to me that I probably need to create an angular service to store a boolean value regarding whether the user is authenticated or not. Then, I need to add some code to check this service for every route using $routeChangeStart, and redirecting to the login page only when necessary. I have looked at many examples, but can't quite put the pieces together in the context of my own application.
Could someone help me connect the dots, please?
I'm developing a project that I'm not using razor. Only html and angular in the views. So... What I did is:
I created a directive "appWrapper" that holds the layout. Inside of it there is a section that has the ng-view.
This directive uses a controller, the AuthCtrl, and this controller uses a service, the AuthService.
Because of this, I have access to everything that's inside this controller in the entire html. So I could say ng-click="vm.logout()" in an item in the sidebar, for example.
This "AuthService" has some methods that can be called by the controller. Login and logout being 2 of them.
When I execute login, I set some cookies with some information I get back from my controller.
When I logout, I remove these cookies.
On the app.js, where my routes are, I have I .run at the end that checks these cookies every time the location (url) is going to be changed. If they exist, it lets the user continue. If not, it redirects the user to the login.
Did it help? If needed, I can post the code for you to use as example.
No need to create an action inside a controller for checking this. It would require your application to go to the server, back to the browser, back to the server and back to the browser for every action the user takes. This is not good.
I can share what I do in my own apps, and although I am using ui-router, the same technique can easily be applied to the built in router.
Basic logical workflow:
Automatically add a resolve to every route that fetches the user's current auth status and caches it.
Check if route requires authentication, and reject if user is not logged in, otherwise allow to proceed normally.
On $routeChangeError (or something similar) check to see what action to perform.
The advantage of this is it allows you to have a great deal of flexibility with regard to providing a nice client side experience with security.
Adding Credentials to Every Route:
let originalStateFunction = $stateProvider.state;
$stateProvider.state = function (state, config) {
//
// The "allowAnonymous" is something we added manually
// This will become important later because we might have
// routes that don't require authentication, like say
// the login page
//
if (angular.isDefined(config) && !config.allowAnonymous) {
_.defaults(config.resolve || (config.resolve = {}), {
/*#ngInject*/
userSession: function userSessionSecurityCheck($q, sessionService) {
let def = $q.defer();
sessionService.getSession()
.then(session => {
if(!session.isAuthenticated){
def.reject({
error:'AUTHENTICATION_REQUIRED'
});
}
});
return def.promise;
//You could also do more complex handling here...
// like check permissions for specific routes
// and reject the promise if they fail.
}
});
}
//Now call the original state/when method with our
// newly augmented config object
return originalStateFunction.apply(this, arguments);
};
Inspecting The Route Error
.run($rootScope => {
"ngInject";
$rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error) => {
event.preventDefault();
if(error.error === "AUTHENTICATION_REQUIRED"){
//Now you can redirect the user appropriately
}
});
});
You can create a simple endpoint to return the current user's status by inspecting the Principal, and so long as you are smart about caching that on the client, you will only incur the hit once per user.
The full sample code is too large for SO, but hopefully this gives you a good starting point.
I am thinking you would do it along the lines of below:
MVC controller:
[Authorize] // Make sure we're authorising the whole controller.
public class ProfileController : Controller
{
public ActionResult IsLoggedIn()
{
return Json(this.Request.IsAuthenticated);
// you may need allow get as the second parameter
}
}
Angular:
app.factory('ProfileService', function ($http) {
var service = {
isLoggedIn: isLoggedIn
};
return service;
function isLoggedIn() {
return $http.get('Profile/IsLoggedIn').then(function (response) {
return response.data; // this depends on what the response object looks like
});
}
});
app.run(function (ProfileService, $location) {
ProfileService.isLoggedIn().then(function (isLoggedIn) {
if (!isLoggedIn) {
$location.path('/unauthorised'); // just redirect them to the unauthorised route.
}
});
});
This means that everytime your app runs it will check to see whether you are logged in. You can also use this profile service to go grab other information about the user! And you can pull this service into any other module you wish to perform this kind stuff.
Remember that it is pointless trying to secure the javascript because it is run in a sandbox. But always make sure that you use [Authorize] attributes in your MVC code so that the server is always enforcing Authorisation and Authentication.
Before anyone says this isn't Typescript, any Javascript is also valid Typescript. I leave it to the user to put in the type defs.
-- More Info --
You can add a xmin cache expiry on the is logged in or store these details into local storage if you are constantly requesting the information:
app.factory('ProfileService', function ($http, $q) {
var service = {
isLoggedIn: isLoggedIn
};
var cacheExpiries = {
loggedIn: { value: null, expiry: null }
};
return service;
function isLoggedIn() {
var cacheObj = cacheExpiries['loggedIn'];
var useCache = false;
if (cacheObj.expiry) {
useCache = new Date() < cacheObj.expiry;
}
if (useCache) {
// because http returns a promise we need to
// short circuit function with a promise
return $q(function (res, rej) {
res(cacheObj.value);
});
}
// set the new expiry for the cache, this just adds 5 minutes to now
cacheObj.expiry = new Date(new Date().setMinutes(new Date().getMinutes() + 5));
return $http.get('Profile/IsLoggedIn').then(function (response) {
cacheObj.value = response.data;
return response.data; // this depends on what the response object looks like
});
}
});
You could easily encapsulate the cache into a factory with a proper key value storage api. This would extract some of the logic out of your thin service ( to keep it thin )
Local Storage and Cookie Storage
I have used this module for years to do local storage access, this can be slotted in place of the cache or you could still have your cache service to wrap this storage solution so that you can completely stay decoupled from your dependencies.

How to call $urlRouterProvider.otherwise() after loading JSON file?

I am working on a project based on ionic and angular js. I am loading JSON file which contains some JSON data in key-value pair. What I want to achieve is I have to call $urlRouterProvider.otherwise() method after json file is loaded completely. Following is the code which I have tried but it does not work for me. I have tried putting console in 'defaultRoute' function it is getting executed but '$urlRouterProvider.otherwise('/tab/myjobs')' this line doesn't work.The following code is present in app.config function. Any help will be appreciated.
$.getJSON('js/constants/'+lang+'.json')
.then(function(response) {
$translateProvider.translations(window.localStorage['deviceLanguage'],response);
defaultRoute($urlRouterProvider);
}, function(response) {
//$translate.use('en');
});
function defaultRoute($urlRouterProvider){
if(window.localStorage['userData']) {
var access_token = JSON.parse(window.localStorage['userData']).access_token;
if(access_token){
$urlRouterProvider.otherwise('/tab/myjobs');
}else{
$urlRouterProvider.otherwise('/login');
}
}else{
console.log("in line 282");
$urlRouterProvider.otherwise('/login');
}
}
The problem is that you are running an async method in the config phase.
AngularJS lifecycle is splitted in 2 phases, config (where you can use providers, but not services because these are not yet registered), and run (where you cannot use providers, but you can use services and in general is equivalent to the main function).
The run phase starts when config phase has finished, and config phase do not wait for any async process, so what is happening is that when your JSON get promise is solved your config phase has already finished (so any provider config you try to do in your promise success callback do not really config anything).
So in short, you cannot use $urlRouterProvider.otherwise() passing the result of an async call, like your getJson method.
A couple alternatives to what you are trying to do (redirect user depending on auth) are:
angular ui-router login authentication and angularjs redirect to login page if not authenticated with exceptions.
Consider using $state.go('someState')
You would want your code to look like this:
if(access_token){
$state.go('myJobs'); // where myJobs is the state correlated with your url 'tabs/jobs'
}else{
$state.go('login'); // or whatever the state name is that you have for 'login'
}
}else{
console.log("in line 282");
$state.go('login');
}
If you are trying to send the user to different routes conditionally, use $state.go('route.path') instead of updating the .otherwise() configuration. For example:
var app = angular.module('myapp',['ionic']);
app.controller('$scope', '$state','$http', [function($scope, $state, $http){
$http.get('my/api/path')
.then(function(response){
if(response.authenticated){
$state.go('secret.route');
} else {
$state.go('public.route');
}
});
}]);

How to sync data with angular-data

I have a angular project with a angular-data to work with my Restful API.
my situation is as following:
in my service I return a defineresource
in my controller I use service.defineresource.findAll().then
it all works fine but here is my question. when new data is added to the server how do I update the cache or add the new data to my cache.
Please advice
code:
//in service.js
.factory('Service', function(DS) {
DS.defineresource({
name: 'items'
endpoint:'api/v2/users'
});
});
//in controller.js
.controller('MainCtrl', function($scope, Service) {
Service.findAll().then(function (data) {
$scope.items = data
});
});
my code is above fetches all the data when I load it first time, but after the initial loading it only loads from the cache. My question is there a way to sync and fetch new data without destroying the cache.
To force the refresh of the data from the backend you need to set bypassCache: true in the options. If you don't pass this then angular-data will load from cache. This is documented in the api documentation.
To refresh the data every 15 minutes use $timeout with 900000 milliseconds. To have a button to refresh use a scope function that will be called from a ng-click on a button.
.controller('MainCtrl', function($scope, $timeout, Service) {
$scope.refreshData = function () {
Service.findAll({}, { bypassCache: true }).then(function (data) {
$scope.items = data;
});
}
$scope.refreshData();
$timeout($scope.refreshData, 900000);
});
<button ng-click="refreshData()">Refresh</button>
If you are using only Angular Routes, Use :
$route.reload()
it will reload / refresh your page . so the data will be sync .
you can use it in ng-click or check in your controller when new data is entered use it
$route.reload()
if you are using UI-Router you can do the same but instead of $route.reload() use this :
$state.go($state.current, {}, {reload: true});

Backbone.history is not updating in IE 9. Back button broken

In our app, we actually have two Backbone SPA applications. The first one is for login, registration and other features for unauthenticated users. The URL for this would be something like http://www.example.com/registration#signin. Once you login, you are redirected to our main Backbone app at http://www.example.com/ui#home.
In my main UI app, I am using Backbone.history without pushState. The App file looks something like:
define(function (require) {
var App = new Marionette.Application();
App.addInitializer(function (options) {
...
});
...
App.on('initialize:after', function () {
$(function(){
if (Backbone.history) {
Backbone.history.start({ root: '/ui' });
}
});
$.log("**WebApp**: Marionette app started.");
});
return App;
});
Of course, everything works flawlessly in any browser except IE 9 (and maybe 10, I need to check). In IE 9, all the routing works fine. Clicking links such as http://www.example.com/ui#anotherpage works. However, when the user clicks the Back button in their browser, they are not sent back to the last route fired. Instead, they are sent to http://www.example.com/registration#signin, which is the last page served by Node, our web server. As I click through links, I can see that history.length and Backbone.history.history.length are not updating.
All routes are fired from links/URL's. I'm not using router.navigate() within the code. Here are examples of our Router:
define(function (require) {
var Backbone = require('backbone'),
Marionette = require('marionette');
return Backbone.Marionette.AppRouter.extend({
appRoutes: {
"": "showHome",
"home": "showHome",
"foo": "showFoo"
}
});
});
And Controller:
define(function (require) {
var Backbone = require('backbone'),
Marionette = require('marionette');
return Backbone.Marionette.Controller.extend({
showHome: function () {
require(['webapp','modules/home'], function (WebApp) {
WebApp.module("Home").start();
WebApp.module("Home").controller.showModule();
});
},
showFoo: function () {
require(['webapp', 'modules/foo'], function (WebApp) {
WebApp.module("Foo").start();
WebApp.module("Foo").controller.showModule();
});
}
});
});
UPDATE:
On further research, it turns out the problem is that older versions of IE don't record hash changes in their history. See - Change location.hash and then press Back button - IE behaves differently from other browsers. But I'm still not sure what the workaround for this would be. I'm guessing it would somehow involve manually handling hash change events with a plugin such as jQuery Hashchange and doing... something? Manually setting IE's history? Or crafting a custom history object and using it when we detect a Back button in IE?
I was having the same problem in one of our apps for IE.
Starting backbone history like below works.
Backbone.history.start({
pushState: true,
hashChange: false
});
Update: As mentioned By T Nguyen,
When you set pushState to true, hash URL's no longer trigger routes. Unless you add server-side support for all your Backbone routes, you need to add an event handler on the client side which captures appropriate links and calls .navigate() on the route

Resources