Nswag: how to write TransformOptions method of Base class for angular 2 typescript client

Created on 1 Mar 2017  路  19Comments  路  Source: RicoSuter/NSwag

Hello,

My problem is linked to this issue #512
How do I implement the transform option for typescript for angular2. It seems to be different from angular 1 since we are using Observable. In the generated service I got this
return Observable.fromPromise(this.transformOptions(options_)).concatMap(transformedOptions_ => {;
so I assume that the function is expecting a promise.
This is the transformOptions method of my base class.

` protected transformOptions(options: any) {

    options.headers.Authorization = 'Bearer ' + localStorage.getItem(Const.authData.token);
    var promise = new Promise(function (resolve, reject) {
        resolve(options);
    });
    return promise;
}`

But it doesn't work. The bearer token are not passed to the http request.

When I inspect the final transformation, my headers is not the same as the one added per default.
image

My guess is that the headers are also observables and i'm not adding the authorization header correctly.
What is the best way please?

done bug

Most helpful comment

full sample (for latest CI build):

import * as generated from "./serviceClients";
import { RequestOptionsArgs } from '@angular/http'; // ignore

export class ClientBase {
    transformOptions(options: RequestOptionsArgs) {
        console.log("Options: " + JSON.stringify(options));
        options.headers.append("x-myheader", "myvalue");

        return Promise.resolve(options);
    }
}

All 19 comments

Hi @paddyfink

The way I did it was to create a baseclient.ts, with the following contents:

import { OAuthService } from 'angular-oauth2-oidc';
import globals = require('../app/globals');

class BaseClient {
    private oAuthService: OAuthService;

    constructor() {
        this.oAuthService = globals.injector.get(OAuthService);
    }

    transformOptions(options: RequestOptionsArgs): RequestOptionsArgs {
        options.headers = new Headers({
            'Content-Type': 'application/json; charset=UTF-8',
            'Authorization': this.oAuthService.authorizationHeader(),
            'Cache-Control': 'no-cache',
            'Pragma': 'no-cache'
        });
        return options;
    }
}

Then, reference this baseclient.ts in your swagger command, with: nswag swagger2tsclient /input:[yourSwaggerJson] /output:.[...]/apiclient.ts /template:angular2 /ClientBaseClass:BaseClient /ExtensionCode:[...]/baseclient.ts /UseTransformOptionsMethod:true

You should then find that your generated clients all extend the BaseClient class, and so will get their headers updated accordingly when you make an HTTP request.

Obviously in my example the authorization header is coming from the injector, whereas yours will be coming from localStorage. However, it should still work.

Theres a bug in the angular2 tpl: We must use map() instead of concatMap() in the generated method...

Btw: Ive updated the docu

Ok, it seems that it also works with concatMap(), but I changed it to flatMap() which fits better here...

@paddyfink try using

    options.headers.append("Authorization", "myvalue");

instead of

    options.headers.Authorization = "foobar";

then the header should be set (however it is lower cased).

Or use @Kitcheone solution...

full sample (for latest CI build):

import * as generated from "./serviceClients";
import { RequestOptionsArgs } from '@angular/http'; // ignore

export class ClientBase {
    transformOptions(options: RequestOptionsArgs) {
        console.log("Options: " + JSON.stringify(options));
        options.headers.append("x-myheader", "myvalue");

        return Promise.resolve(options);
    }
}

wahouh that was quick. Thanks @rsuter . If I reinstall nswagstudio I will get the fix?

Append should already be working with the current release

yep I confirm, it's working. Big thanks, you are the man

hi @rsuter, I have changed my client according to your commit but the http call is still no beeing called. The transformOptions method gets called and successfully adds the bearer token followed by returning a promise.

I've been struggling around with this and have no clue why I never reach the line return this.http.request(url_, transformedOptions_); after the .flatMap(transformedOptions_ => { instruction. (Using the synchronous implementation works as expected but not with the new async. way)

Could it be a version mismatch of the rxjs package? I'm currently using "rxjs": "^5.0.1" with angular 2.4.7.

Thanks for your help!

@jgoelten very strange, it works for me with rxjs 5.0.0-beta.12 and ^5.0.1 with concatMap and flatMap...

Can you try to add

import 'rxjs/add/operator/mergeMap';

at the top of the generated TS file?

Thanks for your advice @rsuter ! unfortunately it is still not working. The transformOptions method returns a promise and the calling Observable.fromPromise(...) results in a PromiseObservable (zoneawarepromise):

PromiseObservable
_isScalar:false
promise:ZoneAwarePromise
scheduler:undefined
__proto__:Observable

It seems as if the converted promise does not signal the flatMap about its finished state. As a result it never reaches the next state.

The following list shows my dependent packages with their corresponding versions. Do you have a working sample reference that I might use to compare the packages?

{
  "name": "angulartest",
  "version": "0.0.0",
  "scripts": {
    "test": "karma start ClientApp/test/karma.conf.js"
  },
  "dependencies": {
    "@angular/common": "2.4.7",
    "@angular/compiler": "2.4.7",
    "@angular/core": "2.4.7",
    "@angular/forms": "2.4.7",
    "@angular/http": "2.4.7",
    "@angular/platform-browser": "2.4.7",
    "@angular/platform-browser-dynamic": "2.4.7",
    "@angular/platform-server": "2.4.7",
    "@angular/router": "3.4.7",
    "@types/node": "^7.0.5",
    "angular2-platform-node": "~2.0.11",
    "angular2-template-loader": "^0.6.2",
    "angular2-universal": "^2.1.0-rc.1",
    "angular2-universal-patch": "^0.2.1",
    "angular2-universal-polyfills": "^2.1.0-rc.1",
    "angular": "^1.6.2",
    "aspnet-prerendering": "^2.0.3",
    "aspnet-webpack": "^1.0.17",
    "awesome-typescript-loader": "3.0.0-beta.13 || ^3.0.0",
    "bootstrap": "^3.3.7",
    "css": "^2.2.1",
    "css-loader": "^0.25.0",
    "es6-shim": "^0.35.1",
    "event-source-polyfill": "^0.0.9",
    "expose-loader": "^0.7.1",
    "extract-text-webpack-plugin": "^2.0.0-rc",
    "file-loader": "^0.9.0",
    "html-loader": "^0.4.4",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^2.2.1",
    "json-loader": "^0.5.4",
    "preboot": "^4.5.2",
    "raw-loader": "^0.5.1",
    "rxjs": "^5.1.1",
    "style-loader": "^0.13.1",
    "sha256": "^0.2.0",
    "to-string-loader": "^1.1.5",
    "typescript": "^2.2.1",
    "url-loader": "^0.5.7",
    "webpack": "^2.2.0",
    "webpack-hot-middleware": "^2.12.2",
    "webpack-merge": "^0.14.1",
    "zone.js": "^0.7.7"
  },
  "devDependencies": {
    "@types/chai": "^3.4.34",
    "@types/jasmine": "^2.5.37",
    "chai": "^3.5.0",
    "jasmine-core": "^2.5.2",
    "karma": "^1.3.0",
    "karma-chai": "^0.1.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-cli": "^1.0.1",
    "karma-jasmine": "^1.0.2",
    "karma-webpack": "^1.8.0"
  }
}

@jgoelten I've added a sample project here: https://github.com/NSwag/Samples/blob/master/src/SampleWebApiCoreAngular/ClientApp/app/components/app/app.component.ts (see ctor output)

Run build.bat to compile, then "dotnet run" (.NET Core development mode must be set first)

@jgoelten could you solve your problem?

@jgoelten please open a new issue if the problem persists...

@rsuter thanks to your sample I could solve the problem. It was a simple misunderstanding of the lazy observable behaviour. Thanks for your help!

@rsuter return Promise.resolve(options); solved my problem after upgrades.
Thanks a lot...

@Kitcheone's example mostly works. Except globals isn't a thing and I do not see any reasonable way to inject something into the base class.

The template doesn't even seem to offer any way to use config to add something for the class created to inject and pass to the base class.

@RicoSuter how do I do the same for HttpClient and Angular >6?
Could you please post working sample of code which changes the Headers in the options object.
I tried it like this
options = options.headers.append("Authorization", "myvalue");

But still I see only LazyUpdate property on the headers object.

@KokaChernov thats ok

Was this page helpful?
0 / 5 - 0 ratings