I'm new to a course of Angular with rxjs and our group have received a challenge.
They've sent us a chunk of code and said that althought it works fine, the code is not clean and the function "getUserLogin" has some duplicity in comparison with the "login" method. So, the challenge is to identify it and remove this duplicity.
Here is the code:
login(): Observable<UserModel> {
if (!sessionStorage["userCredentials"]){
return this.http.get<UserModel>(`${environment.apiLogin}/user/login?sistema=${enviroment.system}`,
{ withCredentials: true })
.pipe(
tap((res: UserModel) => {
sessionStorage["userCredentials"] = JSON.stringify(res);
})
);
} else {
return Observable.of(<UserModel>JSON.parse(sessionStorage["userCredentials"]));
}
}
getUserLogin(): Observable<string> {
if (sessionStorage["userCredentials"]){
const userData = <UserModel>JSON.parse(sessionStorage["userCredentials"])
return Observable.of(userData.login);
}
return Observable.of("");
}
Any help in solving this challenge would be very appreciated. Thanks in advance!
Something like this:
login(): Observable<UserModel> {
const cred = this.getUserCredentialsFromStorage();
if (!cred){
return this.http.get<UserModel>(`${environment.apiLogin}/user/login?sistema=${enviroment.system}`,
{ withCredentials: true })
.pipe(
tap((res: UserModel) => {
sessionStorage["userCredentials"] = JSON.stringify(res);
}));
}
return Observable.of(cred);
}
getUserCredentialsFromStorage(): UserModel {
if (sessionStorage["userCredentials"]) {
const userData = <UserModel>JSON.parse(sessionStorage["userCredentials"]);
return userData;
}
return null;
}
getUserLogin(): Observable<string> {
const cred = getUserCredentialsFromStorage();
return Observable.of(cred ? cred.login : '');
}
Note that in refactoring this, you need to pay careful attention to types.
Both getUserLogin and login parse the sessionStorage object to get a UserModel, then do different things with it. One returns an Observable<UserModel> and the other extracts the login string and returns an Observable<string>. So we can refactor out a function that parses the UserModel and have both functions call that.
From the question, the only thing I can infer is that there are duplicate checks for sessionStorage value in both the functions.
IMO below should be the revised code
login(): Observable<UserModel> {
this.getUserLogin().mergeMap(res => {
if (res === '') {
return this.http.get<UserModel>(`${environment.apiLogin}/user/login?sistema=${enviroment.system}`,
{ withCredentials: true })
.pipe(
tap((res: UserModel) => {
sessionStorage["userCredentials"] = JSON.stringify(res);
this.userData(sessionStorage["userCredentials"])
})
);
} else {
this.userData(sessionStorage["userCredentials"])
}
})
}
getUserLogin(): Observable<string> {
if (sessionStorage["userCredentials"]) {
this.userData(sessionStorage["userCredentials"])
}
return Observable.of("");
}
userData(data) {
const userData = <UserModel>JSON.parse(data)
return Observable.of(userData.login);
}
Related
I would just like to ask for suggestions and ideas about my current issue , I have a method which is getUserRoles which is fetching the logged-in user roles list.
The problem I have is that there are instances that the getUserGeneralDetails from the getUserRoles that is causing infinity , it is calling the getUserGeneralDetails infinitely and I dont understand why and what causes it.
Maybe somone can enlighten me with some problem with the code snippet I have below. Would be appreciatd, Thanks.
#method
//In this method, we are just fetching the logged-in user roles list.
public getUserRoles(): Promise<string[]> {
if (this.authUser === null) {
this.setAuthUser()
}
return new Promise((resolve, reject) => {
this.userService
.getUserGeneralDetails(this.authUser.nameid , this.accountId = 0)
.pipe(catchError((error: any, caught: any) => {
reject(error);
return caught;
}),
map((res: any) => res.data && res.data.userAccountDto
.filter(account => account.accountName === res.data.associatedAccount)
.map(account => account.userRoleDto.roleName)
)).subscribe((role: string[]) => {
resolve(role);
});
});
}
#code that calls the getUserRoles method
this.authService
.loginWithCredentials(username, password)
.then((r: any) => {
localStorage.removeItem("previousUrl");
if (r.isSuccess) {
this.authService.getUserRoles().then((userRoles: string[]) => {
localStorage.setItem("userRoles", JSON.stringify(userRoles));
})
this.route.navigateByUrl('/');
}else{
if(r.errors.length > 0){
if(r.errors[0].description.indexOf('not found') !== -1){
errMsg = ERR_FORM_MSG.INVALID_LOGIN;
}
}
this.apiErrMsg = errMsg;
}
})
.catch((e): any => {
this.apiErrMsg = e.message
? e.message
: errMsg;
})
.finally(() => {
this.hasSubmit = false;
this.closeLoader();
});
}
#user details
getUserGeneralDetails(id: number , accountId:number): Observable<any> {
const params = new HttpParams()
.set('id', id.toString())
.set('accountId', accountId.toString());
return this.httpRequestService.get<any>(`${apiBaseUrl}/detail` , params);
}
I would like to redirect users when they sign in with Github or others based on whether they are a new user or a returning user. I'm having trouble accessing the isNewUser property referenced in this answer: How to differentiate signin and signup user in firebase using google auth?
I have a standard sign in function:
const signinWithGoogle = () => {
return auth.signInWithPopup(googleProvider)
.then((response) => {
handleUser(response.user)
})
}
This is the handleUser function:
const handleUser = (rawUser) => {
if (rawUser) {
const currentUser = formatUser(rawUser)
createUser(currentUser.uid, currentUser)
setCurrentUser(currentUser)
if (currentUser.providerData[0].isNewUser===true) {
history.push("/onboarding")
} else {
history.push("/")
}
return currentUser
}
else {
setCurrentUser(false)
return false
}
}
And this is formatUser:
const formatUser = (user) => {
return {
uid: user.uid,
email: user.email,
name: user.displayName,
provider: user.providerData[0].providerId,
avatar: user.photoURL,
}
}
Can anyone see what I'm doing wrong, please?
Cheers, Matt
EDIT:
If we pass the response to the HandleUser function and console log response.additionalUserInfo.isNewUser we get 'true'. However, if we use that in our if statement, it seems to be ignored for some reason
const handleUser = (response) => {
if (response) {
console.log("response: ", response.additionalUserInfo.isNewUser)
const currentUser = formatUser(response.user)
createUser(currentUser.uid, currentUser)
setCurrentUser(currentUser)
console.log('response', response)
console.log('additional info', response.additionalUserInfo)
const isNew = response.additionalUserInfo.isNewUser
console.log('isNewUser', isNewUser)
if (isNew) {
console.log('redirecting to /onboarding')
history.push("/onboarding")
} else {
console.log('redirecting to /')
history.push("/")
}
return currentUser
}
else {
setCurrentUser(false)
return false
}
}
EDIT 2: Here is the output from the console logs
That error is coming from the signInWithGithub function in the modal
async function signInGitHub() {
try {
await signinWithGitHub()
}
catch(err) {
console.log("Error: ",err.code)
}
finally {
closeModal();
}
}
It looks like you are passing a User to that function and not the raw response. The isNewUser is present on the additionalUserInfo property. Please try refactoring as shown below:
const handleUser = (rawUser) => {
if (rawUser) {
const currentUser = formatUser(rawUser.user)
createUser(currentUser.uid, currentUser)
setCurrentUser(currentUser)
if (currentUser.additionalUserInfo.isNewUser) {
history.push("/onboarding")
} else {
history.push("/")
}
return currentUser
}
else {
setCurrentUser(false)
return false
}
}
Also make sure you pass the raw response:
handleUser(response.user)
I'm using an rxjs epic as a middleware for an async action in a react-redux app.
I'm trying to simulate an ajax request (through a dependency injection) and test the behavior of this epic based on the response.
This is my epic :
export const loginEpic = (action$, store$, { ajax }) => { // Ajax method is injected
return action$.ofType(LoginActions.LOGIN_PENDING).pipe(
mergeMap(action => {
if (action.mail.length === 0) {
return [ loginFailure(-1) ]; // This action is properly returned while testing
} else {
return ajax({ ... }).pipe(
mergeMap(response => {
if (response.code !== 0) {
console.log(response.code); // This is logged
return [ loginFailure(response.code) ]; // This action is expected
} else {
return [ loginSuccess() ];
}
}),
catchError(() => {
return [ loginFailure(-2) ];
})
);
}
})
);
};
This part test if the mail adress is empty and works just fine (Or at least just as expected):
it("empty mail address", () => {
testScheduler.run(({ hot, expectObservable }) => {
let action$ = new ActionsObservable(
hot("a", {
a: {
type: LoginActions.LOGIN_PENDING,
mail: ""
}
})
);
let output$ = loginEpic(action$, undefined, { ajax: () => ({}) });
expectObservable(output$).toBe("a", {
a: {
type: LoginActions.LOGIN_FAILURE,
code: -1
}
});
});
});
However, I have this second test that fails because the actual value is an empty array (There is no login failed returned):
it("wrong credentials", () => {
testScheduler.run(({ hot, cold, expectObservable }) => {
let action$ = new ActionsObservable(
hot("a", {
a: {
type: LoginActions.LOGIN_PENDING,
mail: "foo#bar.com"
}
})
);
let dependencies = {
ajax: () =>
from(
new Promise(resolve => {
let response = {
code: -3
};
resolve(response);
})
)
};
let output$ = loginEpic(action$, undefined, dependencies);
expectObservable(output$).toBe("a", {
a: {
type: LoginActions.LOGIN_FAILURE,
code: -3
}
});
});
});
Any idea on what I'm doing wrong / why this part returns an empty array (The console.log does actually log the code):
if (response.code !== 0) {
console.log(response.code);
return [ loginFailure(response.code) ];
}
While this part returns a populated array:
if (action.mail.length === 0) {
return [ loginFailure(-1) ];
}
I'm guessing the use of Promise is causing the test to actually be asynchronous. Try changing the stub of ajax to use of(response) instead of from
I am new in nodejs and mongodb. Its really very confusing to use promise in loop in nodejs for new developer.I require the final array or object. which then() give me final result. Please correct this.
I have a controller function described below.
let League = require('../../model/league.model');
let Leaguetype = require('../../model/leagueType.model');
let Leaguecategories = require('../../model/leagueCategories.model');
let fetchLeague = async function (req, res, next){
let body = req.body;
await mongo.findFromCollection(Leaguetype)
.then(function(types) {
return Promise.all(types.map(function(type){
return mongo.findFromCollection(Leaguecategories, {"league_type_id": type._id})
.then(function(categories) {
return Promise.all(categories.map(function(category){
return mongo.findFromCollection(League, {"league_category_id": category._id})
.then(function(leagues) {
return Promise.all(leagues.map(function(league){
return league;
}))
.then(function(league){
console.log(league);
})
})
}))
});
}))
})
.then(function(final){
console.log(final);
})
.catch (error => {
console.log('no',error);
})
}
mongo.findFromCollection function is looking like this.
findFromCollection = (model_name, query_obj = {}) => {
return new Promise((resolve, reject) => {
if (model_name !== undefined && model_name !== '') {
model_name.find(query_obj, function (e, result) {
if (!e) {
resolve(result)
} else {
reject(e);
}
})
} else {
reject({ status: 104, message: `Invalid search.` });
}
})
}
and here is my model file
var mongoose = require('mongoose');
const league_categories = new mongoose.Schema({
name: {
type: String,
required: true
},
active: {
type: String,
required: true
},
create_date: {
type: Date,
required: true,
default: Date.now
},
league_type_id: {
type: String,
required: 'league_type',
required:true
}
})
module.exports = mongoose.model('Leaguecategories', league_categories)
First i recommend you stop using callbacks wherever you can, its a bit dated and the code is much harder to read and maintain.
I re-wrote your code a little bit to look closer to what i'm used to, this does not mean this style is better, i just personally think its easier to understand what's going on.
async function fetchLeague(req, res, next) {
try {
//get types
let types = await Leaguetype.find({});
//iterate over all types.
let results = await Promise.all(types.map(async (type) => {
let categories = await Leaguecategories.find({"league_type_id": type._id});
return Promise.all(categories.map(async (category) => {
return League.find({"league_category_id": category._id})
}))
}));
// results is in the form of [ [ [ list of leagues] * per category ] * per type ]
// if a certain category or type did not have matches it will be an empty array.
return results;
} catch (error) {
console.log('no', error);
return []
}
}
I have this block of code in a function:
this.apiService.fetchCategories(!this.cacheData).subscribe(
response => {
if(this._jsnValService.valCategories(response)) {
this.customerMap.categories = this.formatCategories(response["categories"]);
} else {
alert("Categories failed the schema validation. Please contact support if this happens again.");
}
},
error => {
this.notification.title = "Oops, there's a problem.";
this.notification.content = "Seems there's an issue getting the provider categories.";
this.notification.show("provider_categories_api");
}
);
It fetches some data and then runs a validation on the data (if(this._jsnValService.valCategories(response)) {).
However, my validation of the data is actually async too, because it validates it against a json schema which is in a seperate json file so it has to read the file first.
I have used a promise to read the file contents and then do the validation:
#Injectable()
export class ValidateJSONSchemaService {
constructor(private http: Http) {}
public valCategories(json) {
this._getSchema("./jsonSchema.categories.json").then((schema) => {
this._valSchema(json, schema);
});
};
private _valSchema(json, schema): any {
var ajv = new Ajv();
var valid = ajv.validate(schema, json);
if (!valid) {
console.log(ajv.errors);
return false;
} else {
console.log(valid);
return true;
};
};
private _getSchema(fileName): any {
return new Promise((resolve, reject) => {
this.http.get(fileName)
.map(this._extractData)
.catch(this._handleError)
.subscribe(schema => resolve(schema));
});
};
private _extractData(res: Response) {
let body = res.json();
return body.data || {};
};
How can I edit the top code block in this question to account for the asynchronous function inside the if statement (if(this._jsnValService.valCategories(response)) {)?
if you are using ES6 you could use async/await like this:
async function _validateCategories() {
this.apiService.fetchCategories(!this.cacheData).subscribe(
response => {
const valid = await this._jsnValService.valCategories(response)
if(valid) {
this.customerMap.categories = this.formatCategories(response["categories"]);
} else {
alert("Categories failed the schema validation. Please contact support if this happens again.");
}
},
error => {
this.notification.title = "Oops, there's a problem.";
this.notification.content = "Seems there's an issue getting the provider categories.";
this.notification.show("provider_categories_api");
}
);
}
if not, your function fetchCategories should return a promise or allow you to pass a callback to do something like this:
async function _validateCategories() {
this.apiService.fetchCategories(!this.cacheData).subscribe(
response => {
this._jsnValService.valCategories(response).then((error, valid)=> {
if(error) {
alert("Categories failed the schema validation. Please contact support if this happens again.");
}
this.customerMap.categories = this.formatCategories(response["categories"]);
})
},
error => {
this.notification.title = "Oops, there's a problem.";
this.notification.content = "Seems there's an issue getting the provider categories.";
this.notification.show("provider_categories_api");
}
);
}