Cannot Dismiss LoadingController In Error Response Of Subscribe() - Ionic 4 - javascript

I'm displaying a LoadingController when the user tries to login. Meanwhile, an API is being called.
I’m able to dismiss the LoadingController when I get a SUCCESS response from subscribe, but when I get an ERROR response, I’m not able to dismiss. Please help!
I’m a professional Python developer and a total newbie to Ionic, just started a day ago. So, please assist as such.
import { Component, OnInit } from '#angular/core';
import { ToastController, LoadingController } from '#ionic/angular';
import { CallapiService } from '../callapi.service';
#Component({
selector: 'app-login',
templateUrl: './login.page.html',
styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {
userEmail = '';
userPassword = '';
loginUrl = 'login/';
loginMethod = 'POST';
postBody = {};
constructor(
public toastController: ToastController,
public loadingController: LoadingController,
private callApiService: CallapiService,
) { }
ngOnInit() {
}
async presentToast(displayMessage) {
const toast = await this.toastController.create({
message: displayMessage,
duration: 2000,
position: 'middle',
});
return await toast.present();
}
async presentLoading(loadingMessage) {
const loading = await this.loadingController.create({
message: loadingMessage,
});
return await loading.present();
}
loginUser() {
if (this.userEmail === '' || this.userPassword === '') {
this.presentToast('Email and password are required.');
}
else {
this.presentLoading('Processing...');
this.postBody = {
email: this.userEmail,
password: this.userPassword,
};
this.callApiService.callApi(this.loginUrl, this.postBody, this.loginMethod).subscribe(
(success) => {
console.log(success);
this.loadingController.dismiss();
},
(error) => {
console.log(error);
this.loadingController.dismiss();
}
);
this.loadingController.dismiss();
}
}
}

Without any service,
Same issue I faced while using Ionic 4 loading controller.
After trial and error I got working solution.
As loading controller functions are using async and await because both are asynchronous functions.
dismiss() function will called before present() function because, dismiss function will not wait until creating and presenting the loader, it will fire before present() as soon function will call.
Below is working code,
loading:HTMLIonLoadingElement;
constructor(public loadingController: LoadingController){}
presentLoading() {
if (this.loading) {
this.loading.dismiss();
}
return new Promise((resolve)=>{
resolve(this.loadingController.create({
message: 'Please wait...'
}));
})
}
async dismissLoading(): Promise<void> {
if (this.loading) {
this.loading.dismiss();
}
}
someFunction(){
this.presentLoading().then((loadRes:any)=>{
this.loading = loadRes
this.loading.present()
someTask(api call).then((res:any)=>{
this.dismissLoading();
})
})
}

this.callApiService.callApi(this.loginUrl, this.postBody, this.loginMethod)
.subscribe(
(data) => {
// Called when success
},
(error) => {
// Called when error
},
() => {
// Called when operation is complete (both success and error)
this.loadingController.dismiss();
});
Source: https://stackoverflow.com/a/54115530/5442966

Use Angular property binding. Create a component to your loading:
import { Component, Input } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Component({
selector: 'app-loading',
template: ''
})
export class LoadingComponent {
private loadingSpinner: HTMLIonLoadingElement;
#Input()
set show(show: boolean) {
if (show) {
this.loadingController.create().then(loadingElem => {
this.loadingSpinner = loadingElem;
this.loadingSpinner.present();
});
} else {
if (this.loadingSpinner) {
this.loadingSpinner.dismiss();
}
}
}
constructor(private loadingController: LoadingController) {}
}
...then in 'login.page.html' use your componente:
...
<app-loading [show]="showLoading"></app-loading>
... in 'LoginPage' create a property 'showLoading' and set it to true or false where you whant:
//.... some source code
export class LoginPage implements OnInit {
showLoading;
userEmail = '';
userPassword = '';
loginUrl = 'login/';
loginMethod = 'POST';
postBody = {};
//.... some source code
loginUser() {
if (this.userEmail === '' || this.userPassword === '') {
this.presentToast('Email and password are required.');
} else {
this.showLoading = true;
this.postBody = {
email: this.userEmail,
password: this.userPassword
};
this.callApiService
.callApi(this.loginUrl, this.postBody, this.loginMethod)
.subscribe(
success => {
console.log(success);
this.showLoading = false;
},
error => {
console.log(error);
this.showLoading = false;
}
);
this.showLoading = false;
}
}
}
This works for me, I reuse the loading component on others pages!
Recommended reading: https://angular.io/start

I actually ran into this exact issue and for me the answer was just to use await.
The functions for both creating and dismissing loaders return promises. What I realized was happening is that the subscribe/promise rejection was halting all other promises from completing. Now, I just await both presenting and dismissing and I have no issue:
async getData() {
//await presenting
await this.presentLoading('Loading...');
try {
let response = await this.httpService.getData();
await this.loadingController.dismiss();
//...
catch(err) {
this.loadingController.dismiss();
//handle error
//...
}
}
async presentLoading(msg: string) {
const loading = await this.loadingController.create({
spinner: 'crescent',
message: msg
});
await loading.present();
}
I hope this simple solution helps!

Related

How to default format response when statusCode is 200 or another successful one in NestJS?

How to format the response when I got successful response?
For example my code is
#Get(':id')
async getOne(#Param('id') id: string) {
const model = await this.categoriesService.getById(id);
if (model) {
return model;
} else {
throw new NotFoundException('Category not found');
}
}
I got a response is:
{
"id": 1,
"name": "test",
"image": null
}
How to default format to?
{ status: 200|201..., data: [MyData] }
There are many ways for this response but in my opinion, is best practice is to use an interceptor
based on documentation
// src/common/interceptors/format-response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, HttpStatus } from '#nestjs/common';
import { map, Observable } from 'rxjs';
#Injectable()
export class FormatResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(value => {
value = (value) ? value : []
return { status: "success", data: [value]};
}));
}
}
and in the controller inject the interceptor
import { UseInterceptors } from '#nestjs/common';
#UseInterceptors(FormatResponseInterceptor)
export class UserController {
constructor() {}
#Get(':id')
async getOne(#Param('id') id: string) {
const model = await this.categoriesService.getById(id);
if (model) {
return model;
} else {
throw new NotFoundException('Category not found');
}
}
}
And for change the format of error, you can use Filter
I usually just explicitly write
try {
....
return { status: HttpStatus.OK, data: [MyData] }
} catch(e){
return { status: HttpStatus.NOT_FOUND, message: e.message || 'my error' }
}

View is rendered before user type is loaded from firestore

So I'm building an Ionic - Angular app that have an hospital patient submit a request to the nurse stuff and the nurse stuff can see their assigned requests (based on the room that assigned to the patient submitting the request). A nurse can see all requests and a patient can see only his/her requests. I have a function in the auth.service.ts that is called (setUserType() ) once a user is logged in manually or if it is an auto login(token is stored and found) and fetch the user type and name once it finished authentication.
The problem is, in the my-requests.page.ts in NgOnInit I call a function in the requests service that run a query to fetch all requests(if it is a nurse) or to fetch only the user's requests(if it is a patient) based on the user type I assigned once login/auto login occured. This field is unassigned once the my-requests.page.html is rendered and I can't seem to find a way to make it render only after I have the user type information.
setUserType() function:
let userId: string;
this.userIdObservable.subscribe(x => {
userId = x;
});
const userQuery = this.firestore.doc<Users>(`added-users/${userId}`);
userQuery.valueChanges().subscribe(x => {
this._userType = x.type;
this._userName = x.name;
});
My requests ngOnInit function:
ngOnInit() {
this.segment.value = 'progress';
this.requestSubscription = this.requestsService
.loadRequests()
.subscribe(requests => {
this.requestsList = requests;
});
}
Now all the auth functions -
Auth page Authenticate function:
authenticate(email: string, password: string) {
this.isLoading = true;
this.loadingCtrl
.create({
keyboardClose: true,
message: 'Logging in...'
})
.then(loadingEl => {
loadingEl.present();
let authObs: Observable<AuthResponseData>;
if (this.isLogin) {
authObs = this.authService.login(email, password);
} else {
authObs = this.authService.signup(email, password);
}
authObs.subscribe(resData => {
console.log(resData);
this.isLoading = false;
loadingEl.dismiss();
this.authService.setUserType();
this.router.navigateByUrl('/requests/tabs/add-requests');
}, errRes => {
loadingEl.dismiss();
const code = errRes.error.error.message;
let message = 'Could not sign you up, please try again.';
if (code === 'EMAIL_EXISTS') {
message = 'This Id exists already!';
} else if (code === 'EMAIL_NOT_FOUND') {
message = 'No such user.';
} else if (code === 'INVALID_PASSWORD') {
message = 'Could not log you in, please try again.';
}
this.showAlert(message);
});
});
}
Auth service login function:
login(email: string, password: string) {
return this.http
.post<AuthResponseData>(
`https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=${
environment.firebaseAPIKey
}`,
{ email: email, password: password, returnSecureToken: true }
)
.pipe(tap(this.setUserData.bind(this)));
}
Auth service autologin function:
autoLogin() {
return from(Plugins.Storage.get({ key: 'authData' })).pipe(
map(storedData => {
if (!storedData || !storedData.value) {
return null;
}
const parsedData = JSON.parse(storedData.value) as {
token: string;
tokenExpirationDate: string;
userId: string;
email: string;
};
const expirationTime = new Date(parsedData.tokenExpirationDate);
if (expirationTime <= new Date()) {
return null;
}
const user = new User(
parsedData.userId,
parsedData.email,
parsedData.token,
expirationTime
);
return user;
}),
tap(user => {
if (user) {
this._user.next(user);
this.setUserType();
}
}),
map(user => {
return !!user;
})
);
}
This is how you can do you dont have to include it in any module cli will do that for you.
import {Component, Injectable, OnInit} from '#angular/core';
import {BehaviorSubject} from 'rxjs';
import {FormGroup} from '#angular/forms';
#Injectable({
providedIn: 'root'
})
export class UserStateService {
private user = new BehaviorSubject({
isLoggedIn: false,
userType: null
});
constructor() {
}
setUser(user) {
this.user.next(user);
}
getUser() {
return this.user;
}
}
// my request
#Component({
selector: 'request-component',
templateUrl: './request-component.html'
})
export class RequestComponent implements OnInit {
constructor(private userStateService: UserStateService) {}
ngOnInit(): void {
this.userStateService
.getUser()
.subscribe(
((val: {isLoggedIn: boolean, userType: any}) => {
// calll you service
}));
}
}
// in your auto login or login you call setter
this.userStateService.setUser({isLoggedIn: true, userType: 'data from login'});

How do I load data from an API into an array in Angular 2?

So last week I started learning AngularJS, only to realize that I'm better off learning Angular 2 instead. After much reading and tinkering with sample apps on Plunker, I'm finally ready to dive into Angular 2. Last week with AngularJS I was able to create a simple app that retrieves data from an API and turns that into a navigation menu. So this week I am attempting to port that code to Angular 2.
I can't say that it was easy, but after much fiddling I found that the reason it was not working was that it was not even pulling the data. Below is my code for the service that pulls the data.
./src/app/navigation.service.ts
import { NavItem } from './navigation.model';
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class NavService {
sections: NavItem[] = [];
categories: NavItem[] = [];
constructor(private http: Http) {
}
loadSections() {
//var headers = new Headers();
//headers.append('Content-Type', 'application/json');
this.http
.get('http://localhost:15557/api/navigation/sections/list')
.map(res => {
return res.json()
})
.subscribe(
data => {
this.sections = data;
},
err => this.logError(err),
() => console.log("Loaded all sections")
);
}
loadCategories(id) {
this.http
.get('http://localhost:15557/api/navigation/categories/' + id)
.map(res => res.json())
.subscribe(
data => this.categories = [data],
err => this.logError(err),
() => console.log("Loaded categories in section with id " + id)
);
}
logError(err) {
console.error('There was an error: ' + err);
}
}
./src/app/navigation.model.ts
export class NavItem {
// I am never going to use int unless I need to do math operations //
id: string;
name: string;
pid: string;
slug: string;
constructor(id: string, name: string, pid: string, slug: string) {
this.id = id;
this.name = name;
this.pid = pid;
this.slug = slug;
}
}
./src/app/navigation.component.ts
import { Component } from '#angular/core';
import { NavItem } from './navigation.model';
import { NavService } from './navigation.service';
#Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavComponent {
public section: NavItem;
constructor(private service: NavService) { }
ngOnInit() {
this.service.loadSections();
}
}
What am I doing wrong with this code?
I have never used a service like that before. Maybe your implematation has a simple error, but here is how I always implement a service in angular. :)
Service
getSomething() {
return this.http.get('your-api-url').map(res => {
return res.json()
});
}
Component
makeRequest() {
this.service.getSomething().subscribe(data => {
this.variable = data;
}, err => {
console.log("error :/");
});
}

Angular2 Injecting Service into another Service

I can't find my error.
app.module.ts
...
providers: [ValidateService,AuthService]
...
I do the following in my register.component.ts:
import {AuthService} from '../../services/auth.service';
...
constructor( private _validateService: ValidateService,
private _fms: FlashMessagesService,
private _authService: AuthService,
private _router: Router
) { }
...
ngOnInit() {
this._authService.uniqueUser({username:'zomh'}).subscribe(data => {
console.log("data.success: "+data.success);
if(!data.success) { // Username already exists
console.log('exists');
}
else {
console.log('does not exist');
}
});
}
Works as expected the user is already in the database therefore I get the a user exists in the console.
I do pretty pretty much the very same thing (I broke it down to this point) in my validate.service.ts:
import { AuthService } from './auth.service';
import { Injectable } from '#angular/core';
import { FormControl } from '#angular/forms';
#Injectable()
export class ValidateService {
constructor( public _authService: AuthService) { }
validateRegister(user) {
if(user.name == undefined || user.email == undefined || user.username == undefined || user.password == undefined)
return false;
else
return true;
}
validateEmailPattern(c: FormControl) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(c.value))
return null;
else
return {invalidPattern:true};
}
validateUsernamePattern(c: FormControl) {
const re = /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/
if (re.test(c.value))
return null;
else
return {invalidPattern:true};
}
validateUsernameIsUnique (c: FormControl) {
let ret:any;
if (c.value.length >= 3)
{
console.log(c.value);
this._authService.uniqueUser({username:'zomh'}).subscribe(data => {
if(!data.success) { // Username already exists
console.log('call from service: exists');
}
else {
console.log('call from service: does not exist');
}
});
}
return {usernameIsTaken:true};
}
}
But here I get a Cannot read property _authService of undefined Exception
For me it looks like the service did not inject correctly. But I can't find my error.
Update 1:
So i did copy the auth Service call into the Constructor and its working. Therefore it has to be some this. related error (?) i can't get the value of this._authService from any other method outside of the constructor ?
#Injectable()
export class ValidateService {
constructor( private _authService: AuthService ) {
this._authService.uniqueUser({ username: 'zomh' }).subscribe(data => {
if (!data.success) { // Username already exists
console.log('call from service: exists');
}
else {
console.log('call from service: does not exist');
}
});
}
I dont think you can have a new line between #Injectable and export class ValidateService {
Try it without that line.
After reading an article I rewrote my method into an instance method:
validateUsernameIsUnique = (c: FormControl) => {
let ret: any;
if (c.value.length >= 3) {
this._authService.uniqueUser({ username: c.value }).subscribe(data => {
if (!data.success) { // Username already exists
console.log('call from service: exists');
}
else {
console.log('call from service: does not exist');
}
});
}
...
It fixed the problem. I am still not sure why this had to be done though, feel free to add knowledge

Ionic 2 Waiting for multiple responses

I'm having some problems with my app login page. I'm new with Ionic 2 and Angular and I have tried to figure this out with help of Google but no success so far...
These lines here are causing the problem, alert is returning "undefined" as soon as I click login button, even thought it should wait for response.
let accessToken = this.getAccessToken();
let details = this.getProfileDetails(accessToken);
alert(JSON.stringify(details));
Whole code:
import { Component } from '#angular/core';
import { NavController, Platform } from 'ionic-angular';
import { FbProvider } from '../../providers/fb-provider';
import { TabsPage } from '../tabs/tabs';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Component({
selector: 'page-login',
templateUrl: 'login.html'
})
export class LoginPage {
platform
fb
email
name
id
constructor(public navCtrl: NavController, pf: Platform, fbProvider: FbProvider, public http: Http) {
this.platform = pf;
this.fb = fbProvider;
this.email = '';
this.name = '';
this.id = '';
}
ionViewDidLoad() {
console.log('Hello LoginPage Page');
}
fbLogin() {
let accessToken = this.getAccessToken();
let details = this.getProfileDetails(accessToken);
alert(JSON.stringify(details));
}
getAccessToken(){
this.fb.login().then((fbLoginData) => {
let params = new FormData();
params.append('facebookAccessToken', fbLoginData.authResponse.accessToken);
this.http.post('http://myHostUrl/api/accessToken', params).map(res => res.json())
.subscribe(
data => {
return data.accessToken;
},err => {
alert(err);
}
);
},(err) => {
alert('Facebook login failed');
});
}
getProfileDetails(accessToken){
let params = new FormData();
params.append('accessToken', accessToken);
this.http.post('http://myHostUrl/api/userDetails', params).map(res => res.json())
.subscribe(
data => {
return data;
},err => {
alert(err);
}
);
}
}
It's undefined because you need to wait the asynchronous functions to finish. The following code it's done with rxjs to manage the asynchrony of the two functions and the http calls. try it.
fbLogin() {
this.getAccessToken()
.switchMap(accessToken => this.getProfileDetails(accessToken))
.first() // Just one and complete ....
.subscribe(
details => alert(JSON.stringify(details)),
error => alert(error)
);
}
getAccessToken() : Observable<any> {
return Observable.fromPromise(<Promise<any>> this.fb.login())
.map(fbLoginData => fbLoginData.authResponse.accessToken)
.switchMap(accessToken => {
let params = new FormData();
params.append('facebookAccessToken', accessToken);
return this.http.post('http://myHostUrl/api/accessToken', params)
.map(res => res.json())
.map(data => data.accessToken)
});
}
getProfileDetails(accessToken) : Observable<any>{
let params = new FormData();
params.append('accessToken', accessToken);
return this.http.post('http://myHostUrl/api/userDetails', params).map(res => res.json());
}

Categories