I'm using NestJS and Angular 2, both have similar (close) approach to work with Interceptors. I would like to find best practice to identify some specific request to do some additional work.
To declare Interceptor who will listen some Controller (in NestJS) I should use this logic:
#UseInterceptors(ObjectsInterceptor)
#Controller('objects')
export class ObjectsController {
#Get()
async findAll(): Promise<ObjectDto[]> {
// Request which should be intercepted
...
}
#Get(':slug')
async findOne(#Params('slug') slug: string): Promise<ObjectDto> {
// Request which shouldn't be intercepted
...
}
}
In Interceptor:
#Injectable()
export class ObjectsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
// Some logic to detect specific request
return next.handle();
}
}
Probably I use wrong way to solve my problem
just put decorator above method instead of class
#Get()
#UseInterceptors(ObjectsInterceptor)
async findAll(): Promise<ObjectDto[]> {
// Request which should be intercepted
...
}
Related
I have a timer in my main component of 3 seconds. inside the timer I perform http call-
constructor(){
this.timer = timer(3000, 3000);
this.timerObservable = this.timer.subscribe(x => {
this.http.get(URL).subscribe(()=>{
//DO SOMETHING
});
});
}
In another component I have a button that suppose to perform a different http call, pressing on the button invoke the sumbit function-
submit(){
this.http.get("/sumbitForm").subscribe(()=> {
//DO SOMETHING
})
}
When a user clicks on the button, if the timer is in process (the http inside of it was called and not resolved yet) I want to wait before I perform the http call on the button until it resolved, but if the timer is not in process (the time from the previous call did not passed yet) I want to execute it immediately.
I think that forkJoin and concat is not relevant here (this is a timer and not a 'regular' subscription that I want to wait to its execution either way) and I could not find a pretty way to do it, any idea?
You need to share some information between your two components, i.e. when the polling request is in progress and when it isn't. You should use a Service for this. It's also always good practice to move your http request logic to a Service instead of using the HttpClient directly in the component. This allows you to do your general error handling in one place.
Let's call this Service ApiService.
ApiService
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable, BehaviorSubject, interval } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
#Injectable({ providedIn: 'root' })
export class ApiService {
// Use a BehaviorSubject to notify listeners about ongoing polling requests.
// A BahaviorSubject always has a current value that late subscribers will
// receive immediately upon subscribing
private pollRequestInProgress = new BehaviorSubject<boolean>(false);
// Share this BehaviorSubject as an Observable
public pollRequestInProgress$ = pollRequestInProgress.asObservable();
constructor(private http: HttpClient)
doPoll(url: string): Observable<any> {
return interval(3000).pipe( // interval(3000) is equivalent to timer(3000, 3000)
tap(_ => pollRequestInProgress.next(true)), // notify that a poll request is about to happen
switchMap(_ => this.http.get(url)), // do your poll request
tap(_ => pollRequestInProgress.next(false)) // notify that your poll request ended
);
}
}
MainComponent
This is the Component where you want to start your polling from.
private destroy$: Subject<void> = new Subject<void>();
constructor(private apiService: ApiService) {}
// move logic to ngOnInit instead of constructor
ngOnInit() {
// subscribe and thereby start the polling
this.apiService.doPoll(URL).pipe(takeUntil(this.destroy$))
.subscribe(pollResponse => {
//DO SOMETHING
});
}
ngOnDestroy() {
// unsubscribe when the Component gets destroyed.
this.destroy$.next();
this.destroy$.complete();
}
FeatureComponent
This is the Component where you want to perform a http request when you click a button.
constructor(private http: HttpClient, private apiService: apiService) {}
submit() {
// Listen to whether a polling request is currently in progress.
// We will immediately receive a value upon subscribing here, because we used
// a BehaviorSubject as the underlying source.
this.apiService.pollRequestInProgress$.pipe(
// Emit the first item that tells you that no polling request is in progress.
// i.e. let the first 'requestInProgress === false' through but not any
// other items before or after.
first(requestInProgress => !requestInProgress),
// If no polling request is in progress, switch to the http request you want
// to perform
switchMap(this.http.get("/sumbitForm")) // <-- consider moving this http.get to your ApiService aswell
).subscribe(httpResponse => {
// you've got your http response here
});
// you don't have to unsubscribe here as first and http.get both complete
// and thus unsubscribe automatically
}
Check out a simple example of the code logic above here: https://stackblitz.com/edit/rxjs-t4hjcr
You can use Angular Subject
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
#Injectable({ providedIn: 'root' })
export class CallService {
private subject = new Subject<any>();
timerCompleted() {
this.subject.next();
}
checkTimer(): Observable<any> {
return this.subject.asObservable();
}
}
The app component uses the call service to subscribe to timer complete and make them available to the app component template.
import { Component, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs';
import { CallService } from './_services/index';
#Component({
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnDestroy {
subscription: Subscription;
constructor(private callService: CallService) {
this.subscription = this.callService.checkTimer().subscribe(() => {
// call your api after timer complete
});
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
add below code in your timer
this.timer = timer(3000, 3000);
this.timerObservable = this.timer.subscribe(x => {
this.http.get(URL).subscribe(()=>{
this.callService.timerCompleted();
});
});
For more reference you can check http://jasonwatmore.com/post/2018/06/25/angular-6-communicating-between-components-with-observable-subject
I want to have a bunch of data ready the very first thing after a user logs in my app (keeping it cached in a Service).
I thought implementing this data-loading logic before resolving the parent route the user will be redirected to if login is successful, as a Resolver.
To be more specific: before showing the user's home page I would like to have a list of data already loaded and, if the list is not empty, have the first element of that list set as the selected element by default. So, this means, two Services:
ItemsService: this Service will know how to request the list of items and keep it cached after the first request is done
ItemSelectedService: this Service will know which item has been set as selected at anytime
And the following Resolver:
#Injectable()
export class ItemsResolver implements Resolve<any> {
constructor(
private itemSelectedService: ItemSelectedService,
private itemsService: ItemsService
) { }
resolve() {
this.itemsService.getAll()
.subscribe((items) => {
if (items.length > 0) {
this.itemSelectedService.setAsSelected(items[0]);
}
});
}
}
As #resolve() needs to return an Observable (return this.itemsService.getAll() would have just been enough...) but I'm not returning any because I need to subscribe and call itemSelectedService#setAsSelected() once the item list has been fetched asynchronously... what would be the right way to achieve the desired behavior?
Try giving it a tap
resolve() {
return this.itemsService.getAll()
.pipe(
tap(
filter(items=>items.length > 0)
do(items=>this.itemSelectedService.setAsSelected(items[0]))
)
);
}
do / tap
Transparently perform actions or side-effects, such as logging.
https://www.learnrxjs.io/operators/utility/do.html
You can use flatmap to resolve the observable in chain
#Injectable()
export class ItemsResolver implements Resolve<any> {
constructor(
private itemSelectedService: ItemSelectedService,
private itemsService: ItemsService
) { }
resolve() {
return this.getAll()
.pipe(
flatmap(items => this.setSelectService(item[0]))
)
}
getAll() {
return this.itemsService.getAll();
}
setSelectService(item) {
return this.itemSelectedService.setAsSelected(item);
}
}
I'm using NestJS as the framework for a client API. Within the framework we are using a pretty standard Passport/JWT auth infrastructure that is working fine. Our AuthGuard is firing when the bearer token is found and, in secure API endpoints, I can inject the HTTP context via '#Res() request' and get access to the 'request.user' property which contains the payload of my Jwt token.
On top of this we are attempting to implement a 'RolesGuard' in a very similar fashion to the sample code provided in the documentation and some of the sample projects on GitHub (none of which actually use this guard but they include it as a sample guard).
Our issue is that our AuthGuard fires and validates the Jwt token and THEN our RolesGuard fires but the request object it is passed does not have the user meta-data attached to the request.
The key code in our RolesGuard is:
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
In the above snipped the user is always false. Has anyone written a role/permission based guard in Nest that successfully gets access to the scope of the current user? All the code is firing and everything appears registered correctly.
-Kevin
Ultimately this appears to be an ordering issue with the guards and it doesn't look like it can be easily resolved (without the framework allowing some control over the ordering).
My hope was to register the RolesGuard globally but that causes it to be registered first and fire first.
#UseGuards(AuthGuard('jwt'), RolesGuard)
#Roles('admin')
If I register it at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself. It isn't perfect but it works.
-Kevin
register RoleGuard at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself.
don't register RoleGuard at module causes it'll be registered first and fire first.
*.module.ts
imports: [],
providers: [{provide: APP_GUARD, useClass: RolesGuard} ,], // remove guard
controllers: [],
exports: [],
Make your RolesGuard extend AuthGuard('StrategyName') and then call super.canActivate for example:
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// call AuthGuard in order to ensure user is injected in request
const baseGuardResult = await super.canActivate(context);
if(!baseGuardResult){
// unsuccessful authentication return false
return false;
}
// successfull authentication, user is injected
const {user} = context.switchToHttp().getRequest();
}
}
In other words you have to Authenticate first then Authorize
If anyone else stumbles across this question: putting multiple guards into one #UseGuards decorator works, but if you want to keep them separated (say, if you use a custom decorator), you can give the 2nd guard access to req.user by placing it before the #UseGuards call that puts the user on the request object, as in this example:
#RestrictTo(UserAuthorities.admin)
#UseGuards(JwtAuthGuard)
#Get("/your-route")
It seems that this is a consequence of how decorators work in TypeScript.
You can also use multiple roles for role-based Authentication.
In UserResolver
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UseGuards } from '#nestjs/common';
import { RolesGuard } from 'src/guards/auth.guard';
#UseGuards(new RolesGuard(['admin']))
#Resolver()
export class UserResolver { ... }
In RolesGuard
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { ExecutionContextHost } from '#nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
constructor(private roles: string[] | null) {
super();
}
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any, info: string) {
if (!this.roles) {
return user || null;
}
if (!user) {
throw new UnauthorizedException('Not Valid User.');
}
const role = user.role;
const doesRoleMatch = this.roles.some(r => r === role);
if (!doesRoleMatch) {
throw new UnauthorizedException('Not Valid User.');
}
return user;
}
}
Current routing configuration:
//...
{
path: 'foo/:id',
component: SomeComponent,
canActivate: [SomeGuard]
},
//...
Then in guard I call permission service to get access for component:
#Injectable()
export class SomeGuard implements CanActivate {
constructor(private service: Service) {
}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
const id = parseInt(next.params['id']);
return this.service.getPermissions(id).then(permissions => {
if (permissions.canView) {
return true;
}
return false;
});
}
}
But in the component I utilize the same permissions endpoint, which means I call it twice in a row to get to one page:
//...
constructor(private route: ActivatedRoute,
private service: Service) {
}
ngOnInit() {
this.id = parseInt(this.route.snapshot.params['id']);
this.service.getPermissions(this.id).then(permissions => {
// ...
});
}
//...
So it would be great to just save the permissions data in the route and utilize it by both guard and the component. I tried using resolve, but it turns out resolve only activates after the guards, which is no good. So how can i save permissions data?
This looks like the kind of task for a caching service. Permissions do not change often so they are the perfect candidate for caching. That way even multiple visits to the same resource would not trigger multiple HTTP requests for permission checks.
Edit: Since you need permissions to be loaded each time, you could listen for RouteNavigationStart and clear the cache. If this becomes too cumbersome to maintain in the PermissionsService you could extract the logic into a separate service.
You could something like this in the service you use to get your permissions:
// Permissions service
private permissionCache;
constructor(
router: Router,
) {
// clear cache when a route navigation starts
router.events
.filter(event => event instanceof NavigationStart)
.subscribe(event => this.permissionCache = {})
}
getPermissions(id) {
if (permissionCache[id]) {
return Observable.of(permissionCache[id]);
} else {
// get the permissions
permissionCache[id] = response;
return Observable.of(response);
}
});
I am working on an Angular4 app.
Here is a service I am using to get the data-
export class BookingService {
constructor(private http: HttpClient) {}
getMemberBookings(memberID: number): Observable<any> {
return this.http.get('http://myapi.com/bookings/member/'+memberID).map(response => response['bookings']);
}
}
And then in my component-
export class PrintButtonComponent implements OnInit {
bookings: any;
constructor(private service: BookingService) {}
ngOnInit() {}
downloadPDF() {
this.getBookings(memberID);
//pdf creation logic
}
getBookings(memberID: number) {
this.service.getMemberBookings(memberID).subscribe(data => this.bookings = data);
}
}
The problem is I want to use the data from the service in the downloadPDF method as there is other data in it that will be needed to create the PDF.
But when I return the data from the subscribe or set it to a property, it is giving undefined. I understand that this is due to asynchronous nature, but I dont want to put my pdf creation logic inside the subscribe method.
So how do I solve this problem? I am quite new to Angular and observables.
Thank you.
Since the code above doesn't involve multiple values per observable and doesn't require to stick to them, this can be done with async..await with no extra nesting:
async downloadPDF() {
await this.getBookings(memberID);
//pdf creation logic
}
async getBookings(memberID: number) {
this.bookings = await this.service.getMemberBookings(memberID).toPromise();
}
As any other RxJS operator, toPromise has to be imported.