web-dev-qa-db-fra.com

Analyser la date avec Angular 4.3 HttpClient

Je suis en train de passer au nouveau HttpClient de Angular 4.3. Un des avantages est que je peux spécifier une information de type sur la méthode GET et que le JSON renvoyé est analysé dans le type donné, par exemple.

this.http.get<Person> (url).subscribe(...)

Malheureusement, toutes les dates du fichier JSON sont analysées en tant que nombres dans l'objet résultant (probablement parce que les objets Java Date sont sérialisés en tant que nombres dans le backend).

Avec l'ancien Http, j'utilisais une fonction reviver pour appeler JSON.parse () comme ceci:

this.http.get(url)
  .map(response => JSON.parse(response.text(), this.reviver))

et dans la fonction reviver, j'ai créé des objets de date à partir des nombres:

reviver (key, value): any {
  if (value !== null && (key === 'created' || key === 'modified'))
    return new Date(value);

  return value;
}

Existe-t-il un mécanisme similaire avec le nouveau HttpClient? Ou quelle est la meilleure pratique pour effectuer la conversion lorsque le JSON est analysé?

20
Ralf Schneider

Malheureusement, il ne semble pas y avoir de moyen de passer un reviveur à la méthode JSON.parse qui est utilisée dans le Angular HttpClient. Voici le code source permettant d’appeler JSON.parse: https://github.com/angular/angular/blob/20e1cc049fa632e88dfeb00476455a41b7f42338/packages/common/http/src/xhr.ts#L189

Angular n'analysera la réponse que si le type de réponse est défini sur "json" (vous pouvez le voir à la ligne 183). Si vous ne spécifiez pas le type de réponse, alors Angular est remplacé par défaut par "json" ( https://github.com/angular/angular/blob/20e1cc049fa632e88dfeb00476455a41b7f42338/packages/common/ http/src/request.ts # L112 ).

Vous pouvez donc utiliser un type de réponse de "texte" et analyser le json vous-même. Ou vous pouvez simplement être paresseux et sérialiser puis désérialiser la réponse (JSON.parse(JSON.stringify(res.body), reviver)).

Plutôt que de modifier chaque appel, vous pouvez créer un intercepteur comme suit:

json-interceptor.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';

// https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L18
const XSSI_PREFIX = /^\)\]\}',?\n/;

/**
 * Provide custom json parsing capabilities for api requests.
 * @export
 * @class JsonInterceptor
 */
@Injectable()
export class JsonInterceptor implements HttpInterceptor {

  /**
   * Custom http request interceptor
   * @public
   * @param {HttpRequest<any>} req
   * @param {HttpHandler} next
   * @returns {Observable<HttpEvent<any>>}
   * @memberof JsonInterceptor
   */
  public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.responseType !== 'json') {
      return next.handle(req);
    }
    // convert to responseType of text to skip angular parsing
    req = req.clone({
      responseType: 'text'
    });

    return next.handle(req).map(event => {
      // Pass through everything except for the final response.
      if (!(event instanceof HttpResponse)) {
        return event;
      }
      return this.processJsonResponse(event);
    });
  }

  /**
   * Parse the json body using custom revivers.
   * @private
   * @param {HttpResponse<string>} res
   * @returns{HttpResponse<any>}
   * @memberof JsonInterceptor
   */
  private processJsonResponse(res: HttpResponse<string>): HttpResponse<any> {
      let body = res.body;
      if (typeof body === 'string') {
        const originalBody = body;
        body = body.replace(XSSI_PREFIX, '');
        try {
          body = body !== '' ? JSON.parse(body, (key: any, value: any) => this.reviveUtcDate(key, value)) : null;
        } catch (error) {
          // match https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L221
          throw new HttpErrorResponse({
            error: { error, text: originalBody },
            headers: res.headers,
            status: res.status,
            statusText: res.statusText,
            url: res.url || undefined,
          });
        }
      }
      return res.clone({ body });
  }

  /**
   * Detect a date string and convert it to a date object.
   * @private
   * @param {*} key json property key.
   * @param {*} value json property value.
   * @returns {*} original value or the parsed date.
   * @memberof JsonInterceptor
   */
  private reviveUtcDate(key: any, value: any): any {
      if (typeof value !== 'string') {
          return value;
      }
      if (value === '0001-01-01T00:00:00') {
          return null;
      }
      const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
      if (!match) {
          return value;
      }
      return new Date(value);
  }
}

Ensuite, vous devez le fournir dans votre module:

*. module.ts

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { JsonInterceptor } from '...';

@NgModule({
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: JsonInterceptor,
            multi: true
        }
    ]
})
...

Dans l'exemple, j'ai essayé d'imiter comment angular analysait autant que possible. Ils ne semblent pas exporter HttpJsonParseError, je ne pouvais donc pas attribuer l'erreur à ce type. Pas parfait, mais j’espère que l’idée sera transmise.

Voici un exemple en cours (regardez dans la console pour voir la date analysée): https://stackblitz.com/edit/json-interceptor

J'ai créé une demande de fonctionnalité ici: https://github.com/angular/angular/issues/21079

18
bygrace

Cela fonctionne pour moi:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ApiInterceptor implements HttpInterceptor {
  private dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)$/;

  private utcDateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;

  constructor() { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .do((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          this.convertDates(event.body);
        }
      });
  }

  private convertDates(object: Object) {
    if (!object || !(object instanceof Object)) {
      return;
    }

    if (object instanceof Array) {
      for (const item of object) {
        this.convertDates(item);
      }
    }

    for (const key of Object.keys(object)) {
      const value = object[key];

      if (value instanceof Array) {
        for (const item of value) {
          this.convertDates(item);
        }
      }

      if (value instanceof Object) {
        this.convertDates(value);
      }

      if (typeof value === 'string' && this.dateRegex.test(value)) {
        object[key] = new Date(value);
      }
    }
  }
}

L'avantage de ceci par rapport à la réponse de bygrace est que vous n'avez pas besoin d'analyser vous-même json, vous convertissez simplement les dates après angular est fait avec l'analyse .

Cela fonctionne également avec les tableaux et les objets imbriqués. J'ai modifié this il devrait supporter les tableaux.

14
Lerner

Vous pouvez toujours, mais vous devez importer l'opérateur map()- de rxjs comme ceci:

import 'rxjs/add/operator/map';

Ensuite, vous pouvez, comme Diego l’a fait remarquer, utiliser le map comme ceci:

return this.http.get<BlogPost>(url)
.map(x => {
x.published = new Date(String(x.published));
    return x;
})
[...]
6
Jonas Stensved

Vous pouvez utiliser:

this.http.get(url, { responseType: 'text' })
    .map(r => JSON.parse(r, this.reviver))
    .subscribe(...)
3
Diego Maninetti

Similaire à réponse de Jonas Stensved , mais en utilisant des tuyaux:

import { map } from "rxjs/operators";

this.http.get(url)
  .pipe(
    map(response => {
      response.mydate = new Date(response.mydate);
      return response;
    })

Notez la syntaxe d'importation différente de l'opérateur map.

Les pipes ont été introduites dans RxJS 5.5. Ils facilitent la gestion des importations, la lisibilité du code et réduisent la taille du paquet. Voir Comprendre les importations des opérateurs .

3
Markus Pscheidt