OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.45k stars 6.49k forks source link

[REQ] Support for Angular Standalone Components / Module Less #17031

Open RaphaelJenni opened 10 months ago

RaphaelJenni commented 10 months ago

Is your feature request related to a problem? Please describe.

Angular released standalone components a while back. Now, with Angular 17, standalone components are the new default. Unfortunately, the typescript-angular generator still creates modules.

Describe the solution you'd like

The typescript-angular generator should get support for standalone components. For version 17+, this should be activated by default; for older versions, it should not.

Describe alternatives you've considered

There are no real alternatives to it.

Additional context

cromefire commented 9 months ago

There are no real alternatives to it.

No, this is just wrong, especially for something that just provides a bunch of services and no actually components that could actually be standalone... It's called standalone components after all not standalone services...

I mean there's no issue with using modules for the generated code in your standalone components project. Angular Core uses them, angular material uses them and so on. Even that "activated by default" just means that angular's code generator will generate new components as standalone and it will generate an entry point without a module, not that modules are disabled or any behavior of the system itself is changed at all.

The one issue that's actually there is that the provideIn: "root" doesn't seem to work and that should be provideIn: AppModule I think because otherwise the DI doesn't find the HttpClient it seems.

cromefire commented 9 months ago

Just for fun I tried to run the standalone migration on the client for fun and that migration just results in:

>  NX  Generating @angular/core:standalone-migration

SchematicsException [Error]: Could not find any files to migrate under the path libs/api-client/. Cannot run the standalone migration.
    at SchematicImpl.<anonymous> (node_modules/@angular/core/schematics/ng-generate/standalone-migration/bundle.js:47955:13)
    at Generator.next (<anonymous>)
    at fulfilled (node_modules/@angular/core/schematics/ng-generate/standalone-migration/bundle.js:49:24)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

 >  NX   Could not find any files to migrate under the path libs/api-client/. Cannot run the standalone migration.

   Pass --verbose to see the stacktrace.

and even the migration itself says there's nothing to migrate...

cromefire commented 9 months ago

Actually the providedIn using the AppModule isn't even nessecary, you just seem to need to add importProvidersFrom(HttpClientModule) to your app configuration providers in the main.ts or wherever it is for you. Providing via the module would still be nice for lazy loading though.

bakcsa83 commented 6 months ago

In one of my code base I have something like this:

app.module.ts:

export function apiConfigFactory(): Configuration {
  const params: ConfigurationParameters = {
    basePath: environment.apiBasePath,
  };
  return new Configuration(params);
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ApiModule.forRoot(apiConfigFactory)
  ],
  providers: [
  ],
  bootstrap: [AppComponent]
})

Notice that I override the ApiModule configuration with an own config that sets the basePath based on an environment property.

I've tried this in app.config.ts but does not work:

export const appConfig: ApplicationConfig = {
  providers: [provideAnimations(),provideHttpClient(),provideRouter(routes),importProvidersFrom([ApiModule.forRoot(apiConfigFactory)])]
};
export function apiConfigFactory(): Configuration {
  const params: ConfigurationParameters = {
    basePath: environment.apiBasePath,
  };
  return new Configuration(params);
}

Could you advise how to make this work with a standalone component setup?

cromefire commented 6 months ago

I've tried this in app.config.ts but does not work:

export const appConfig: ApplicationConfig = {
  providers: [provideAnimations(),provideHttpClient(),provideRouter(routes),importProvidersFrom([ApiModule.forRoot(apiConfigFactory)])]
};
export function apiConfigFactory(): Configuration {
  const params: ConfigurationParameters = {
    basePath: environment.apiBasePath,
  };
  return new Configuration(params);
}

Could you advise how to make this work with a standalone component setup?

I think you still have to import it in the component itself, but you don't need to configure it again as far as I know (like the router for example). But I don't know all the quirks yet as I don't have many projects right now with standalone components.

bakcsa83 commented 6 months ago

My bad, it is actually working. No extra import is need, just the above snippet in the app.config.ts.

However, the importProvidersFrom() is just a workaround for bringing in "module based" libraries, isn't it? The proper way would be to implement a method like provideHttpClient() or provideRouter(routes).

cromefire commented 6 months ago

However, the importProvidersFrom() is just a workaround for bringing in \"module based\" libraries, isn't it? The proper way would be to implement a method like provideHttpClient() or provideRouter(routes).

Well the provide methods just do the same thing as the module, just manually, so I don't think you get any benefit from doing it that way pretty much: https://github.com/angular/angular/blob/17.3.0/packages/router/src/provide_router.ts#L53-L100

So yeah, you can generate one for the client and if someone wants to do that, sure, but don't really expect any benefit. Angular libraries are all basically module based and I don't see that changing as modules are build exactly for this: managing a collection of connected components and services. There's nothing really wrong with them and you're free to mix and match them if you like, even in the same project. Angular even still assumes you are using modules to provide services: https://angular.io/guide/standalone-components#standalone-injectors

xfh commented 5 months ago

The module is not needed, one just needs to provide Configuration.

To get a more consistent feel with the provider functions API, I've simply added a api.provider.ts file with the following content:

export function withBackendApiConfiguration(configurationParameters: ConfigurationParameters = {}): Configuration {
    return new Configuration({
        // any default parameters
        basePath: 'my-default-api',
        // overrides
        ...configurationParameters,
    });
}

export function provideApi(withConfiguration:  Configuration = withBackendApiConfiguration()): EnvironmentProviders {
    return makeEnvironmentProviders([
        {provide: Configuration, useValue: withConfiguration},
    ]);
}

usage in app.config.ts (with the provideHttpClien` function, as an example for other a similar angular api):

provideApi(
    withBackendApiConfiguration({
        basePath: 'some-other-api',
    }),
),
provideHttpClient(
    withXsrfConfiguration({
        cookieName: XSRF_COOKIE_NAME,
        headerName: XSRF_HEADER_NAME,
    }),
    withInterceptorsFromDi(),
    withInterceptors([
        authenticationInterceptor(
            withRetryOnLoginSucceess(),
            withNotifyNotAuthorised(),
        ),
        // must run *after* AUTHENTICATION_INTERCEPTOR so new Xsrf-Tokens get picked up by http retrys
        HTTP_XSRF_INTERCEPTOR,
        HTTP_ERROR_INTERCEPTOR,
        VERSION_INTERCEPTOR,
    ]),
),

You can even prevent the generation of the api.module.ts by removing the file and adding api.module.ts to .openapi-generator-ignore.

Pearseak commented 4 months ago
function authConfig(authService: OAuthService) {
  return async () => {
    authService.configure({
      issuer: environment.oAuthIssuer,
      redirectUri: environment.oAuthRedirectUri,
      clientId: environment.oAuthClientId,
      responseType: 'code',
      scope: 'openid'
    });
    await authService.loadDiscoveryDocumentAndTryLogin();
  }
}

function apiConfig(authService: OAuthService, api: "query" | "command" | "report"): QueryConfiguration | CommandConfiguration | ReportConfiguration {
  const credentials = {'Bearer': authService.getAccessToken()};
  if (api === 'query') return new QueryConfiguration({basePath: environment.queryApi, credentials});
  else if (api === 'command') return new CommandConfiguration({basePath: environment.commandApi, credentials});
  else if (api === 'report') return new ReportConfiguration({basePath: environment.reportApi, credentials});
  throw new Error("Invalid API type");
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection(),
    provideRouter(routes),
    provideHttpClient(),
    provideOAuthClient(),
    {provide: APP_INITIALIZER, useFactory: authConfig, multi: true, deps: [OAuthService]},
    {provide: QueryConfiguration, useFactory: (auth: OAuthService) => apiConfig(auth, 'query'), deps: [OAuthService], multi: false},
    {provide: CommandConfiguration, useFactory: (auth: OAuthService) => apiConfig(auth, 'command'), deps: [OAuthService], multi: false},
    {provide: ReportConfiguration, useFactory: (auth: OAuthService) => apiConfig(auth, 'report'), deps: [OAuthService], multi: false}
  ]
};

I use --additional-properties="apiModulePrefix=Query,configurationPrefix=Query,serviceFileSuffix=.query.service,serviceSuffix=Query" to support multiple api