I have an application made with Ionic 2, The work flow is like this
Case A . When user is using app for the first time
User Logs in (loading is shown)
When successfully logged in loading window is hidden and user is forwarded to Dashboard page.
In dashboard page items are loaded via ajax request.
Case B. When user is already logged in before
The first screen is Dashboard and items are loaded via ajax request.
Problem
In case A, when user logs in and forwarded to DashboardPage, the loading screen doesn't gets dismissed. Sometimes it gets dismissed but most of the time it doesnot? Is this an ionic bug or am I doing something wrong??
Here is my DashboardPage
//imports here
export class DashboardPage {
public loadingmsg: any;
public ajaxRequest: any;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
private webservice: WebService,
private loadingCtrl: LoadingController
)
{
this.loadDashboardContents();
}
loadDashboardContents(){
//other codes
this.loadingmsg = this.loadingCtrl.create({
content:"Loading contents, please wait..."
});
this.loadingmsg.present();
this.ajaxRequest = this.webservice.getDashboardContents(params).subscribe(data => {
this.loadingmsg.dismiss().then(()=>{
//other codes to save retrieved data to localstorage.
});
});
}
}
UPDATE
The login method from login page
loginUser(){
this.loading=this.loadingctrl.create({
content:"Logging in, please wait..."
});
this.loading.present();
this.ajaxRequest = this.webservice.loginUser(params).subscribe(data => {
this.loading.dismiss();
if(data.status =="ok"){
this.navctrl.push(DashboardPage).then(()=>{
const index = this.viewCtrl.index;
this.navctrl.remove(index);
});
}else{
//show error alert
}
}, err =>{
this.loading.dismiss();
});
}
My Ionic and cordova version information
Ionic Framework: 3.5.0
Ionic App Scripts: 1.3.9
Angular Core: 4.1.3
Angular Compiler CLI: 4.1.3
Node: 6.10.3
OS Platform: Windows 10
Cordova Version: 6.5.0
I am currently using loading in my project and it works well in all case. To ensure loading will always dismiss you need to add some code:
1. duration, dismissOnPageChange
let loading = this.loadingCtrl.create({
content: "",
duration: 5000, //ms
dismissOnPageChange: true
})
2. dissmis when ajax call success or error:
.subscribe(success=>{
//some code
loading.dismiss();
},error=>{
//some code
loading.dismiss();
})
It may be due to the this reference inside your subscribe method. I would try declaring loadingmsg locally and removing this.
loadDashboardContents(){
//other codes
let loadingmsg = this.loadingCtrl.create({
content:"Loading contents, please wait..."
});
loadingmsg.present();
this.ajaxRequest = this.webservice.getDashboardContents(params).subscribe(data => {
loadingmsg.dismiss().then(()=>{
//other codes to save retrieved data to localstorage.
});
});
}
Related
In my website I have a series of images that serve as nuxt links to game pages:
<template>
<NuxtLink :to="game.pageName">
<img :src="game.boxImage" :height="gamePanelHeight" class="elevation-4"
/></NuxtLink>
</template>
Each of those links draws its properties from a content markup file like this:
index: 3
boxImage: gameImages/box_image.png
title: game title
pageName: games/whatever
And they're loaded into the page like so:
<script>
export default {
async asyncData({ $content, params }) {
const games = await $content('games').sortBy('index', 'asc').fetch()
return { games }
},
}
</script>
Whenever I refresh this page. All of these images break until I navigate outside the page and come back. What's causing this issue and how do I fix it?
This is a static Nuxt application FYI. And it's being served through an AWS S3 bucket but I don't think that's what's causing this issue.
EDIT: Also the boxImage that's in gameImages/box_image.png is from the static folder.
asyncData is not a hook that is triggered upon reaching an URL or using a reload (F5), it is only triggered during navigation.
If you want it to work even after a reload, use the fetch() hook.
More info here: https://nuxtjs.org/docs/2.x/components-glossary/pages-fetch#options
Edit on how to write it with fetch()
<script>
export default {
data() {
return {
games: [],
}
},
async fetch() {
this.games = await this.$content('games').sortBy('index', 'asc').fetch()
},
}
</script>
Buckle up, this one's a little bit complicated. I know that Express sends the browser a connect.sid cookie... and Passport uses this to deserialize the User on web requests. Not only that, but when I log in to my application from my NativeScript app (I'm running on a Pixel 2 emulator on a Windows PC, but I know it also works on iOS), the cookie seems to be correctly set and sent along with future web requests. I also understand how the application-settings API works, and that you can use this to store a user-identifying token for future boots of the application (so that I don't have to log in every time).
So here's where the disconnect occurs. Conceivably I can override the cookie in the request header if I have it stored, but nowhere can I find documentation on how to retrieve a cookie from the successful login request in nativescript.
Here's the code:
TokenSvc
import { Injectable } from "#angular/core";
import { getString, setString } from "application-settings";
export class TokenSvc {
static isLoggedIn(): boolean {
return !!getString("token");
}
static get token(): string {
return getString("token");
}
static set token(token: string) {
setString("token", token);
}
}
Login Component
(Note I am making an embarrassing attempt at getting the cookies from a new HttpHeaders instance... not sure why I thought that would work.)
#Component({
selector: "app-login",
moduleId: module.id,
templateUrl: "./login.component.html",
styleUrls: ["./login.component.scss"]
})
export class LoginComponent {
credentials: ILoginCredentials;
#ViewChild("password") password: ElementRef;
#ViewChild("handle") handle: ElementRef;
#ViewChild("confirmPassword") confirmPassword: ElementRef;
constructor(private page: Page, private router: Router, private AuthSvc: AuthSvc, private _store: Store<AppStore>) {
this.page.actionBarHidden = true;
this.credentials = {
email: "",
password: "",
cPassword: "",
handle: "",
publicName: ""
};
}
login() {
const loginCredentials: ICredentials = {
username: this.credentials.email,
password: this.credentials.password,
rememberMe: false
};
this.AuthSvc.login(loginCredentials).subscribe(
(payload) => {
console.log(payload);
if (payload.failure) {
alert(payload.failure);
} else {
// user!
let cookies = new HttpHeaders().get("Cookie");
console.log(cookies);
TokenSvc.token = cookies;
this._store.dispatch({ type: "SET_USER", payload: payload });
this.router.navigate(["/tabs"]);
}
}, () => alert("Unfortunately we were unable to create your account.")
);
}
}
The essential question here is... how do I persist a cookie-based session in NativeScript application-settings with a Node/Express back-end?
The essential answer is: you don't.
Prefer JWT, OAuth2 or any other token-based authentication method when it comes to mobile development. You can use the same authentication method for web too.
Store the user token using the secure storage and send the token along with any request made by the user.
I have a PWA built with ionic deep linker. I have done a demo here https://stackblitz.com/edit/ionic-mee2ut?file=app%2Fcustomer%2Fcustomer.component.html where the browser back button doesn't work as expected.
Steps to reproduce
1.In Dashboard page click on edit button.It will navigate to customer
page(see URL.It is changed to /Customer/CustomerId).
2.In Customer page, you will see the customer info and other customers
list, there click edit from other customers list.This will open another
page.(see URL.It is changed to /Customer/CustomerId).
3.Click on browser back button u can see that the URL is changed but the
view is not updated.
If I repeat steps 1 & 2 then click on nav back button instead of browser button then it works correctly.Both the URL and the view gets updated.
Is there something I am doing wrong because the browser back button does not work as expected or this is issue of ionic framework.
This is how i navigate between views
EditCustomer(Customer: any) {
this.navCtrl.push('Customer', { Id: Customer.Id, Name: Customer.Name });
}
Can somebody please tell me a way how to resolve this issue?
I saw your code in the above url, you are passing id as param but not the name so, that is the reason url is changing but data is not reflected i modified your code in app.module.ts file please replace this code in your app.module.ts file
IonicModule.forRoot(MyApp, {}, {
links: [
{ component: DashboardComponent, name: 'Dashboard', segment: 'Dashboard' },
{ component: CustomerComponent, name: 'Customer', segment: 'Customer/:Id/:Name' }
]
})
Please replace your app.module.ts with the following code
import { Component } from '#angular/core';
import { Platform, IonicApp, App } from 'ionic-angular';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = 'Dashboard';
constructor(private _app: App, platform: Platform, private _ionicApp: IonicApp,) {
platform.ready().then(() => {
this.setupBackButtonBehavior();
});
}
private setupBackButtonBehavior () {
// If on web version (browser)
if (window.location.protocol !== "file:") {
// Register browser back button action(s)
window.onpopstate = (evt) => {
//Navigate back
if (this._app.getRootNav().canGoBack())
this._app.getRootNav().pop();
};
}
}
}
I was able to use something like this:
let randomID = this.makeId(5); // random string id
this.navCtrl.push('path', {
eventID: eventID,
instituteID: instituteID,
randomID: randomID
}, {
id: `path/${eventID}/${instituteID}/${randomID}`
});
This "id" seems to fix it, but if you can go to the same page, then it requires a "random" value to separate each visit to that page.
#IonicPage({
name: 'path',
segment: 'path/:instituteID/:eventID/:randomID'
})
It looks like, by default, it uses the name of the page as an id for that view. If multiple views have same id => issue when using browser back/forward. That's where the random comes in, to separate multiple instances of the same page.
We're working with two ember applications that each run different version of ember and ember-simple-auth, and want to get ember-simple-auth to work well with both version.
The old app
Ember 1.8.1
Ember-simple-auth 0.7.3
The new app
Ember 2.3.1
Ember-simple-auth 1.0.1
Uses cookie session store
We trying to change the session API for the older version so that it stores the access and refresh tokens correctly so the new app can use it.
So far, we’ve tried overriding the setup and updateStore methods to work with the authenticated nested object but are still running into issues.
Disclaimer - Patrick Berkeley and I work together. We found a solution after posting this question that I figured I would share.
In order for a 0.7.3 version of ember-simple-auth's cookie store to play nicely with a 1.0.0 version, we did have to normalize how the cookie was being formatted on the app with the earlier version in a few key places, mostly centered around the session object (the 0.7.3 session is an ObjectProxy that can be extended in the consuming app to create your own custom session).
The methods that we needed to override, centered around the structure of data being passed to the cookie store to persist and what was being returned when a session was being restored. The key difference is on version 0.7.3, the access_token, etc is stored top-level on the content object property of the session. With 1.0.0. this is nested inside another object inside content with the property name of authenticated. We therefore needed to ensure that everywhere we were making the assumption to set or get the access_token at the top level, we should instead retrieve one level deeper. With that in mind, we came up with these methods being overridden in our custom session object:
// alias access_token to point to new place
access_token: Ember.computed.alias('content.authenticated.access_token'),
// overridden methods to handle v2 cookie structure
restore: function() {
return new Ember.RSVP.Promise((resolve, reject) => {
const restoredContent = this.store.restore();
const authenticator = restoredContent.authenticated.authenticator;
if (!!authenticator) {
delete restoredContent.authenticated.authenticator;
this.container.lookup(authenticator).restore(restoredContent.authenticated).then(function(content) {
this.setup(authenticator, content);
resolve();
}, () => {
this.store.clear();
reject();
});
} else {
this.store.clear();
reject();
}
});
},
updateStore: function() {
let data = this.content;
if (!Ember.isEmpty(this.authenticator)) {
Ember.set(data, 'authenticated', Ember.merge({ authenticator: this.authenticator }, data.authenticated || {}));
}
if (!Ember.isEmpty(data)) {
this.store.persist(data);
}
},
setup(authenticator, authenticatedContent, trigger) {
trigger = !!trigger && !this.get('isAuthenticated');
this.beginPropertyChanges();
this.setProperties({
isAuthenticated: true,
authenticator
});
Ember.set(this, 'content.authenticated', authenticatedContent);
this.bindToAuthenticatorEvents();
this.updateStore();
this.endPropertyChanges();
if (trigger) {
this.trigger('sessionAuthenticationSucceeded');
}
},
clear: function(trigger) {
trigger = !!trigger && this.get('isAuthenticated');
this.beginPropertyChanges();
this.setProperties({
isAuthenticated: false,
authenticator: null
});
Ember.set(this.content, 'authenticated', {});
this.store.clear();
this.endPropertyChanges();
if (trigger) {
this.trigger('sessionInvalidationSucceeded');
}
},
bindToStoreEvents: function() {
this.store.on('sessionDataUpdated', (content) => {
const authenticator = content.authenticated.authenticator;
this.set('content', content);
if (!!authenticator) {
delete content.authenticated.authenticator;
this.container.lookup(authenticator).restore(content.authenticated).then((content) => {
this.setup(authenticator, content, true);
}, () => {
this.clear(true);
});
} else {
this.clear(true);
}
});
}.observes('store'),
This took us most of the way there. We just needed to ensure that the authenticator name that we use matches the name on 1.0.0. Instead of 'simple-auth-authenticator:oauth2-password-grant', we needed to rename our authenticator via an initializer to 'authenticator:oauth2'. This ensures that the apps with the newer version will be able to handle the correct authenticator events when the cookie session data changes. The initializer logic is simple enough:
import OAuth2 from 'simple-auth-oauth2/authenticators/oauth2';
export default {
name: 'oauth2',
before: 'simple-auth',
initialize: function(container) {
container.register('authenticator:oauth2', OAuth2);
}
};
The above satisfies our needs- we can sign in to an app using ember-simple-auth 0.7.3 and have the cookie session stored and formatted properly to be handled by another app on ember-simple-auth 1.0.0.
Ideally, we would just update the Ember and Ember Simple Auth versions of the app though business needs and the fact that we wanted to focus our energies on the v2 versions (which are completely fresh and new code bases) propelled us to go down this path.
First of all I don't use Ruby nor Devise :) (all my searches led me to plugins that kind of rely on Devise)
I want to do pretty simple authentication with Ember, I have a REST backend that blocks requests without a proper cookie(user-pass) and i want Ember to watch when it gets 403 forbidden (won't let you to transition into protected URLs) and then pop up a user-login dialog.
So when a user tries to send a new message for example(lets say i've built a forum) Ember will fire the request and if it gets 403 it will block the transition and popup a login form and will retry the transition after the login have completed
Also is there a way to get the errors from ember-data and respond to them? (if a user tries to change an attribute he can't access i would like to inform him about it[Access denied or something like that])
I want to use custom errors that my server will send to ember data not just error numbers but words like "Sorry you can't change this before 12 PM"
You can simply listen to the response of your server and transition to your LOGIN (or whatever you call it) route. In my apps I happen to keep two types of routes (LOGIN and AUTHENTICATED). When they access the authenticated routes without logging in, they get a 401 unauthorized error and get transitioned to the LOGIN route.
// AuthenticatedRoute.js
redirectToLogin: function(transition) {
// alert('You must log in!');
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
},
events: {
error: function(reason, transition) {
if (reason.status === 401) {
this.redirectToLogin(transition);
} else {
console.log(reason);
window.alert('Something went wrong');
}
}
},
model: function () {
return this.store.find('post');
},
So now when the user requests for post he gets a 401 and gets transitioned to LOGIN controller.
// LoginController.js
login: function() {
var self = this, data = this.getProperties('username', 'password');
// Clear out any error messages.
this.set('errorMessage', null);
$.post('/login', data).then(function(response) {
self.set('errorMessage', response.message);
if (response.success) {
alert('Login succeeded!');
// Redirecting to the actual route the user tried to access
var attemptedTransition = self.get('attemptedTransition');
if (attemptedTransition) {
attemptedTransition.retry();
self.set('attemptedTransition', null);
} else {
// Redirect to 'defaultRoute' by default.
self.transitionToRoute('defaultRoute');
}
}
});
}
The basic answer you need is capturing the events in the route and transitioning accordingly. I just happened to include the code for attempted transition as it comes in handy at times.