I am trying to use resolve for prefetching a http request in my Angular 4 project, but its throwing a ERROR Error: Uncaught (in promise): Response with status whenever there is an error response from Node server. The code works fine when there is a success http code like 200. Here's the resolve code I am working on:
#Injectable()
export class LoginResolverService implements Resolve<Object> {
constructor(private http: Http) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Object> | Promise<Object> | Object {
const headers = new Headers({'Content-Type': 'application/json'});
return this.http.get('http://localhost:3000/login_check', {headers: headers})
.map((response: Response) => {
return response.json();
}).catch((error: Response) => Observable.throw(error.json()));
}
}
Component file code:
loginState: Observable<Object>;
constructor(private route: ActivatedRoute, private router: Router) { }
ngOnInit() {
this.route.data.subscribe((data: Data) => {
this.loginState = data['isLoggedin'];
this.loginState.subscribe((response: any) => {
console.log(response);
}, (error: any) => {
console.log(error)
})
});
}
Routes related code:
{path: 'notifications', loadChildren: './notifications/notifications.module#NotificationsModule', resolve: {isLoggedin: LoginResolverService}}
Please help me solve this issue, as I couldn't find any proper information about using resolve with http
In my resolver service, I call a separate data service to actually get the data:
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IProduct> {
let id = route.paramMap.get('id');
if (isNaN(+id)) {
console.log(`Product id was not a number: ${id}`);
this.router.navigate(['/products']);
return Observable.of(null);
}
return this.productService.getProduct(+id)
.map(product => {
if (product) {
return product;
}
console.log(`Product was not found: ${id}`);
this.router.navigate(['/products']);
return null;
})
.catch(error => {
console.log(`Retrieval error: ${error}`);
this.router.navigate(['/products']);
return Observable.of(null);
});
}
Then in the component:
ngOnInit(): void {
this.product = this.route.snapshot.data['product'];
}
That's it!
Or if you expect that the resolved data property will change without changing the route (not including user edits to that data), then you can use:
ngOnInit(): void {
this.route.data.subscribe(data => {
this.product = data['product'];
});
}
The router itself calls the resolver and handles the subscription. So you don't have to.
Related
I have this basic CRUD methods in Nestjs.
The issue I am facing is that when I am applying the getCurrentUserId() method on top on all methods it works fine but when I am applying in bottom it doesnt work and gives error.
Is there anything wrong with middleware ?
user.controller.ts
#Controller('users')
#Serialize(UserDto)
export class UsersController {
constructor(private usersService: UsersService) {}
#Post('/signup')
create(#Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.create(createUserDto);
}
#Get('/#:userName')
async getUserByUsername(#Param('userName') userName: string) {
const user = await this.usersService.findByName(userName);
console.log(userName);
if (!user) {
throw new NotFoundException('User Not Found');
}
return user;
}
//! Testing for current user
#Get('/current')
#UseGuards(JwtAuthGuard)
async getCurrentUserId(#CurrentUser() id: string) {
console.log('running endpoint');
return id;
}
}
current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(data : unknown , context : ExecutionContext) => {
const req = context.switchToHttp().getRequest();
console.log("I am running")
return req.id;
}
)
current-user.middleware.ts
#Injectable()
export class CurrentUserMiddleware implements NestMiddleware {
constructor(private usersService: UsersService) {}
async use(req: RequestId, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
console.log(token);
if (!token) {
throw new UnauthorizedException('Unauthorized');
}
try {
const { userId } =
await this.usersService.getUserByToken(token);
req.id = userId;
console.log(req.id)
next();
} catch {
throw new UnauthorizedException();
}
}
}
And I have added the middleware to user.module.ts like this
export class UsersModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(CurrentUserMiddleware).forRoutes(
'users/current'
);
}
}
The route is matching on #Get('/#:userName') before it makes it to #Get('/current') so its executing the code inside of your getUserByUsername method instead.
Just move getCurrentUserId to the top and you should be fine.
Routes are evaluated in the order they are defined and the first matching one is used to handle the request. In general you should always put the most specific routes (the ones without route params) at the top of your controller to avoid this problem.
I am trying to modify an NestJS incoming request and append some data either to header or Body. I was able to replace all the body data with my data but i would like to append and not remove the incoming body data.
Here is the code i have
export class MyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
const decoded = jwt_decode(token);
request.body['userId'] = decoded['id'];
}
return next.handle();
}
}
Thanks in advance
I have added two examples as after running testing for the interceptor, it passed without any issue. Of course, my example will be very different to your set up, however, hopefully it'll give you enough insight:
The test file:
test('should not mutate entire request body object', () => {
const dto = {
username: 'testuser',
email: 'test#domain.com',
};
const headers = {
authorization: 'Bearer sdkfjdsakfjdkjfdal',
};
return request(app.getHttpServer())
.post('/')
.send(dto)
.set(headers)
.expect(({ body }) => {
expect(body.userId).toBeDefined();
delete body.userId;
expect(body).toStrictEqual(dto);
});
});
I understand your problem as attempting to obtain information about the authenticated user, and return it/use it later on? However, your current implementation seems to completely override the request.body instead of append your property to the original object.
Interceptor:
#Injectable()
export class HttpRequestBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
// decode token
request.body['userId'] = 'user_123456789';
}
return next.handle();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post()
#UseInterceptors(HttpRequestBodyInterceptor)
getHello(#Req() req): string {
return req.body;
}
}
This returns the correct response and the test will pass. However, you may find a more robust solution would be:
#Injectable()
export class HttpRequestBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
// decode token
request.userId = 'user_123456789';
}
return next.handle();
}
}
And then access this in your controller by:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post()
#UseInterceptors(HttpRequestBodyInterceptor)
getHello(#Req() req) {
return {
userId: req.userId,
...req.body,
};
}
}
Finally, if your only need for an interceptor is to obtain that userId property, you may find that https://docs.nestjs.com/security/authentication#jwt-functionality is useful.
#Injectable()
export class JwtInterceptor implements NestInterceptor {
constructor(private readonly jwtService: JwtService, private readonly
userService: UserService) { }
async intercept(context: ExecutionContext, next: CallHandler):
Promise<Observable<any>> {
var request: WsArgumentsHost = context.switchToWs();
var { handshake: { headers: { authorization } } } =
request.getClient();
try {
var jwt = authorization.split(" ")[1];
var { phone } = await this.jwtService.verify(jwt, jwtConstraints)
var user: User = await this.userService.findUserByPhoneNumber(phone);
request.getData()["user"]=user;
return next.handle().pipe(map((data) => { return { ...data, 'user': "david" }; }));
i hope this will help someone in future while working with socket.i wanted the user object in the body after they pass authentication .the above trick worked out for me
In my Angular application, there is a global error handler and a service responsible for making http calls, having return type as Observable<any>. Some services have handled the errors explicitly and some not.
For those, which has not been catched, the global error (having class 'CustomErrorHandler') runs fine. For the service calls, which has been handled and catched gracefully, the global error doesn't seem to fire.
My question: Is there a way to execute the global error handling irrespective of whether the http service calls has been handled or not?
custom-error-handler.service.ts
#Injectable()
export class CustomErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error) {
error = error || new Error('There was an unexpected error');
const loggingService: LoggerService = this.injector.get(LoggerService);
const location = this.injector.get(LocationStrategy);
const message = error.message ? error.message : error.toString();
const url = location instanceof PathLocationStrategy
? location.path() : '';
// get the stack trace, lets grab the last 10 stacks only
StackTrace.fromError(error).then(stackframes => {
const stackString = stackframes
.splice(0, 20)
.map((sf) => {
return sf.toString();
}).join('\n');
// log with logging service
loggingService.error({ message, url, stack: stackString });
});
throw error;
}
}
auth.service.ts
#Injectable()
export class UserAuthService {
login(emailAddress: string, password: string): Observable<boolean> {
if (this.isAuthenticated()) {
return Observable.of(true);
}
return Observable.create(observer => {
this.http.get('http://someurl/login')
.map(res => res.json())
.subscribe(
data => {
observer.next(this.isAuthorized);
observer.complete();
}, err => {
observer.error(err);
}
);
});
}
}
my-component.ts
import { UserAuthService } from '../services/auth.service';
#Component({
selector: 'my-component',
templateUrl: './my-component.html',
styleUrls: ['./my-component.scss']
})
export class MyComponent {
constructor(private authService: UserAuthService) {}
handleLogin() {
this.authService.login(formValues.emailAddress, formValues.password)
.subscribe(res => {
console.log('Logged In');
}, err => {
console.log('Logging Failed');
// Global Error DOESN'T fire here as the Error has been handled
});
}
handleLoginPart2() {
this.authService.login(formValues.emailAddress, formValues.password)
.subscribe(res => {
console.log('Logged In');
}); // Global Error does fire here as the Error has NOT been handled
}
}
I was able to resolve the issue myself, by creating a HttpClient which inherits from Http.
By doing this I am able to handle the error gracefully.
http-client.service.ts
import { ConnectionBackend, Http, RequestOptions, RequestOptionsArgs, Response } from '#angular/http';
#Injectable()
export class HttpClient extends Http {
http;
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
get(url, options?: RequestOptionsArgs): Observable<Response> {
return super.get(url, options)
.catch(this.handleError);
}
private handleError(errorRes: Response | any) {
return Observable.throw(retError);
}
}
I have a service that makes request to an api and according to its response I should decide that a component must be loaded or not. but because of a time spent to receive the response, component loads regardless of response status, and after some time (about 0.5 secs) response is received and if the component must not be loaded, we navigate to somewhere else. I don't want the component to be loaded before receiving the response.
I'm using canActivate function from AuthGuard in angular 4 as below:
export class AuthGuard implements CanActivate {
access = true;
res: any;
constructor(private router: Router, private routeService: RouteService){}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
setTimeout(() => {
if( !this.exept.includes(this.router.url) ){
this.routeService.FormOperation(this.router.url).subscribe(item=> {
this.res = item;
if (this.res.status == 200) {
if (this.res.data.Access[1] == false) {
this.access = false;
}
if (this.access == true)
{
return true;
}
else {
this.router.navigate(['/dashboard']);
return false;
}
})
}
},0);
if (sessionStorage.getItem('token') && this.access)
{
// logged in so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
I'm using setTimeout so that I can get a correct this.router.url .
Update:
I added resolver as below:
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): void {
this.routeService.FormOperation(this.router.url).toPromise()
.then(response => {
this.form_operation_data = response;
if(!this.form_operation_data['data']['Access'][1]) {
this.router.navigate(['/dashboard']);
}
})
.catch(err => {
console.error(err);
});
}
but still the component loads before response data receives ...
You're so close: your AuthGuard should return true or false, and based on this value, the route will be activated or not. Now you have to add this auth guard to your routing (here is an example for activating child route). And if you want to fetch data before component load, you can use resolvers.
Resolver
/* Imports */
#Injectable()
export class DataResolverService {
constructor(
private _serviceToShareData: ServiceToShareData,
private _serviceToGetData: ServiceToGetData,
) { }
/* An example with Promise */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<void> {
return this._serviceToGetData.anyRequestToApi()
.then(response => this._serviceToShareData.setData(response))
.catch(err => {
console.error(err);
});
}
}
And now you can get fetched data from ServiceToShareData service in your component, which you want to load with this data.
Routing module
/* Other imports */
import {DataResolverService } from './path-to-service/data-resolver-service'
import {AuthGuard} from './path-to-service/auth-guard'
const routes: Routes = [
{
path: 'app',
component: ParentComponent,
children: [
{
path: 'child-route',
component: childRouteComponent,
canActivate: [AuthGuard],
resolve: {
data: DataResolverService
}
}
]
}
];
/* Other stuff like #NgModule and class export*/
Very new to Angular. I checked similar questions but they either dive into specifics or I just don't understand the solutions.
The actual error:
"Cannot read property 'idPlanet' of undefined at Object.eval [as updateRenderer] (PlanetComponent.html:11)"
The issue:
planetDetail.idPlanet is undefined most probably?
Suspect:
getPlanetDetail()
planet.component.html:
<div class="col-sm-9">
ID: {{ planetDetail.idPlanet }}
</div>
planet.component.ts:
import {
Component,
OnInit
} from '#angular/core';
import { Planet } from './planet';
import { PlanetService } from './planet.service';
#Component({
selector: 'planets',
templateUrl: './planet.component.html'
})
export class PlanetComponent implements OnInit {
private planets: Planet[];
private planetDetail: Planet;
constructor(
private planetService: PlanetService
) {}
public ngOnInit(): void {
this.getPlanetDetail();
this.getPlanets();
}
public getPlanets() {
this.planetService.getPlanets()
.subscribe(
(planets) => this.planets = planets
);
}
public getPlanetDetail() {
this.planetService.getPlanetDetail()
.subscribe(
(planets) => this.planetDetail = planets
);
}
}
planet.service.ts:
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Planet } from './planet';
#Injectable()
export class PlanetService {
private planetsUrl = 'http://localhost/index.php/api/planet/'; // URL to web api
// Injecting the http client into the service
constructor(private http: Http) {}
public getPlanets(): Observable<Planet[]> {
return this.http.get(this.planetsUrl + 'planets/id_sol/1')
.map(this.parseData)
.catch(this.handleError);
}
public getPlanetDetail(): Observable<Planet> {
return this.http.get(this.planetsUrl + 'planet/id_planet/1')
.map(this.parseData)
.catch(this.handleError);
}
private parseData(res: Response) {
let body = res.json();
if (body instanceof Array) {
return body || [];
} else {
return body.post || {};
}
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
I'm at a loss tbh, I tried to build my getPlanetDetail() method from getPlanets() which works fine. Should I use a promise?
I'm having a hard time figuring out where exactly I can put console.log() to debug. I'm using angular-starter kit from Github.
Thanks for your time.
edit 1: the api outputs {"idPlanet":"1","name":"Earth"}
As the request is asynchronous sometime the value will not be fetched from the server when the page loads, therefore, the planetDetail is undefined. in order to avoid this you can use add '?' between planetDetail and idPlanet. that prints only if it has the value
ID: {{ planetDetail?.idPlanet }}
If you want to print the result or error
public getPlanetDetail() {
this.planetService.getPlanetDetail()
.subscribe(
(planets) => {
console.log(planets);
this.planetDetail = planets
},
error => {console.log(error);}
);
}
Since your response is {"idPlanet":"1","name":"Earth"}, try the following:
public getPlanetDetail(): Observable<Planet> {
return this.http.get(this.planetsUrl + 'planet/id_planet/1')
.map(res => res.json())
.catch(this.handleError);
}
my friend to debug if your 'planetDetail' is populated, just add 'console.log (planetDetail)' in the 'subscribe' method. Follow the example below.
public getPlanetDetail() {
this.planetService.getPlanetDetail()
.subscribe(
(planets) => this.planetDetail = planets
);
}
public getPlanetDetail() {
this.planetService.getPlanetDetail()
.subscribe(
(planets) => this.planetDetail = planets, (err) => (err), () => console.log(this.palnetDetail)
);
}
More about subscribe()
subscribe(function(response) {
console.log("Success Response" + response)
},
function(error) {
console.log("Error happened" + error)
},
function() {
console.log("the subscription is completed")
});