Nswag: How to change the typescript template

Created on 1 Dec 2016  路  16Comments  路  Source: RicoSuter/NSwag

Hi all and thanks for all your efforts.

We're doing Angular2 with Typescript, and we want to use NSwag to generate our services. The point being we're using a HttpClient class which is a wrapper around Angular's Http service. We add our http headers and base endpoint url in HttpClient class as seemingly the interceptors are not supported in Angular2.

So we need to change the template for Typescript generation but I couldn't find a proper way to do that in NSwagStudio. Is there any way to customize the generated code to fit our framework?

Thanks

done enhancement

Most helpful comment

I've added more options: ClientBaseClass, UseTransformOptionsMethod and UseTransformResultMethod

image

Output:

import 'rxjs/Rx'; 
import {Observable} from 'rxjs/Observable';
import {Injectable, Inject, Optional, OpaqueToken} from '@angular/core';
import {Http, Headers, Response, RequestOptionsArgs} from '@angular/http';

export const API_BASE_URL = new OpaqueToken('API_BASE_URL');

export class Foo {
    protected transformOptions(options: RequestOptionsArgs) {
        return options; 
    }

    protected transformResult(url: string, response: Response, processor: (response: Response) => any) {
        return processor(response);
    }
}

@Injectable()
export class EventsClient extends Foo {
    private http: Http = null; 
    private baseUrl: string = undefined; 
    protected jsonParseReviver: (key: string, value: any) => any = undefined;

    constructor(@Inject(Http) http: Http, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
        this.http = http; 
        this.baseUrl = baseUrl ? baseUrl : ""; 
    }

    create(event: Event): Observable<number> {
        let url_ = this.baseUrl + "/api/Events";

        const content_ = JSON.stringify(event ? event.toJS() : null);

        return this.http.request(url_, this.transformOptions({
            body: content_,
            method: "post",
            headers: new Headers({
                "Content-Type": "application/json; charset=UTF-8"
            })
        })).map((response) => {
            return this.transformResult(url_, response, (response) => this.processCreate(response));
        }).catch((response: any, caught: any) => {
            if (response instanceof Response) {
                try {
                    return Observable.of(this.transformResult(url_, response, (response) => this.processCreate(response)));
                } catch (e) {
                    return <Observable<number>><any>Observable.throw(e);
                }
            } else
                return <Observable<number>><any>Observable.throw(response);
        });
    }

Foo is defined in extension code

All 16 comments

I am not familiar with Angular2, but I believe extension classes would help you here. I am using it with the Fetch template in order to customize the generated client classes.

Thanks for your reply. That's a cool feature but is there any way to make it global? so that we wouldn't have to extend every single Client class.

I'm not sure, it doesn't appear to support that currently but @rsuter will be able to confirm. That could be useful.

Not sure if this helps, but I currently extend each client class but share logic (i.e. a function injectOptions to transform request headers) across each, so the extension classes are very small. Then I aggregate all client classes into a larger class (i.e. Api) for further control into how each class is initialized.

Yep, That could be the way to go. But I'd rather using a cleaner method.
I reckon using Http interceptors for Angular2 (through 3rd party npm packages) would be a better approach.
Thanks.

I've added more options: ClientBaseClass, UseTransformOptionsMethod and UseTransformResultMethod

image

Output:

import 'rxjs/Rx'; 
import {Observable} from 'rxjs/Observable';
import {Injectable, Inject, Optional, OpaqueToken} from '@angular/core';
import {Http, Headers, Response, RequestOptionsArgs} from '@angular/http';

export const API_BASE_URL = new OpaqueToken('API_BASE_URL');

export class Foo {
    protected transformOptions(options: RequestOptionsArgs) {
        return options; 
    }

    protected transformResult(url: string, response: Response, processor: (response: Response) => any) {
        return processor(response);
    }
}

@Injectable()
export class EventsClient extends Foo {
    private http: Http = null; 
    private baseUrl: string = undefined; 
    protected jsonParseReviver: (key: string, value: any) => any = undefined;

    constructor(@Inject(Http) http: Http, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
        this.http = http; 
        this.baseUrl = baseUrl ? baseUrl : ""; 
    }

    create(event: Event): Observable<number> {
        let url_ = this.baseUrl + "/api/Events";

        const content_ = JSON.stringify(event ? event.toJS() : null);

        return this.http.request(url_, this.transformOptions({
            body: content_,
            method: "post",
            headers: new Headers({
                "Content-Type": "application/json; charset=UTF-8"
            })
        })).map((response) => {
            return this.transformResult(url_, response, (response) => this.processCreate(response));
        }).catch((response: any, caught: any) => {
            if (response instanceof Response) {
                try {
                    return Observable.of(this.transformResult(url_, response, (response) => this.processCreate(response)));
                } catch (e) {
                    return <Observable<number>><any>Observable.throw(e);
                }
            } else
                return <Observable<number>><any>Observable.throw(response);
        });
    }

Foo is defined in extension code

You can try this with the artifacts from the latest CI build: https://ci.appveyor.com/project/rsuter/nswag-25x6o/build/artifacts

Looks great! I will test it out soon.

Nice one! Thanks man!

Is this ok for your scenario? Release soon... I have to do more tests first...

Thanks rsuter, it works for me. The only drawback is that I need to add a reference to my base class from the auto generated code and I don't want to touch the generated file. So it would be nicer to generate the base class and add a reference to the file as well.

By the way, I'm having problem with generating files. It says access is denied which is not relevant. So at the moment I can't create the files, I need to copy paste from the NSwagStudio to my code. Maybe I'm missing something. I've attached my nswag file.

To avoid adding a reference, define the base class in ExtensionCode. This way it is generated into the same output file...

Just create a .ts file with your base class and select the file as "extension code" in nswagstudio.

You forgot to attach the file..

Ok I'll give it a try. Thanks mate!

@RSuter FYI: The signature for the transformResult function is not specific enough and needs a generic parameter to avoid compile errors in the generated code.

Use this signature instead in your extensions file:

protected transformResult<R, U>(url: string, response: R, processor: (response: R) => U) {

}

To avoid this :

image

Although this suggests there's something that needs changing in the template too - since we then lose the typing for response. In my case I was just setting up a jsonParseReviver in transformResult (to be global for all clients) so I didn't need to access 'Response'.

@simeyla thanks for the update. Wouldn't it be easier to use any instead of generics? Can you update the docs here? https://github.com/RSuter/NSwag/wiki/SwaggerToTypeScriptClientGenerator%3A-Angular

Was this page helpful?
0 / 5 - 0 ratings