Операция после работы с Angular 4

Я изучаю Node.JS с Angular 4. Я создаю образец Node API для простого запроса GET/POST. Моя работа GET работает нормально, и я могу получить данные в Angular. Моя операция OST не вызвана вообще от Angular. Если я использую Postman, я могу вызвать POST успешно, и данные также будут вставлены в базу данных.

Вот мой пример кода для 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]);
});

Это отлично работает с Postman, и я получаю 201.

Вот мой код Angular 4:

    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');
    }
}

Что здесь не так?

Ответ 1

Наконец, можно разрешить это с помощью обещания, и оно решает проблему. Не уверен, что именно происходит с наблюдаемым.

>  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);
    }

Ответ 2

Прежде всего, сделайте себе услугу и оберните услугу Angular Http, чтобы вам не приходилось вручную добавлять токены и заголовки для каждого запроса. Вот простая реализация, на которой вы можете опираться:

Прежде всего, создайте службу Cookies, которая будет действовать как резерв, где localStorage не поддерживается:

@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;
  }
}

Затем хранитель данных, который отслеживает все, что добавлено в хранилище (полезно для обновления токена аутентификации для каждого запроса при его изменении):

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});
      }
    }
  }
}

Затем вы хотите обернуть Angular Http:

@Injectable()

/* Wrapper for Angular Http class, let 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));
  }
}

И, наконец, вы также должны добавить общую службу api, которую вы вызовете, вместо того, чтобы создавать новую услугу для каждой части вашего приложения. Это сэкономит вам много кода и усилий. Вот он:

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');
      }
    );
  }
}

Вы заметите, что я также создал интерфейс, который отображает мой ответ от бэкэнд, который обычно выглядит примерно так:

{error: any; data: any; results: number; total: number;}

Теперь, когда мы позаботились об этих проблемах, давайте рассмотрим ваш первоначальный вопрос. Наиболее вероятная причина того, почему ваш запрос не работает, заключается в том, что вы не подписываетесь на Http наблюдаемый. Наблюдаемые данные ленивы, поэтому, если вы не подписаны на него через .subscribe или @ngrx/effects, он ничего не сделает.

Итак, допустим, что вы вызываете CreateGroup следующим образом:

this.groupsService.CreateGroup(data);

Это не сделает ничего, пока вы не подписаться:

this.groupsService.CreateGroup(data).subscribe(() => {

  // Here you can react to the post, close a modal, redirect or whatever you want.
});

Я бы также рекомендовал добавить .first() к вашим вызовам api, поскольку это не позволит вам отказаться от подписки на наблюдаемые вручную, когда компонент будет уничтожен.

Итак, чтобы использовать реализацию, как указано выше, вы просто выполните:

constructor(private appApi: AppApi) {}

...

this.appApi.post('/groups').first().subscribe(() => {

  // Do something
});

Я надеюсь, что это будет полезно.

Ответ 3

Не стройте данные POST в HTTP POST. Просто передайте объект.