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}))
Related
I have orders resolver, like this one(thats an example, not actually problem):
#Resolver(() => OrderEntity)
export class OrderResolver {
constructor(
private readonly orderService: OrderService,
private readonly usersService: UsersService,
) {}
.................
#ResolveField('users', () => [UsersEntity])
async users(#Parent() order: OrderEntity): Promise<UserEntity[]> {
return await this.usersService.findAllByOrderId(order.id);
}
#ResolveField('usersCount', () => Int)
async usersCount(#Parent() order: OrderEntity): Promise<number> {
const users = await this.usersService.findAllByOrderId(order.id);
return users.lenght; // i can't use like this: order.users.lenght, couse it's still undefined
}
}
And there are i call userService.findAllByOrderId method two times, because i can't use order.users from context in this method, as its still undefined
So how can i write one #ResolveField method for both fields: order.users and order.usersCount, or how to call usersCount method when order.users are already existing?
Thank's a lot for any answers!
I call one method 2 times, instead 1, how can i optimize it?
Do you really need to do a query to get the number of the noOfUsers ?. Cant you just call the async users function that returns UserEntity[] and then get the length of the array that is returned .length
or
If you really need to return the noOfUsers as an int then you can create a wrapper object and put the 2 fields in that e.g.
export class UserResponse {
users: Array<UserEntity>;
noOfUsers: int
}
Then you can only have 1 method that returns both fields:
#ResolveField('users', () => [UserResponse])
async users(#Parent() order: OrderEntity): Promise<UserResponse> {
const userEntities: Array<UserEntity> = await this.usersService.findAllByOrderId(order.id);
const response: UserResponse = {
users: userEntities,
noOfUsers: userEntities.length
}
return response;
}
I have a #Get() method in a Controller. I get the response, but after the response is successfully I would like to call an async method after a delay of some milliseconds.
I'm using a Middleware, but seems like the response is pending for the time specified in the async method.
How can I do to solve the response in time, and after the delay call the custom method from the Custom Service?
Here is the used sample code:
#Controller()
export class CustomController {
#Get("custom-controller-route")
getCustomValue(#Res() response: Response) {
return response.status(200).send({
value: 1
})
}
}
The middleware code is the following:
#Injectable()
export class CustomMiddleware implements NestMiddleware {
constructor(private readonly customService: CustomService) {}
use(req: any, res: any, next: () => void) {
let send = res.send
res.send = async (customResponse: CustomResponse) => {
const { value } = customResponse
await customService.customMethod(value, 5000) // Delay of 5 seconds
res.send = send
return res.send(exchangeRateResponse)
}
next()
}
}
And the CustomService has the next code:
#Injectable()
export class CustomService {
async customMethod(value: any, delay) {
await firstValueFrom(
timer(delay).pipe(
tap(() => {
// Here is the logic that needs to be run in the delay time after the response is finished
console.log(`Custom Service - custom method called with: ${value} after: ${delay} milliseconds.`)
})
)
)
}
}
I could do it instead of returning the response in the Controller method, I only call the res.status(200).send({}) with the specific response and after that I call the specific method call with the delay.
#Controller()
export class CustomController {
#Get("custom-controller-route")
getCustomValue(#Res() response: Response) {
response.status(200).send({ value: 1 })
// Call the delayed Service method
}
}
Any other better option is welcome
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.
I want the $beforeValidate to wait for the async operation to complete as it updates the object to make it pass validation. But currently the $beforeValidate completes and rejects the record as it is not waiting for the async operation to complete.
class Label extends Model {
async $beforeValidate() {
if(this.name === undefined){
const res = await axios.get('/getSomeName')
console.log(res.body)
this.name = res.body
}
}
static get jsonSchema () {
return {
type: 'object',
required: ['name'],
properties: {
id: { type: 'integer' },
name: { type: 'string' }
}
}
}
}
Now when i insert a label with name undefined I can see that the validation error is raised before the async API call has even finished
await Label.query().insert({name: undefined})
Unfortunately $beforeValidate is a synchronous operation.
You can check it on the Official Documentation. Even there is an issue about it.
Does anyone know the complete sequence of reflux methods? I have this reflux store and its relation shown in the flow below:
component =(listen state)=> store =(listen)=> action =(listen trigger)=> component
That is the complete cycle, and I am trying to integrate this into socket.io
Code snippet:
Store:
export default Reflux.createStore({
listenables: [action],
init() {
this.state = messages;
this.registerListener();
},
registerListener() {
msgSocket.on('message', (user, msgs) => {
this.state.msg.push(`${user} : ${msgs}`);
this.trigger(this.state);
});
},
getInitialState() {
return this.state;
},
onSendMessage(username, message) {
msgSocket.emit('message', username, message);
}
});
But I get this result:
[user: undefined,
user: myMessage]
Why does it seem its repeating? anyone knows?