Angular 2 and native Promises - javascript

I'm having issue using native promises with second angular and typescript:
export class LoginComponent implements OnInit {
public user = {};
constructor( private authService:AuthenticationService) { }
ngOnInit() {
}
login() {
console.log( 'Connecting to server' );
this.authService.login( this.user ).then(( response ) => {
// works
console.log( response['success'] );
// error
console.log( response.success );
}, ( error ) => {
console.log( error );
});
}
}
below is simple service, with fake connect to the server:
export class AuthenticationService {
// ... some code ...
login( loginData ) {
let self = this;
return new Promise(function(resolve, reject){
// fake delay - for now is no back end
setTimeout(function() {
if ( loginData.username === 'username' && loginData.password === 'password' ) {
resolve({
message: "Successfully logged in",
success: true,
errors: null
});
} else {
reject({
message: "Wrong user data reperesented",
success: false,
errors: {
"username": true,
"password": true
}
});
}
}, 100);
});
}
// ... some code ...
}
Tried to search what I have to do to solve Property 'success' does not exist on type '{}'. error but without success.

This happens because the code above is not typed.
To make this work it should be either
login( loginData ): Promise<any> { ... }
or
this.authService.login( this.user ).then(( response: any ) => { ... })
A better way is to make types work for you instead of ignoring them:
interface IAuthLoginResponse {
message: string;
success: boolean;
errors: any;
}
...
login( loginData ): Promise<IAuthLoginResponse> {
return new Promise<IAuthLoginResponse>(function (resolve, reject) { ... })
}

Related

Jest: to check if a function is called by a specific instance of class in JavaScript/Typescript

I am testing some express middlewares with jest.
it("should throw 400 error if request.body.id is null", () => {
const req = { body: { id: null } } as any;
const res = {} as any;
const next = jest.fn();
myMiddleware(req, res, next);
expect(next).toBeCalledWith(expect.any(ErrorResponse));
expect(next).toBeCalledWith(
expect.objectContaining({
statusCode: 400,
errCode: "error-0123-2342",
message: "Field id is missing",
})
);
});
my ErrorResponse:
export class ErrorResponse extends Error {
public statusCode: number;
public errCode: string;
constructor(
statusCode: number = 500,
errCode: string = "error-123-1993",
message: string = "Internal Server Error"
) {
super(message);
this.statusCode = statusCode;
this.errCode = errCode;
}
}
I manage to check if the ErrorResponse with specific property is called in the next function, but it doesn't guarantee the ErrorResponse Object contains only 3 properties (statusCode, errCode, message) if someone change the ErrorResponse to add One more property, such as details.
I would like to do something below and guarantee the ErrorResponse Object contains only 3 properties (statusCode, errCode, message).
it("should throw 400 error if request.body.id is null", () => {
const req = { body: { id: null } } as any;
const res = {} as any;
const next = jest.fn();
myMiddleware(req, res, next);
expect(next).toBeCalledWith(
new ErrorResponse(
400,
"error-3123-2332",
"Field id is missing"
)
);
});
May I know if there is an way to do it in jest?
After a cursory look at the jest documentation, it seems that expect.extend might do what you want:
expect.extend({
toBeErrorResponse(received) {
if (received instanceof ErrorResponse &&
Object.keys(received).length === 2)
return {
message: () => "expected no ErrorResponse",
pass: true,
};
else
return {
message: () => "expected an ErrorResponse",
pass: false,
};
}
});
test("my ErrorResponse", () => {
const next = jest.fn();
next(new ErrorResponse(400, "E", "M"));
expect(next).toBeCalledWith(expect.toBeErrorResponse());
});

Error during add AsyncValidator in angular

I'm trying to add a new AsyncValidator to check whether user's email already exist in database.
Below is may validator:
export class UniqueEmailValidator implements AsyncValidator {
constructor(private webService: WebWrapperService) {}
validate(ctrl: AbstractControl): Promise < ValidationErrors | null > | Observable < ValidationErrors | null > {
return this.webService.isEmailExistEx(ctrl.value)
.pipe(
map(res => {
console.log("get response" + res);
if (res) {
return { 'uniqueEmail': true};
}
return null;
})
);
}
}
The function isEmailExistEx in service will send a post request to server.
isEmailExistEx(email: string): Observable<boolean> {
this.http.post(this.baseUrl + "auth/verify",
{
"email": email
})
.subscribe(
(val: any) => {
if (!val.result) {
return of(false);
} else {
return of(true);
}
},
response => {
return of(false);
},
() => {
return of(false);
});
}
It reports following error:
A function whose declared type is neither 'void' nor 'any' must return a value.
How should I modify this function?
You're subscribeing to the Observable which will consume the value wrapped in it.
Use map instead of subscribeing and return a boolean value from it::
isEmailExistEx(email: string): Observable<boolean> {
return this.http.post(this.baseUrl + "auth/verify", { email })
.pipe(
map((val: any) => val.result ? true : false)
);
}

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'});

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

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!

How to handle conditions after asynchronous request

Thanks for reading my question in advance. I'm using the dva and Ant Design Mobile of React handling phone register function.
Before sending the verify code, I will judge if the phone has been registered. If yes, it will Toast " This phone has been registered".
Now, the return value is correct:
const mapStateToProps = (state) => {
console.log(state.register.message)
}
// {code: 221, message: "This phone has been registered"}
So I write it as:
const mapStateToProps = (state) => ({
returnData: state.register.message
})
And then when I click the button, it will dispatch an action (send a request):
getVerifyCode() {
const { form, returnData } = this.props;
const { getFieldsValue } = form;
const values = getFieldsValue();
this.props.dispatcher.register.send({
phone: values.phone,
purpose: 'register',
})
// if(returnData.code === 221){
// Toast.fail("This phone has been registered", 1);
// } else {
// Toast.success("Send verify code successfully", 1);
// }
}
But when I tried to add the if...else condiction according to the return value
if(returnData.code === 221){
Toast.fail("This phone has been registered", 1);
} else {
Toast.success("Send verify code successfully", 1);
}
only to get the error:
Uncaught (in promise) TypeError: Cannot read property 'code' of
undefined
I supposed it's the problem about aynchromous and tried to use async await:
async getVerifyCode() {
...
await this.props.dispatcher.register.send({
phone: values.phone,
purpose: 'register',
})
}
But get the same error
Cannot read property 'code' of undefined
I wonder why and how to fix this problem ?
added: this is the models
import * as regiserService from '../services/register';
export default {
namespace: 'register',
state: {},
subscriptions: {
},
reducers: {
save(state, { payload: { data: message, code } }) {
return { ...state, message, code };
},
},
effects: {
*send({ payload }, { call, put }) {
const { data } = yield call(regiserService.sendAuthCode, { ...payload });
const message = data.message;
yield put({ type: 'save', payload: { data },});
},
},
};
handle conditions in the models solved the problem:
*send({ payload }, { call, put }) {
const { data } = yield call(regiserService.sendAuthCode, { ...payload });
if(data.code === 221){
Toast.fail("This phone has been registered", 1);
} else {
Toast.success("Send verify code successfully", 1);
}
yield put({ type: 'save', payload: { data }});
}

Categories