Call REST POST API with Angular 5 and HTTPClient - spring

I'm creating frontend for my backend using Angular and having troubles calling POST API using HTTPClient. Below is my code:
article.service.ts
#Injectable()
export class ArticleService {
url = "//localhost:8080/deleteArticle";
constructor(private http: HttpClient) { }
deleteArticle(article: Article): Observable<HttpResponse<Article>> {
return this.http.post<Article>(this.url, article,
{
observe: 'response'
}
);
}
}
article.component.ts
#Component({
selector: 'app-article',
templateUrl: './article.component.html'
})
export class AcrticleComponent implements OnInit {
articleForm: FormGroup;
constructor(private formBuilder:FormBuilder, private articleService: ArticleService) {
}
ngOnInit() {
this.articleForm = this.formBuilder.group({
title: ['', [ Validators.required ] ]
});
}
onFormSubmit() {
let article = this.articleForm.value;
this.deleteArticle(article);
this.articleForm.reset();
}
deleteArticle(article: Article) {
this.articleService.deleteArticle(article).subscribe(
article => {
console.log(article);
},
err => {
console.log(err);
}
);
}
get title() {
return this.articleForm.get('title');
}
}
Spring Controller:
#PostMapping("/deleteArticle")
#CrossOrigin(origins = "http://localhost:4200")
public String deleteArticle(#RequestParam(value = "id") String id) {
deleteService.deleteArticle(id);
}
After entering the title and hitting submit, it returns this error (status 404):
{error: "Collection 'localhost:8080' not found"}
Can you show me what I did wrong and how my angular frontend couldn't find my backend endpoint?

The url needs to be complete.
include the http:
But I would suggest using the webpack dev server proxy.
If you put all your apis under the /api/ url then you can proxy all calls to /api/* back to your spring backend.
then when you launch your project you do the same there and proxy in /api/* using nginx or similar.
You can read more about how to proxy using angular-cli here https://github.com/angular/angular-cli/wiki/stories-proxy

Make sure you are spring application is running on the same port as 8080 and add http before your url. I hope this not your problem,but try like this..
#Injectable()
export class ArticleService {
url = "http://localhost:4200/deleteArticle";//add http here
constructor(private http: HttpClient) { }
deleteAccount(article: Article): Observable<HttpResponse<Article>> {
return this.http.post<Article>(this.url, article,
{
observe: 'response'
}
);
}
}
EDIT
Add a class Like Article and get the Id which you are sending from Article class of Angular Service
#PostMapping("/deleteArticle")
#CrossOrigin(origins = "http://localhost:4200")
public String deleteArticle(#RequestParam() Article article,
RedirectAttributes redirectAttributes) {
deleteService.deleteArticle(article.id, redirectAttributes);
}

Related

Why is ValidateNested fields not working in e2e tests?

I have the following NestJS controller:
class PhoneTestDto {
#IsNotEmpty()
#IsPhoneNumber()
phone: string
}
class TestDto {
#IsNotEmpty()
#Type(() => PhoneTestDto)
#ValidateNested({ always: true, each: true })
#ArrayNotEmpty()
phones: PhoneTestDto[]
}
#Controller('v1/user')
export class UserController {
constructor(private readonly userService: UserService) {}
#Post()
async addUser(#Body() body: TestDto): Promise<LoginResponseDto> {
console.log("valid")
return
}
}
When I run the app and send through Postman a request such as:
{
"phones": [{}]
}
I get a correct response (phones.0.phone must be a valid phone number...)
When I try to run an e2e test, it passes without validating the phone.
This is my test file:
describe('Test', () => {
let app: INestApplication
beforeEach(async () => {
jest.resetModules()
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile()
app = moduleRef.createNestApplication()
app.useGlobalPipes(new ValidationPipe({transform: true}))
await app.init()
})
it('test1', async () => {
const response = await request(app.getHttpServer())
.post('/v1/user')
.send({phones: [{}]})
expect(response['res']['statusCode']).toEqual(201)
})
})
Sending {} or {[]} does produce a validation error so validation is generally working, but not the validation of nested fields in the context of testing.
ALSO: Removing jest.resetModules() brings correct behavior.
I'm not sure how jest.resetModules() relate to nestjs validation, and how should I use if I do need to reset modules.

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

Navigate to another route from a service

I have service that intercepts all my Http requests so I can check if the user's token is valid or not. When a Http response is 401, I want the current user to be logged out of the application:
import { Http, ConnectionBackend, RequestOptions, RequestOptionsArgs, Request, Response } from '#angular/http'
import { Router } from '#angular/router'
import { Observable } from 'rxjs/Observable'
import { Injectable } from '#angular/core'
import { Config } from './shared/config'
import { RouterExtensions } from 'nativescript-angular/router'
import 'rxjs/add/observable/throw'
#Injectable()
export class RequestInterceptorService extends Http {
private config: Config = new Config()
constructor(
backend: ConnectionBackend,
defaultOptions: RequestOptions,
private router: Router,
private routerExtensions: RouterExtensions,
) {
super(backend, defaultOptions)
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options))
}
intercept(observable: Observable<Response>): Observable<Response> {
return observable.catch((err, source) => {
if (err.status === 401) {
this.logout()
return Observable.empty()
} else {
return Observable.throw(err)
}
})
}
logout() {
this.config.clear()
this.routerExtensions.navigate(["/signin"], {
clearHistory: true
})
}
}
My issue is that router or routerExtensions are always undefined, so I can't really redirect the user to any other path at all using this service.
So, I found the solution. It was just a mater of understanding all of what I was doing.
I created a RequestInterceptor service that extends Http module. It includes a interceptor that checks if the status code is equal to 401. In order to make it compatible with my project, I decided to provide it as custom implementation of Http. The following code on my #NgModule does just that:
providers: [
{
provide: Http,
useClass: RequestInterceptorService
}
]
But the thing RequestInterceptorService does not have access to Http dependencies: ConnectionBackend and RequestOptions, so I had to inject them using the deps property, which is an array where I can identify an array of dependencies that will get injected. In my case, I need to inject not only the dependencies that Http requires (XHRBackend, RequestOptions), but also the one that my service will use: RouterExtensions.
My providers declaration looks like this:
providers: [
{
provide: Http,
useClass: RequestInterceptorService,
deps: [XHRBackend, RequestOptions, RouterExtensions]
}
]
After doing this, I was able to successfully redirect a user to the login page when a 401 status code is returned from any request.

No 'Access-Control-Allow-Origin' header when retrieven data from WebApi to angular 2 application

I am trying to retrieve data from a WebApi project I have created. I have build a new Angular 2 application, which going to get this data through an Api call.
Already have made the data ready in the WebApi project. If I use postman to call the Api, I get data like this example:
[
{
"Id": 1,
"BookingNr": "123456789",
"OutboundDate": "2016-02-05T00:00:00",
"ReturnDate": "2016-04-04T00:00:00",
"Route": "Oslo - Stockholm",
"Passengers": "1 Adult and 1 Child",
"VehicleType": "Car 1.85m x 4.5m"
},
{
"Id": 2,
"BookingNr": "234567891",
"OutboundDate": "2016-03-05T00:00:00",
"ReturnDate": "2016-04-04T00:00:00",
"Route": "Stockholm - Oslo",
"Passengers": "2 Adult and 1 Child",
"VehicleType": "Car 1.85m x 4.5m"
}
]
In my angular 2 project, I have a main component, which calls a service to get the data from the api.
Main component:
#Component({
selector: 'reservation-component',
providers: [...FORM_PROVIDERS, BookingsService],
directives: [...ROUTER_DIRECTIVES, CORE_DIRECTIVES, BookingsListComponent ],
styles: [`
agent {
display: block;
}
`],
pipes: [],
template: `
***No Html in this example***
`,
bindings: [BookingsService],
})
#Injectable()
export class BookingsComponent {
bookings: Array<amendmentBookings> = [];
constructor(public bookingsService: BookingsService) {
this.bookings = this.bookingsService.getBookings();
}
}
Then there is the Service, which makes the call.
Service
#Injectable()
export class BookingsService {
constructor(private router: Router, public http: Http) {
console.log('Booking Service created.', http);
}
getBookings(): Array<amendmentBookings> {
var bookingsRetrieved: Array<amendmentBookings>
this.http.get('http://localhost:55350/api/bookings')
.map(res => res.json())
.map((bookings: Array<any>) => {
let result: Array<amendmentBookings> = [];
if (bookings) {
bookings.forEach(booking => {
result.push(
new amendmentBookings(
booking.bookingNumber,
new Date(booking.outboundDate),
new Date(booking.returnDate),
booking.route,
booking.passengers,
booking.vehicleType))
});
}
return result;
}).subscribe(data => {
bookingsRetrieved = data;
console.log(bookingsRetrieved)
},
err => console.log(err));
return bookingsRetrieved;
}
}
export class amendmentBookings {
constructor(
public bookingNumber: string,
public outboundDate: Date,
public returnDate: Date,
public route: string,
public passengers: string,
public vehicleType: string
) { }
}
When I try to call it, I get the following error:
XMLHttpRequest cannot load http://localhost:55350/api/bookings. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
Have tried to search for the problem, but can only find that something is blocking the request, but no solution for it..
This is how my bookingsController looks like in Visual Studio (I'm using entity framework)
BookingController
public class BookingsController : ApiController
{
private BookingsContext db = new BookingsContext();
// GET: Bookings
public IEnumerable<Booking> Get()
{
return db.Bookings.ToList();
}
}
You need to enable CORS on your Web API if you want to be able to call it from javascript code hosted on a different domain.
So basically in your Web API bootstrap script you would call:
config.EnableCors();
and then decorate your controller with the proper attribute:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class BookingsController : ApiController
{
...
}
Of course enabling CORS for all domains (*) comes with its security implications so you might want to selectively enable it only for the domain on which your javascript calling code is hosted.

ExtDirectSpring Router URL?

I'm trying to get ExtDirectSpring to work but can't figure out what the URL to the Direct Router shall be.
My controller:
#Controller
#RequestMapping(value = "/sonstiges/extdirect")
#Transactional
public class ExtDirectController {
#RequestMapping(value = "")
public String start() throws Exception {
return "/sonstiges/extdirect";
}
#ExtDirectMethod(value = ExtDirectMethodType.STORE_READ)
public List<String> loadAllMyStuff() {
return Arrays.asList(new String[] {"one", "two"});
}
}
In my JSP I add a provider like this:
Ext.direct.Manager.addProvider({
"type":"remoting", // create a Ext.direct.RemotingProvider
"url":"/fkr/app/controller/extjs/remoting/router", // url to connect to the Ext.Direct server-side router.
"actions":{ // each property within the actions object represents a Class
"ExtDirectController":[
// array of methods within each server side Class
{
"name":"loadAllMyStuff", // name of method
"len":0
},
{
"name":"myTestFunction", // name of method
"len":0
}
]
},
"namespace":"FKR"// namespace to create the Remoting Provider in
});
... and use the following store to populate a grid:
store: {
model: 'Company',
remoteSort: true,
autoLoad: true,
sorters: [
{
property: 'turnover',
direction: 'DESC'
}],
proxy: {
type: 'direct',
directFn: FKR.ExtDirectController.loadAllMyStuff
}
}
Loading the page, a request to http://localhost:8080/fkr/app/controller/extjs/remoting/router is send, but not to my loadAllMyStuff- function. My guess is that the URL to the direct router is wrong.
What is the correct URL to the router?
EDIT: I just figured out that the method router in RouterController expects the parameters extAction and extMethod, but with the given code the parameters action and method are sent. That seems to be odd ...

Resources