I am using firebase auth and when a user logs it, I'm trying to get his uid (key) and save it.
This is my code (Here console.log(that.uid); returns the key):
loginWithEmail(email, password)
{
// Resolving scope problems in TypeScript
let that = this;
return this.af.auth.login(
{
email: email,
password: password
},
{
provider: AuthProviders.Password,
method: AuthMethods.Password,
}).then((user) =>
{
that.uid = user.uid;
// console.log(that.uid);
})
}
and I have a function that gets called from another component that returns the uid:
getUid()
{
console.log(this.uid);
return this.uid;
}
Here console.log(this.uid); returns 'undefined'.
Why does it happen and how can I resolve it?
EDIT:
I was read the answers below and understand that the problem is with async.. but I didn't find a solution how to save my data (in this example: user id) from async function to sync one.
I will be glad if someone can offer a solution to my code so I will be able to understand how to fix it and to use well async and sync.
EDIT2:
getUid() function is provide inside afservice whith the loginWithEmail(email, password) function.
getUid() was called from another component, in my case HomeComponent in this way:
export class HomeComponent implements OnInit
{
permission: number;
constructor(private afService: AF, private router: Router)
{
console.log(afService.getUid());
}
}
thanks.
Related
Hi I am trying to figure out how to create factory and define relationship between models.
For example I have UserFactory with User entity and this entity has connection to userType table. In factory I have not access to EntityManager so I couldnĀ“t find any existing.
export class UserFactory extends Factory<User> {
model = User
definition(faker: Faker): Partial<User> {
const user = {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
...
userType: // Here I need do something like this:
// EntityManager.findOne(UserType, {id: 1}}
// But EntityManager is private in Factory class
}
return user
}
}
Itried also something like this but this return me an error:
ValidationError: Value for User.type is required, 'undefined' found
DatabaseSeeder
export class DatabaseSeeder extends Seeder {
async run(em: EntityManager): Promise<void> {
const users: User[] = new UserFactory(em).each(async user => {
const userType : UserType| null = await em.findOne(UserType, 1)
console.log(tenant)
const userType = await em.findOne(UserType, 1)
if (userType !== null) {
user.type = userType
} else {
user.type = em.create(UserType, {
type: 'test'
})
}
}).make(10)
}
}
What is the proper way to achieve this please?
You can use the shared seeder context as describer in the docs:
https://mikro-orm.io/docs/seeding#shared-context
export class AuthorSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
// save the entity to the context
context.author = em.create(Author, {
name: '...',
email: '...',
});
}
}
export class BookSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
em.create(Book, {
title: '...',
author: context.author, // use the entity from context
});
}
}
I guess this shared context should be also available in the seeder factories, but you can always handle this yourself, as both the seeder and factory is your implementation, so you can pass any additional options in there. Its you who initializes the factory so I dont think there is a better way than doing it in your code.
I would suggest not to flush and findOne things in your seeder, you should aim for a single flush and use the shared context instead for entity look up.
I've made a facade service to avoid multiple calls to the API.
It call retrieveMyUser each time the request is made.
If the request has never been made it store the value usingBehaviorSubject. If it has already been made it take the value stored.
I want to clear the data of my BehaviorSubject in auth.service.ts when a user logout. My try to do that is that I call a clearUser() method from facade-service.ts.
facade-service.ts :
...
export class UserServiceFacade extends UserService {
public readonly user = new BehaviorSubject(null);
retrieveMyUser() {
console.log(this.user.value);
return this.user.pipe(
startWith(this.user.value),
switchMap(user => (user ? of(user) : this.getUserFromServer())),
take(1)
)
}
private getUserFromServer() {
return super.retrieveMyUser(null, environment.liveMode).pipe(tap(user => this.storeUser(user)));
}
public clearUser() {
console.log("cleared");
this.storeUser(null)
console.log(this.user.value); // Output null
}
private storeUser(user: V2UserOutput) {
this.user.next(user);
}
}
auth.service.ts :
...
logout() {
var cognitoUser = this.userPool.getCurrentUser();
if (cognitoUser) {
this.userServiceFacade.clearUser()
cognitoUser.signOut();
}
this._router.navigate(['/login']);
}
...
The method clearUser() in auth.service.ts is well called and print cleared correctly.
But when I login, after I logout the console.log(this.user.value); in retrieveMyUser still output the previous value. It was null when at logout though.
So, how do I clear BehaviorSubject cache or to reset BehaviorSubject from another service ?
There are many things in your code which sound weird at reading:
You shouldn't access immediately to the value of a BehaviorSubject without using the asObservable() as recommended by ESLint here.
Instead, you could use another variable which will keep the latest value for the user.
You should use the power of TypeScript in order to help you with types definition and quality code (in my opinion).
The use of a BehaviorSubject with a startWith operator can be simplified using a ReplaySubject with a bufferSize of 1 (replay the latest change)
Your subject acting like a source storage should be private in order to limit the accessibility from outside.
I took your code and make some updates from what I said above:
export class UserServiceFacade extends UserService {
private _user: V2UserOutput;
private readonly _userSource = new ReplaySubject<V2UserOutput>(1);
public get user(): V2UserOutput { // Use for accessing to the user data without the use of an observable.
return this._user;
}
constructor() {
super();
this.clearUser(); // It will make your ReplaySubject as "alive".
}
public retrieveMyUser$(): Observable<V2UserOutput> {
return this._userSource.asObservable()
.pipe(
switchMap(user => (user ? of(user) : this.getUserFromServer())),
take(1)
);
}
private getUserFromServer(): Observable<V2UserOutput> {
return super.retrieveMyUser(null, 'environment.liveMode')
.pipe(
tap(user => this.storeUser(user))
);
}
public clearUser() {
console.log('cleared');
this.storeUser(null);
}
private storeUser(user: V2UserOutput) {
this._user = user;
this._userSource.next(user);
}
}
Cheers!
New to Angular/Apollo/TS and this is driving me nuts, so any help is appreciated.
I am working on setting up a small app with Angular 10, Apollo, and a GraphQL API. I recently built the same thing in Vue and thought recreating the project would be a good way to pick up some Angular.
My connection to the API is working, as is my query, but I can't figure out how to map the results to an array so I can access them in my component. Using console.log inside the subscription shows the correct data is returned. console.log outside of the query on 'this' shows the query results, however they are never saved/mapped to the variable they should be set to.
Here's the code for my service:
import { Injectable } from '#angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import gql from 'graphql-tag';
const USER_SEARCH = gql`
query getUsers {
search(query: "moose", type: USER, first: 10) {
nodes {
... on User {
login
email
location
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
userCount
}
}`;
export class UserService {
loading: boolean = true;
users: [];
constructor(private apollo: Apollo) { }
getUsers(): any {
this.apollo.watchQuery<any>({
query: USER_SEARCH
})
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.users = data.search;
});
console.log(this);
return this.users;
}
}
I can call the getUsers() function from my component, and 'this' has the service listed, and inside of it 'users' has my query results listed in it. However, console.log for this.users in the service or the component returns undefined.
I've tried about every type of example I could find, including the query examples from the apollo docs, and the example of using apollo with angular from hasura.io. Tried using a pipe and map, pluck, just valueChanges, a few different subscribes, setting a variable inside the function to assign the data value to, setting the query to variable, setting the query in ngOnInit in the component, and a few other things I'm sure I'm forgetting. Nothing seems to work. I looked into using a callback to wait for the query to return before setting the value, but my understanding is that I shouldn't have to do anything like that. I'm sure it's something dumb I'm missing or don't know about with Apollo or Angular, but I'm just not positive what it is I'm missing.
Any ideas?
this.getUsers = this.getUsers.bind(this);
within a constructor?
using setTimeout is not an ideal solution, you can directly update your component variable in subscribe callback function and do whatever you want to do with it in your template. Look at my example
getItems() {
this.apollo
.watchQuery({
query: this.getItemsQuery,
})
.valueChanges.subscribe((result: any) => {
this.items = result?.data?.items;
});
}
and in template
<mat-option *ngFor="let item of items" [value]="item.price">
{{ item.name }}
</mat-option>
Maybe not the ideal solution, so I'm still open to trying other things, but I was able to get the value set in my component by using a promise with a timer in the service, then an async await in the component.
Service
getUsers(): any {
return new Promise((resolve, reject) => {
let me = this;
this.apollo.watchQuery<any>({
query: USER_SEARCH
})
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.users = data.search;
});
setTimeout( function() {
if(me.users !== 'undefined'){
resolve(me.users)
}
}, 1000)
})
}
Component
async getUsers(): Promise<any> {
this.users = await this.userService.getUsers();
console.log(this.users);
}
This allows this.users to be set from the service. As far as I can tell, Apollo is still running the query when Angular starts setting values, resulting in the value originally being shown as undefined, but my service having values from the query in the console. Not sure if there's a better way with Apollo or Angular to resolve this issue, but if so I'd love to hear about it.
Thanks!
I am trying to return the document id when I create it. Since Firebase functions are async the return value is not completed until the query is done. How can I prevent this so I can wait to get the value when the query is done?
This function create the document is located in a service:
public makeDoc(title: string, score: number): any{
const fields = {
title: title,
score: number
}
this.db.collection('saved').add(fields)
.then(function(ref) {
console.log(ref.id);
return ref.id;
})
}
I call this from a function which is located in a component:
onCreate() {
const str = this.createService.makeDoc(this.title, this.score);
console.log(str);
}
Try following:
const fields = {
title: title,
score: number
}
var newFieldsRef = this.db.collection('saved').push();
this.db.collection('saved').child(newFieldsRef).set(fields);
var id = newFieldsRef.toString();
You don't want to prevent waiting until the query is done, you should embrace the use of promises here.
First, if you haven't, make sure you import the firestore namespace in the service:
import { firestore } from 'firebase';
Now, for your service:
I had to slightly change your makeDoc method as the fields object wasn't being created in a valid way (e.g. reusing the number type):
public makeDoc(titleParam: string, scoreParam: number): Promise<firestore.DocumentReference> {
const fields = {
title: titleParam,
score: scoreParam
};
return this.db.collection('saved').add(fields);
}
This now returns a Promise<DocumentReference> which, when resolved, the reference will point to the created document.
Now, the call to it in onCreate looks like:
onCreate() {
this.createService.makeDoc('myTitle', 123)
.then((ref) => { console.log(ref.id); })
.catch((err) => { console.log(err); });
}
And this will log the id as soon as it is available.
in my Angular App i make a simple call to a node.js server. the HttpClient "get"
function returns the right answer. This answer I want to store in a variable of my component "interfaces". But in the "subscribe" function of the get request my "this" pointer doesn't point to my component. Instead it tells me that it is of type "SafeSubscriber". Any call to my member "interfaces" lead to the following error:
TypeError: Cannot read property 'interfaces' of undefined
export class SettingsComponent implements OnInit {
public interfaces : string[];
constructor(private http: HttpClient) {
this.interfaces = [];
this.interfaces.push("huhu");
}
ngOnInit() : void {
this.http.get('http://localhost:3000/settings/interfaces').subscribe((data) => {
// Read the result field from the JSON response.
console.log(data);
this.interfaces.push("xxx");
Object.keys(data).forEach(function(k) {
console.log(k);
this.interfaces.push("xxx");
});
}),
err => {
console.log("error " + err);
};
}
}
As you can see I also tried to enter some values manually into the array just to make sure, that not the server response is causing the problem.
Any help is appreciated.
I used this code as a blueprint which is from:
https://angular.io/guide/http
#Component(...)
export class MyComponent implements OnInit {
results: string[];
// Inject HttpClient into your component or service.
constructor(private http: HttpClient) {}
ngOnInit(): void {
// Make the HTTP request:
this.http.get('/api/items').subscribe(data => {
// Read the result field from the JSON response.
this.results = data['results'];
});
}
}
You're losing reference to the correct this in this statement:
Object.keys(data).forEach(function(k) {..})
Inside the function block code this refers to the calling context , which is the subscribe method itself, that's why interfaces is undefined, since it's not a property of the subscribe method.
You can change the function for a lambda en it should be fine:
Object.keys(data).forEach((k) => {..})