Angular2: getting service's property value before call a method - rxjs

I have implemented FireDatabaseService as #Injectable({ providedIn: 'root' }) in my app. It of course works with DI. In this service I need Firebase UID from another service, but this UID is being fetched with subscription. Because of this methods that need UID are called before this UID is available. I have no idea how to get UID before getUserGroup() is called. Here is my service:
...
#Injectable({ providedIn: 'root' })
export class FireDatabaseService {
uid: string;
constructor(
private readonly db: AngularFireDatabase,
private readonly authService: AuthService
) {
this.authService.authState.subscribe(
(state: firebase.User) => this.uid = state.uid
);
}
getUserGroup(): Observable<string> {
return this.db
.object(`users/${this.uid}`)
.valueChanges()
.pipe(map((userData: any) => userData?.group as string));
...
}
Calling fireDatabaseService.getUserGroup() goes with uid = ''. Is there any possibility to fix it? Thanks for help.

It is a little unclear to me what your requirements are. The easiest way to accomplish what I think you are asking would be the following:
...
#Injectable({ providedIn: 'root' })
export class FireDatabaseService {
constructor(
private readonly db: AngularFireDatabase,
private readonly authService: AuthService
) {
}
getUserGroup(): Observable<string> {
return this.authService
.authState
.pipe(
switchMap((state: firebase.User) =>
this.db
.object(`users/${state.uid}`)
.valueChanges()
),
map((userData: any) => userData?.group as string)
);
...
}
Everytime getUserGroup is called, there would be another call to the authService. That might be undesireable, but that could be mitigated through the use of shareReplay(1) or through another strategy depending on the functionality you are expecting.

I suggest that you do not subscribe at all in your service, but rather define your uid and userGroup as observables. The userGroup$ can be defined from the uid$ observable:
#Injectable({ providedIn: 'root' })
export class FireDatabaseService {
uid$ = this.authService.authState.pipe(
map(state => state.uid)
);
userGroup$ = this.uid$.pipe(
switchMap(uid => this.db.object(`users/${uid}`).valueChanges())
map(userData => userData?.group as string)
);
constructor(
private readonly db: AngularFireDatabase,
private readonly authService: AuthService
) { }
}
Observables are lazy, so it is not necessary to wrap them as functions. They don't emit a value until .subscribe() is called on them.

Related

Angular rxjs replaysubject not subscribed to observable

I have a service where i emit a value to a replaysubject observable as below. MyService.update() is called from app initializer.
Service:
#Injectable({ providedIn: 'root' })
export class MyService {
private _userSubject$ = new ReplaySubject<UserDetails>(1);
user$: Observable<UserDetails> = this._userSubject$;
test$ = new BehaviorSubject<string>('test');
constructor() {
this.user$.subscribe((x) => console.log('it subscribes here'))
}
update(details: SessionDetailsResponse): void {
this._userSubject$.next(details.userDetails);
this.test$.next('updated');
}
}
Component:
ngOnInit() {
this.myService.user$
.pipe(takeUntil(this.unSubscribe$))
.subscribe((u) => {
console.log('u', u); //not coming here
this.currentUserId = u.id; //this is not subscribing here.
});
this.sessionDetails.test$
.subscribe(x => {
console.log('test', x); //this one subsribig with only default value and not the updated value.
})
}
Not sure why it is not subscribing.Can anyone tell me what am i doing wrong here?
Thanks
It was my mistake. The service has already been decorated as ProvidedIn: Root as singleton service. In addition to this i have added to providers array of the module. Due to this the constructor is called twice and the values are lost.

NestJS/RxJS - Is there an easier way to "observe" one time?

I am new to both NestJS & RxJS. I'm working very hard to write things in an idiomatic RxJS, since one of the purposes of this project is to learn both of them much better, rather than trying to bypass or hack around their APIs.
Anyway, so I have a piece of logic where I am looking up a JWKSet from an OAuth2 server. I use the NestJS HttpService for that, which returns an Observable. I then use that observable to set the result to a ReplaySubject. Then, in my JwtStrategy, I use the secretOrKeyProvider function to subscribe to the ReplaySubject in order to get the value each time an HTTP request comes in.
Now, I'm sure there are tons of things wrong with this approach, since I just barely understand how to work with RxJS. My biggest issue is the last part. When I subscribe to the ReplaySubject in the secretOrKeyProvider function, I immediately unsubscribe. This is because I want to cleanup leftover subscriptions.
Overall, this feels very wrong to me, to subscribe and immediately unsubscribe. I feel like I am doing something wrong. I am seeking a review of this code to learn what improvements I can make.
While all the code below works, my goal is to be guided into making better, more proper use of RxJS.
#Injectable()
export class JwkService implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(JwkService.name);
readonly key = new ReplaySubject<string>();
private subscription: Subscription;
constructor(
private httpService: HttpService,
private configService: ConfigService
) {}
onModuleInit(): void {
this.subscription = this.httpService
.get(`${this.configService.get<string>(AUTH_SERVER_HOST)}${jwkUri}`)
.pipe(
map((res: AxiosResponse<JwkSet>) => jwkToPem(res.data.keys[0]))
)
.subscribe({
next: (key) => this.key.next(key),
error: (error: Error) => {
this.logger.error(
'CRITICAL ERROR: Unable to load JWKSet',
ajaxErrorHandler(error)
);
}
});
}
onModuleDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
private readonly logger = new Logger(JwtStrategy.name);
constructor(
private readonly jwkService: JwkService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKeyProvider: (
req: Request,
rawJwt: string,
done: doneFn
) => {
jwkService.key
.subscribe({
next: (value: string) => done(null, value),
error: (error: Error) => {
this.logger.error(
'Error getting JWK key',
ajaxErrorHandler(error)
);
done(error);
}
})
.unsubscribe();
}
});
}
}
If you don't need unsubscribe, RxJS gives you a few pipes for that:
.get(`${this.configService.get<string>(AUTH_SERVER_HOST)}${jwkUri}`)
.pipe(
take(1),
map((res: AxiosResponse<JwkSet>) => jwkToPem(res.data.keys[0]))
)
...
or
.get(`${this.configService.get<string>(AUTH_SERVER_HOST)}${jwkUri}`)
.pipe(
first(),
map((res: AxiosResponse<JwkSet>) => jwkToPem(res.data.keys[0]))
)
...
first(), take(1) pipes do unsubscribe for you if takes first value. If value in any cases don't come, then you can unsubscribe your subscription manually, or just complete the subject.

Possible memory leak in NativeScript app if user reopens his app multiple times

I'm not sure where is the bug, maybe I'm using rxjs in a wrong way. ngDestroy is not working to unsubscribe observables in NativeScript if you want to close and back to your app. I tried to work with takeUntil, but with the same results. If the user close/open the app many times, it can cause a memory leak (if I understand the mobile environment correctly). Any ideas? This code below it's only a demo. I need to use users$ in many places in my app.
Tested with Android sdk emulator and on real device.
AppComponent
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Subscription, Observable } from 'rxjs';
import { AppService } from './app.service';
import { AuthenticationService } from './authentication.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnDestroy, OnInit {
public user$: Observable<any>;
private subscriptions: Subscription[] = [];
constructor(private appService: AppService, private authenticationService: AuthenticationService) {}
public ngOnInit(): void {
this.user$ = this.authenticationService.user$;
this.subscriptions.push(
this.authenticationService.user$.subscribe((user: any) => {
console.log('user', !!user);
})
);
}
public ngOnDestroy(): void {
if (this.subscriptions) {
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}
async signIn() {
await this.appService.signIn();
}
async signOut() {
await this.appService.signOut();
}
}
AuthenticationService
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { AppService } from './app.service';
#Injectable({
providedIn: 'root',
})
export class AuthenticationService {
public user$: Observable<any>;
constructor(private appService: AppService) {
this.user$ = this.appService.authState().pipe(shareReplay(1)); // I'm using this.users$ in many places in my app, so I need to use sharereplay
}
}
AppService
import { Injectable, NgZone } from '#angular/core';
import { addAuthStateListener, login, LoginType, logout, User } from 'nativescript-plugin-firebase';
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
const user$ = new BehaviorSubject<User>(null);
#Injectable({
providedIn: 'root',
})
export class AppService {
constructor(private ngZone: NgZone) {
addAuthStateListener({
onAuthStateChanged: ({ user }) => {
this.ngZone.run(() => {
user$.next(user);
});
},
});
}
public authState(): Observable<User> {
return user$.asObservable().pipe(distinctUntilChanged());
}
async signIn() {
return await login({ type: LoginType.PASSWORD, passwordOptions: { email: 'xxx', password: 'xxx' } }).catch(
(error: string) => {
throw {
message: error,
};
}
);
}
signOut() {
logout();
}
}
ngOnDestroy is called whenever a component is destroyed (following regular Angular workflow). If you have navigated forward in your app, previous views would still exist and would be unlikely to be destroyed.
If you are seeing multiple ngOnInit without any ngOnDestroy, then you have instantiated multiple components through some navigation, unrelated to your subscriptions. You should not expect the same instance of your component to be reused once ngOnDestroy has been called, so having a push to a Subscription[] array will only ever have one object.
If you are terminating the app (i.e. force quit swipe away), the whole JavaScript context is thrown out and memory is cleaned up. You won't run the risk of leaking outside of your app's context.
Incidentally, you're complicating your subscription tracking (and not just in the way that I described above about only ever having one pushed). A Subscription is an object that can have other Subscription objects attached for termination at the same time.
const subscription: Subscription = new Subscription();
subscription.add(interval(100).subscribe((n: number) => console.log(`first sub`));
subscription.add(interval(200).subscribe((n: number) => console.log(`second sub`));
subscription.add(interval(300).subscribe((n: number) => console.log(`third sub`));
timer(5000).subscribe(() => subscription.unsubscribe()); // terminates all added subscriptions
Be careful to add the subscribe call directly in .add and not with a closure. Annoyingly, this is exactly the same function call to make when you want to add a completion block to your subscription, passing a block instead:
subscription.add(() => console.log(`everybody's done.`));
One way to detect when the view comes from the background is to set callbacks on the router outlet (in angular will be)
<page-router-outlet
(loaded)="outletLoaded($event)"
(unloaded)="outletUnLoaded($event)"></page-router-outlet>
Then you cn use outletLoaded(args: EventData) {} to initialise your code
respectively outletUnLoaded to destroy your subscriptions.
This is helpful in cases where you have access to the router outlet (in App Component for instance)
In case when you are somewhere inside the navigation tree you can listen for suspend event
Application.on(Application.suspendEvent, (data: EventData) => {
this.backFromBackground = true;
});
Then when opening the app if the flag is true it will give you a hint that you are coming from the background rather than opening for the first time.
It works pretty well for me.
Hope that help you as well.

NestJS - Pass user data from custom auth guard to resolvers

I know this question gets asked frequently for the default passport AuthGuard('yourStrategy'),
but haven't found the answer for custom auth guards yet.
Why I use a custom auth guard? Because the default one and GraphQL seems to be unable to work together.
Since some update on GraphQL's side, the default AuthGuard cannot read the header any more.
I need to pass the user data, which I got from the bearer token, somehow to the resolvers.
How is passport doing this? How would you do this? I'm pretty new to nestJS and the lack of dockumentation and / or propper tutorials drives me crazy.
Relevant code:
auth.guard.ts
#Injectable()
export class AuthGuard implements CanActivate {
constructor(readonly jwtService: JwtService/*, readonly userService: UsersService*/) { }
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().request;
const Authorization = request.get('Authorization');
if (Authorization) {
const token = Authorization.replace('Bearer ', '');
const { userId, firstName } = this.jwtService.verify(token) as { userId: string; firstName: string } ;
return !!userId;
}
}
}
jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload) {
return {userId: payload.userId, firstName: payload.firstName};
}
}
auth.module.ts
#Module({
imports: [
forwardRef(() => UserModule) ,
PassportModule.register({
defaultStrategy: 'jwt'
}),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: {expiresIn: 3600}
})],
providers: [AuthService, JwtStrategy, AuthResolver, AuthGuard],
exports: [AuthService, JwtModule, AuthGuard]
})
export class AuthModule {
}
example resolver
#UseGuards(AuthGuard)
#Resolver((of) => UserSchema)
export class UserResolver {
constructor(private readonly userService: UserService) {}
// ===========================================================================
// Queries
// ===========================================================================
#Query(() => UserDto, {description: 'Searchs for a user by a given id'})
async getUserById(#Args('id') id: string) {
/*
* Instead of passing the userID as an query argument, get it from the bearer token / auth guard!
*/
const result = await this.userService.findById(id);
if(result) return result;
return new NotFoundException('User not found!');
}
}
Thanks for help in advance! ;-)
Edit: In case you need to see more code, you could use my github repo: https://github.com/JensUweB/ExamAdmin-Backend
Never mind. I have found a solution to this myself. I found a workaround to get the passport AuthGuard back to work with GraphQL. And for the userId: use a custom User Decorator: github.com/nestjs/graphql/issues/48#issuecomment-420693225

Dispatch Action with Observable Value

I often find myself using the following code:
export class Component implements OnDestroy {
private subscription: Subscription;
user: string;
constructor(private store: UserStore) {
this.subscription = store.select(fromUsers.getUser)
.subscribe(user => this.user = user);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
logout(): void {
this.store.dispatch({
type: LOGOUT,
payload: {
user: this.user
}
})
}
}
As you can see I need to store the user string as a member within the component to send it with my payload.
I would rather use the user string as an observable and make use of the async pipe.
How do I need to change my code to leverage the observable of the user when dispatching the action without storing it in a member variable?
You can use ngrx effects and enhance the LOGOUT command with current user.
#Effect() logoutEffect$ = this.actions$
.ofType(LOGOUT)
.withLatestFrom(this.store$)
.map(([action: Action, storeState: AppState]) => {
return storeState.getUser;
})
.map(payload => ({type: 'LOGOUT_USER', payload}))

Resources