Angular/Firebase: How to put user response in front-end model - javascript

I am new to both. My thought process is as follows. When I login in with Angular to my Firebase backend I send a response to the console if it succeeds. In this response I can see all the keys that a firebase user has. What I want to do is to link these to/in a user model in my front-end so I can access it easily when showing a user profile page or something else. (I don't know if this is the correct thought process too. Nudge me in the right way if you know a better solution)
auth.service.ts
constructor(
private angularFireAuth: AngularFireAuth,
) {
this.userData = angularFireAuth.authState;
}
signIn(email: string, password: string) {
return this.angularFireAuth.auth.signInWithEmailAndPassword(email, password);
}
login.component.ts
signIn(email: string, password: string) {
this.spinnerButtonOptions.active = true;
email = this.loginForm.value.email;
password = this.loginForm.value.password;
this.auth.signIn(email, password).then(
res => {
console.log('signed in ', res);
this.router.navigate(['/dashboard']);
}
).catch(
error => {
console.log('something went wrong ', error);
this.formError = true;
this.spinnerButtonOptions.active = false;
}
);
}
How would I got on to do this? I've searched everywhere and can't find any solution. Is this actually the correct way? If there is a better way please let me know!

You can use your auth service to store the data returned from the Firebase backend. Or else you can store it in a shared service where it's been available throughout the all components and modules.
In your auth service :
#Injectable()
export class AuthService{
public usermodel:any;
constructor(private angularFireAuth: AngularFireAuth) {
this.userData = angularFireAuth.authState;
}
signIn(email: string, password: string) {
return this.angularFireAuth.auth.signInWithEmailAndPassword(email, password);
}
setLoggedInUserData(userDetails: any) {
this.usermodel = userDetails;
}
getLoggedInUserData() {
return this.usermodel;
}
}
In you login component.ts :
signIn(email: string, password: string) {
this.spinnerButtonOptions.active = true;
email = this.loginForm.value.email;
password = this.loginForm.value.password;
this.auth.signIn(email, password).then(
res => {
this.authService.setLoggedInUserData(res);
this.router.navigate(['/dashboard']);
}
).catch(
error => {
console.log('something went wrong ', error);
this.formError = true;
this.spinnerButtonOptions.active = false;
}
);
}
In other components where you need to use the use details inject the auth.service.ts and use the getLoggedInUserData() method fetch the logged in users details.
There are several other ways of doing this too. One option is to use the ngrx store implementation. Other ways is to use global data service at the root level of your angular app to store the user details.

Related

How do I create a HTTP post using React/Typescript Frontend and C#/.Net Backend?

On the frontend code, I have a user login form that takes in the values (strings) email and password. In my userstore using MobX State Management, I have an action when a user presses the login button to submit the strings as an HTTP post
#action login = async (values: IUserFormValues) => {
try {
console.log(values);
const user = await agent.User.login(values);
runInAction(() => {
this.user = user;
});
console.log(user);
} catch (error) {
console.log(error);
}
};
}
The Request looks something like this:
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
post: (url: string, body: {}) =>
axios.post(url, body).then(sleep(1000)).then(responseBody),
};
login: (user: IUserFormValues): Promise<IUser> =>
requests.post(`/user/login`, user)
Now to the backend, this is where I am completely lost. Not sure what to build from here:
[HttpPost("login")]
- Here -
I am gonna have to take these values and verify with a database from SQL server. There's just so many different examples using different middleware that I am just not sure what's correct or best practices.
Here is a very nice tutorial from the Microsoft Docs, using Entity Framework (very nice), and some dependency injection (very very nice).
Basically you create an API controller class with your CRUD methods in them like so:
namespace MyApiControllerClass
{
[Authorize]
[RoutePrefix("users")]
public class UsersApiController : ControllerBase
{
private readonly UserContext _context;
public UsersApiController(UserContext context)
{
_context = context;
}
[Route("/login")]
public IHttpActionResult LoginUser(User user)
{
try
{
// login logic here
return Ok(); // you can return whatever you need
}
catch (Exception exception)
{
// log any issues using your preferred method of logging
return InternalServerError(); // you can return different status codes as well. Depends on what you want
}
}
}
}
You can read more about the Authorize annotation here and customize it to your liking.
Then you fire up your web project which will be available at a local URL that you can set in the project's configuration say http://localhost:4000/ which then makes your controller URL available at http://localhost:34501/users/login. Then you use this URL in your Javascript call and add the User object in the request body.

Receiving [Error: Internal] in RN app when triggering a Cloud Function

I have a Google Cloud Function which I am calling from my RN app but it is returning
[Error: Internal]
I have set the permission to Unauthenticated users so anyone can call it - for testing purposes only. When I set to Authenticated users permission, it throws another error [Error: Unauthenticated] eventhough I am authenticated and I can get the currentUser id in my app.
Tried searching for this error but it didnt send me to any possible solutions so decided to post here and hopefully recieve responses that will help me fix it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const newUser = {
email: data.email,
emailVerified: false,
password: data.password,
disabled: false
}
const role = data.role;
const userRecord = await admin
.auth()
.createUser(newUser);
const userId = userRecord.uid;
const claims = {};
claims[role] = true;
await admin.auth().setCustomUserClaims(userId, claims);
return { result: 'The new user has been successfully created.' };
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
in my RN app I am calling it like this:
var user = {
role: role
}
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
const createUser = await functionsForRegion.httpsCallable('createUser');
createUser(user)
.then((resp) => {
//Display success
});
console.log(resp.data.result);
})
.catch((error) => {
console.log("Error on register patient: ", error)
});
I think the way I am calling it in my RN app is correct because I have tested it with a testFunction and I returned a simple string. So, I believe the problem is somewhere in the function itself.
EDIT: I just tested by simply calling the function and returning the context and it always returns Internal error:
exports.registerNewPatient = functions.region('europe-west3').https.onCall((data, context) => {
return context; //this is returned as INTERNAL error.
}
I just cant get to understand whats going on here, why does it return Internal error when I am authenticated as a user and it should return the authenticated user data, isn't that right?
Try some console.log(context) ; console.log(data) statements in your registerNewPatient function and take a look at the logs. What do they say?
Some other things to consider might include that in your client code you use europe-west1 while your function code has europe-west3. Try to have those line up and see if it works? From my experience, if a specified function isn't found to exist, the client receives an INTERNAL error.

Storing user in database within Firestore

I am working with Firebase and I'm having some troubles. When creating a new user, I am able to store it in my database, but later, when accessing to another component it fails.
//Register a new user in our system
registerUserByEmail(email: string, pass: string) {
return this.afAuth.auth
.createUserWithEmailAndPassword(email, pass)
.then(res => {
this.email = res.user.email;
let user = {
email: this.email,
goal: "",
previousGoals: [],
progress: {
accomplishedToday: false,
completedGoals: 0,
daysInRow: 0,
unlockedBadges: []
}
};
// store in database
new Promise<any>((resolve, reject) => {
this.firestore
.collection("users")
.add(user)
.then(
res => {
console.log(res.id);
this.isAuthenticated = true;
this.router.navigate(["/dashboard"]);
},
err => reject(err)
);
});
});
}
I believe that this piece of code is basically registering as a user the email and storing it successfully into my database (checked it).
Nevertheless, when rendering home.component or /dashboard
home.component
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
getUser() {
this.data.getUser().subscribe(user => {
this.user = user.payload.data();
});
}
data.service
getUser() {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
I get the following error
ERROR
TypeError: Cannot read property 'uid' of null
It looks like by the time you call getUser the user hasn't been authenticated yet.
The simple fix to get rid of the error is to check for this condition in your DataService's getUser:
getUser() {
if (this.currentUser) {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
}
Given this sequence of calls however, I think there may be a better way to handle your use-case:
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
Since you're attaching an auth state listener, you probably want to only start watching the user's data in Firestore once the user has actually been authenticated.
Once simple way to do that is to call out to your DataService's getUser() method from within the auth state listener, once the user is authenticated. Something like this:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.getUser(); // call the DataService's getUser method
}
});

Angular 7: Send response message from service

I make request from service file in my Angular project:
signIn(username: string, password: string): Observable<any> {
const formData = ...
this.http.post(`${this.uri}`, formData, httpOptions)
.subscribe(res => console.log(
if (statusCode === '0') {
this.message = 'test';
} else {
this.message2 = 'test2';
}
})));
return;
}
This function works very well. But I cannot shot my message in my HTML. When I type {{message}} in HTML, its undefined.
I declared message: String; but its not work.
I guess services are private and this problem is so.
How can I show my response in HTML? What is the best way?
UPDATED: This is my component:
message: String;
signIn(username, password): Observable<any> {
this.cookieService.deleteAll();
this.ls.signIn(username, password).subscribe(res =>
xml2js.parseString(res, function (err, result) {
const statusCode = ...
console.log(statusCode); // it getting statusCode
if (statusCode === '0') {
this.message = 'test'; //Potentially invalid reference access to a class field via 'this.' of a nested function
} else {
}
}));
return;
}
You can not access component fields in service. You need to pass response of service by some way and then only you can use that response and based on that assign value to message and display in your html.
I think you should use callback in which you'll wrap your http request's response and will use at outer layer of service.
signIn(username: string, password: string, data:any) {
const formData = ...
this.http.post(`${this.uri}`, formData, httpOptions)
.subscribe(response => {
data(response);
}, error => data(// do something here);
}
now use this as something like this(at component I think):
message: String;
this.restService.signIn(userName, password, response => {
if(response && response.hasOwnProperty('statusCode')) {
if (response['statusCode'] === '0') {
this.message = 'test';
} else {
this.message = 'test2';
}
}
});
now you can use message in html
{{message}}
NOTE
You should always use service layer for data transfer only(if it is rest/api service), business logic or data manipulation should be used at some intermediate service or component level.
At this way you can make generic rest service and use across application, you just need to pass request body, url and response callback object.
If you're going to write logic in service then you'll have to write separate service for each api call.
Angular templates will search variables only in component which we are declared in them. So do changes like below in your template
<p> {{service.message}} </p>
Here the service is which you are injected into your component.
If you are declare message:string in your service that's fine, by default variables are public and we can access them in templates. But with the help of service variable which we are injected them through constructor.
I hope this will solve your issue :)
I suppose your sample code is part of a service class : you cannot access service attributes like message or message2 from your HTML template.
You have to return HTTP observable to the controller and put the subscribe logic in your controller.
Service:
return this.http.post(`${this.uri}`, formData, httpOptions);
Controller:
message: String;
signIn(username, password): Observable {
this.cookieService.deleteAll();
this.ls.signIn(username, password).subscribe(res =>
const self = this;
xml2js.parseString(res, function (err, result) {
const statusCode = ...
console.log(statusCode); // it getting statusCode
if (statusCode === '0') {
self.message = 'test';
} else {
}
}));
return;
}

How to chain/combine Observables

Ok this has been bugging me for a while, wondering if any one could show me a good way of chaining Observables between multiple services.
In the example below in the Auth class what would be a good way of creating an Observable from the this.api.postSignIn() so the signInSubmit() can subscribe to it back in the component? It is worth noting that this.api.postSignIn() is subscribing to an Angular2 http request.
Is this a bit of an anti pattern and is there better ways of doing this?
Basically the functionality I would like to achieve is:
Component - responsible for collecting the sign in data and sending it to the auth service in the correct format. Then once the Auth sign in is complete navigate to the admin page.
Service - Make api call to get token, set token via the token service and set isSignedIn bool then defer control back to the calling component.
#Component({...})
export class SignIn {
private signIn:SignInModel = new SignInModel();
constructor(private auth:Auth, private router:Router) {
}
ngOnInit() {
}
signInSubmit() {
this.auth.signIn(this.signIn)
.subscribe(
() => {
this.router.navigate(['/admin']);
}
)
}
}
#Injectable()
export class Auth {
private isSignedIn:boolean = false;
constructor(private api:Api, private tokenService:TokenService) {
}
public signIn(signIn:SignInModel) {
return this.api.postSignIn(signIn)
.subscribe(
response => {
this.tokenService.set(new TokenModel(response.token));
this.isSignedIn = true;
},
error => {
console.log(error);
}
);
}
public signOut() {
}
}
I would leverage the do and catch operators instead of subscribing within the signIn method.
Here is the refactored signIn method:
public signIn(signIn:SignInModel) {
return this.api.postSignIn(signIn)
.do(
response => {
this.tokenService.set(new TokenModel(response.token));
this.isSignedIn = true;
})
.catch(
error => {
console.log(error);
}
);
}
In your case, you can't subscribe on the returned object of this method since the subscribe method returns a subscription and not an observable. So you can't subscribe on it...

Categories

Resources