Reputation: 1712
I'm learning Node.JS with Angular 4. I build a sample Node API for simple GET/POST request. My GET operation works fine and I am able to fetch data in Angular. My OST operation isn't getting called at all from Angular. If I use Postman, I'm able to call POST successfully and data also gets inserted in database.
Here is my sample code for Node POST:
app.post('/groups', function (req, res, next){
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type");
res.header("Access-Control-Allow-Methods", "GET, POST","PUT");
console.log('Request received with body' + req.body);
//DEV AWS MySQL
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'xxxxxxx',
user : 'xxxxxxx',
password : 'xxxxxxx',
database : 'xxxxxxx',
port : 3306
});
connection.connect();
connection.query('CALL storedprocedure(?, ?, ?, ?, ?, ?)', [req.body.group_avatar_image,req.body.name,req.body.display_name,req.body.unique_id,req.body.description,req.body.adzone], function (err, results, fields){
if (err)
res.send(results);
//res.status(201).send("Groups created successfully");
res.status(201).send(results[0]);
});
This works fine with Postman and I get 201.
Here is my Angular 4 code:
import { Injectable } from '@angular/core';
import { Http, Response,RequestOptions, Request, RequestMethod, Headers} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import { Group } from './group';
@Injectable()
export class GroupsService{
private _GroupsUrl = 'http://localhost:5000/api/groups';
constructor(private _http: Http){};
getGroups(): Observable<Group[]> {
let headers = new Headers({ 'Content-Type': 'application/json' });
headers.append('Accept', 'application/json');
headers.append('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT');
headers.append('Access-Control-Allow-Origin', '*');
//headers.append('Access-Control-Allow-Headers', "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding");
let options = new RequestOptions({ method: RequestMethod.Post, headers: headers, url:this._GroupsUrl });
//debugger;
return this._http.get(this._GroupsUrl)
.map((Response: Response) => <Group[]>Response.json()[0])
//.do(data => console.log ('ALL: ' + JSON.stringify(data)))
.catch(this.handleError);
}
CreateGroup(GroupM): Observable<string>{
let headers = new Headers({ 'Content-Type': 'application/json' });
headers.append('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, OPTIONS');
headers.append('Access-Control-Allow-Origin', 'http://localhost:4200');
headers.append('Access-Control-Allow-Headers', "X-Requested-With, Content-Type");
//let options = new RequestOptions({ method: RequestMethod.Post, headers: headers, body:JSON.stringify(GroupM), url:this._GroupsUrl });
let options = new RequestOptions({ method: RequestMethod.Post});
console.log('Calling ' + this._GroupsUrl + ' with body as :' + JSON.stringify(GroupM) + ' and request options are : ' + JSON.stringify(options));
var req = new Request(options.merge({
url: this._GroupsUrl
}));
debugger;
//return this._http.post(this._GroupsUrl,GroupM)
return this._http.post(req.url,JSON.stringify(GroupM),options)
.map(res => res.json())
.do(data => console.log ('ALL: ' + JSON.stringify(data)))
.catch(this.handleError);
}
private handleError(error:Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server Error');
}
}
What is wrong here?
Upvotes: 10
Views: 1172
Reputation: 108
Don't stringify the POST data in HTTP POST. Simply pass the object.
Upvotes: 1
Reputation: 28368
First of all, do yourself a favour and wrap Angular's Http
service so that you don't have to manually add an auth token and headers for every request. Here's a simple implementation which you can build on:
First of all let's create a Cookies
service which will act as a fallback where localStorage
isn't supported:
@Injectable()
export class Cookies {
public static getItem(sKey) {
if (!sKey) {
return null;
}
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
}
public static setItem(sKey?, sValue?, vEnd?, sPath?, sDomain?, bSecure?) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
return false;
}
let sExpires = '';
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
return true;
}
public static removeItem(sKey, sPath?, sDomain?) {
if (!this.hasItem(sKey)) {
return false;
}
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
return true;
}
public static hasItem(sKey) {
if (!sKey) {
return false;
}
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
}
public static keys() {
let aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
for (let nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
}
return aKeys;
}
}
Then a storage logger which keeps track of things added to the storage (useful for updating the auth token for every request when it changes):
import {Cookies} from '@services/cookies.service';
@Injectable()
export class StorageLogger {
private logger = new BehaviorSubject<any>(null);
public logger$ = this.logger.asObservable();
set(key: string, value: any): void {
try {
localStorage.setItem(key, JSON.stringify(value));
}
catch(err) {
Cookies.setItem(key, JSON.stringify(value));
}
this.get(key);
}
get(key: string) {
let item: any;
try {
item = JSON.parse(localStorage.getItem(key));
}
catch(err) {
item = JSON.parse(Cookies.getItem(key));
}
this.logger.next({value: item, key: key});
}
remove(keys: string[]) {
try {
for (const key of keys) {
localStorage.removeItem(key);
this.logger.next({value: null, key: key});
}
}
catch(err) {
for (const key of keys) {
Cookies.removeItem(key);
this.logger.next({value: null, key: key});
}
}
}
}
Then you want to wrap angular's Http
:
@Injectable()
/* Wrapper for Angular's Http class, let's us provide headers and other things on every request */
export class HttpClient implements OnDestroy {
constructor(
private http: Http,
private storageLogger: StorageLogger
) {
this.getToken();
this.storageSubscription = this.storageLogger.logger$.subscribe(
(action: any) => {
if (action && action.key === tokenIdKey) {
this.getToken();
}
}
);
}
private storageSubscription: Subscription;
private token: string;
ngOnDestroy() {
this.storageSubscription.unsubscribe();
}
getToken(): void {
try {
this.token = localStorage.getItem(tokenIdKey);
}
catch(error) {
this.token = Cookies.getItem(tokenIdKey);
}
}
convertJSONtoParams(json: any): URLSearchParams {
const params: URLSearchParams = new URLSearchParams();
for (const key in json) {
if (json.hasOwnProperty(key) && json[key]) {
if (json[key].constructor === Array && !json[key].length) {
continue;
}
else {
params.set(key, json[key]);
}
}
}
return params;
}
getRequestOptions(params?: any): RequestOptions {
const headers = new Headers();
// headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append('Content-Type', 'application/json');
this.createAuthorizationHeader(headers);
return new RequestOptions({
headers: headers,
search: params ? this.convertJSONtoParams(params) : null
});
}
createAuthorizationHeader(headers: Headers): void {
headers.append('Authorization', this.token);
}
checkResponseStatus(err: any) {
if (err.status === 401) {
// If we want we can redirect to login here or something else
}
return Observable.of(err);
}
get(url: string, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.get(host + url, options).catch((err: Response) => this.checkResponseStatus(err));
}
post(url: string, data: any, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.post(host + url, data, options).catch((err: Response) => this.checkResponseStatus(err));
}
put(url: string, data: any, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.put(host + url, data, options).catch((err: Response) => this.checkResponseStatus(err));
}
delete(url: string, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.delete(host + url, options).catch((err: Response) => this.checkResponseStatus(err));
}
patch(url: string, data: any, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.patch(host + url, data, options).catch((err: Response) => this.checkResponseStatus(err));
}
head(url: string, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.head(host + url, options).catch((err) => this.checkResponseStatus(err));
}
options(url: string, params?: any): Observable<Response> {
const options: RequestOptions = this.getRequestOptions(params);
return this.http.options(host + url, options).catch((err: Response) => this.checkResponseStatus(err));
}
}
And finally you should also add a generic api service which you will call, instead of creating a new service for every part of your application. This will save you a lot of code and effort. Here it is:
import {IResponse} from '@interfaces/http/response.interface';
import {HttpClient} from '@services/http/http-client.service';
@Injectable()
export class AppApi {
constructor(private http: HttpClient) {}
get(url: string, params?: any): Observable<IResponse> {
return this.http.get(url, params)
.map((res: Response) => res.json() as IResponse)
.catch((error: any) => {
return Observable.throw(error.json().error || 'Server error');
}
);
}
post(url: string, data: any, params?: any) {
return this.http.post(url, data, params)
.map((res: Response) => res.json() as IResponse)
.catch((error: any) => {
return Observable.throw(error.json().error || 'Server error');
}
);
}
put(url: string, data: any, params?: any) {
return this.http.put(url, data, params)
.map((res: Response) => res.json() as IResponse)
.catch((error: any) => {
return Observable.throw(error.json().error || 'Server error');
}
);
}
delete(url: string, params?: any): Observable<IResponse> {
return this.http.delete(url, params)
.map((res: Response) => res.json() as IResponse)
.catch((error: any) => {
return Observable.throw(error.json().error || 'Server error');
}
);
}
}
You'll notice that I've also created an interface which types up my response from the backend, which is usually something like:
{error: any; data: any; results: number; total: number;}
Now that we've taken care of those problems, let's tackle your original question. The most likely reason as to why your request isn't running, is that you're not subscribing to the http
observable. Observables are lazy so if you don't subscribe to it via .subscribe
or @ngrx/effects
, it just won't do anything.
So let's assume that you're calling CreateGroup
like this:
this.groupsService.CreateGroup(data);
This won't do anything until you subscribe:
this.groupsService.CreateGroup(data).subscribe(() => {
// Here you can react to the post, close a modal, redirect or whatever you want.
});
I'd also recommend adding a .first()
to your api calls as this will prevent you from having to unsubscribe from the observables manually when the component is destroyed.
So to use the implementation as above you'd simply do:
constructor(private appApi: AppApi) {}
...
this.appApi.post('/groups').first().subscribe(() => {
// Do something
});
I hope this is helpful.
Upvotes: 2
Reputation: 1712
Finally able to resolve it using promise and it resolves the issue. Not sure what exactly is the issue with observable.
> CreateGroup(GroupObj:Group) : Promise<Group>{
return this._http
.post(this._GroupsUrl,JSON.stringify(GroupObj),{headers: this.headers})
.toPromise()
.then(res => res.json().data as Group)
.catch(this.handleError);
}
Upvotes: 4