Unseen
Unseen

Reputation: 27

Req.Body is empty object on Angular 2 Form submission + Bodyparser

I have a reactive (model driven) form in my application and I'm having trouble getting the form data through to mongoose. When I submit the data via http post method, I keep getting an error because the backend is receiving an empty object.

I know the backend works because when I submit a post request with Postman, I get a result status of "201 Created". I've been reading the forums and various blogs for the past few days and I'm still stumped.

Questions

  1. What do I need to change to get the form data to hit the backend appropriately?
  2. Is there a best practice for Angular2 Form data submission?

Notes It may look like there are some things missing, such as the URL, and you will notice some additional fields in the form. I purposely did not include all of these for conciseness and obfuscated the actual links to my backend.

Backend - Mongoose

jobs.controllers.js

var mongoose = require('mongoose');
var Job = mongoose.model('Job');
module.exports.addNewJob = function(req, res){
  console.log(req.body);//added to see req payload from client
  Job
        .create({
            _id: req.body.jobid,
            desc: req.body.name,
            type: req.body.jobType,
            location: req.body.location
        },function(err, job){
            if(err){
                console.log("Error creating job");
                res
                    .status(400)
                    .json(err);
            }else{
                console.log("Job Created", job);
                res
                    .status(201)
                    .json(job);
            }
        });
}

jobs.model.js

var mongoose = require('mongoose');
var jobSchema = new mongoose.Schema({
    _id: {
        type: String
    },
    desc: {
        type: String
    },
    type: {
        type: String,
        default: "TM"
    },
    location: String
});
mongoose.model('Job', jobSchema);

db.js

var mongoose = require ('mongoose');
var dburl = 'mongodb://localhost:27017/dbname';

mongoose.connect(dburl);

mongoose.connection.on('connected', function(){
    console.log('Mongoose connected to ' + dburl);
});

mongoose.connection.on('disconnected', function(){
    console.log('Mongoose disconnedted');
});

mongoose.connection.on('error', function(err){
    console.log('Mongoose connection error: ' + err);
});

require('./jobs.model.js');

index.js

var express = require('express');
var router = express.Router();

var ctrlJobs = require('../controllers/jobs.controllers.js');

router
    .route('/jobs')
    .get(ctrlJobs.jobsGetAll)
    .post(ctrlJobs.addNewJob);

module.exports = router;

app.js

require('./api/data/db.js');
var express = require('express');
var app = express ();
var path = require('path');
var bodyParser = require('body-parser');

var routes = require('./api/routes');

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.use(express.static(path.join(__dirname, 'public')));

app.use(bodyParser.urlencoded({extended: false}));

app.use('/api', routes);//Goes to index.js

Front End - Angular2 Final

jobs.service

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';

import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';


@Injectable()
export class JobsService{
    private _url = "http://123.456.789.100/api/jobs"; //removed actual address for post
    constructor(private _http: Http){}

    addNewJob(form: Object){
        let headers = new Headers({'Content-Type':'application/json'});
        let options = new RequestOptions({headers: headers});
        return this._http
                .post(this._url, JSON.stringify(form), options)
                .map(this.extractData)
                .catch(this.handleError);
    }

    private extractData(res: Response){
        let body = res.json();
        return body.data || { };
    }

    private handleError (error: Response | any) {
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
  }
}

job-form.component

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';

import { JobsService } from './jobs.service';

@Component({
    templateUrl: './job-form.component.html',
    providers: [JobsService]
})

export class JobFormComponent implements OnInit {

    id: string;
    job: any;
    jobForm: FormGroup;
    title: string;

    constructor(
        private _fb: FormBuilder,
        private _router: Router,
        private _route: ActivatedRoute,
        private _jobsService: JobsService
        ){

        }


    ngOnInit(): void {
        this.jobForm = this._fb.group({
                jobid: ['', Validators.required],
                name: ['', Validators.required],
                jobType: ['', Validators.required],
                location: ['', Validators.required]
            });

        this.id = this._route.snapshot.params['id'];

        this.title = this.id ? "Edit Job" : "Create New Job";

    }

    save(form: any){
        console.log(this.jobForm.value);

        this._jobsService.addNewJob(form).subscribe((dataResponse) => {
            console.log("Submitting" + dataResponse);
        });
    }
    reset(){
        this.jobForm.reset();
    }
}

job-form.component.html

<form [formGroup]="jobForm" novalidate (ngSubmit)="save(jobForm.value)">
                <div class="row">
                    <div class="col-xs-2">
                        <label>Job Number</label>
                        <input [ngModel]="job?.jobid ? job.jobid : ''" class="form-control" type="text" formControlName="jobid">
                    </div>
                    <div class="col-xs-8">
                        <label>Title</label>
                        <input [ngModel]="job?.name ? job.name : ''" class="form-control" type="text" formControlName="name">
                    </div>
                    <div class="col-xs-2">
                        <label>Type</label><br />
                        <select [ngModel]="job?.jobType ? job.jobType : ''"class="form-control" formControlName="jobType">
                            <option value="TM">T&M</option>
                            <option value="LS">Lump Sum</option>
                        </select>
                    </div>
                </div>
                <div class="row">
                    <div class="col-xs-2">
                        <label>Status</label><br />
                        <select class="form-control" formControlName="status" [ngModel]="job?.status ? job.status : ''">
                            <option value="Pending">Pending</option>
                            <option value="Active">Active</option>
                            <option value="On Hold">On Hold</option>
                            <option value="Complete">Complete</option>
                            <option value="Closed">Closed</option>
                        </select>
                    </div>
                    <div class="col-xs-5">
                        <label>Location</label>
                        <input [ngModel]="job?.location ? job.location : ''" class="form-control" type="text" formControlName="location">
                    </div>
                </div>
               <br />
                <div class="row">
                    <div class="col-xs-1">
                        <button type="btn btn-primary" class="btn btn-primary" [disabled]="!jobForm.valid">Submit</button>
                    </div>
                    <div class="col-xs-1">
                        <button class="btn btn-default" (click)="reset()">Reset</button>
                    </div>
                </div>
            </form>

Testing

With the headers set to application/json the req.body is an empty object

{}

When I set the headers to application/x-www-form-urlencoded the req.body shows

{ '{"jobid":"8746541","name":"asdfasdfasdfasdf","jobType":"LS","location":"asdfa"}':''}

on submit XHR

PostmanSuccess

Upvotes: 0

Views: 2326

Answers (4)

Jaya Krishna Lakkoju
Jaya Krishna Lakkoju

Reputation: 31

1.At front end level:- When using reactive driven forms Pass the front end class file binding object as getRawValue().

1.1 At front end to backend integration
such as service in which our definite method when call we have to pass the binded object with when saving includes headers as though it was post method.

  1. Add middleware by requiring bodyparser. Use use as a bodyparser.json.

  2. Navigate to particular route in postman and first check with new data and post it.

  3. Now bind with front end object.

  4. What we are passing to post rest API method was object from front end side.

Happy ending 😃

Upvotes: 0

Shivam Mishra
Shivam Mishra

Reputation: 1856

in a file jobs.service you are setting Header to

'Content-Type':'application/json'

in a file index.js you are handling body request

app.use(bodyParser.urlencoded({extended: false}));

so there are two solution you can do.

1.) Either change in a jobs.service

'Content-Type': 'application/x-www-form-urlencoded'

2.) Or change in index.js

app.use(bodyParser.json());

Upvotes: 2

Rohan Shenoy
Rohan Shenoy

Reputation: 895

In app.js add the following middlewares...

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

For more information visit this site:link!

Upvotes: 0

Unseen
Unseen

Reputation: 27

Solution

Added line app.use(bodyParser.json()); to app.js. Essentially, this tells the application that it should natively understand JSON. Submitting the form in the application now results in a new record being added to mongodb.

Upvotes: 1

Related Questions