从 Router 加载 URL 以在 NgModule 中使用

Load URL from Router for use in NgModule

我正在设置 blue/green 部署,并试图根据用户当前正在查看的 url (redirectUri: this.router.url + '/callback',) 更改下面的 redirectUri。我收到具有以下配置的 Uncaught TypeError: Cannot read property 'router' of undefined

import { APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { CoreModule } from './@core/core.module';
import { AuthGuard } from './auth-guard.service';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ThemeModule } from './@theme/theme.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { NbOAuth2AuthStrategy,
        NbAuthModule,
        NbOAuth2ResponseType,
        NbOAuth2GrantType,
        NbAuthOAuth2Token,
       } from '@nebular/auth';
import { OAuth2LoginComponent } from './auth/oauth2-login.component';
import { OAuth2CallbackComponent } from './auth/oauth2-callback.component';
import { environment } from '../environments/environment';
import { Router } from '@angular/router';

@NgModule({
  declarations: [AppComponent, OAuth2LoginComponent, OAuth2CallbackComponent ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    NgbModule.forRoot(),
    ThemeModule.forRoot(),
    CoreModule.forRoot(),
    NbAuthModule.forRoot({
      forms: {},
      strategies: [
        NbOAuth2AuthStrategy.setup({
          baseEndpoint: environment.authUrl,
          name: 'cognito',
          clientId: environment.clientId,
          authorize: {
            endpoint: '/oauth2/authorize',
            responseType: NbOAuth2ResponseType.CODE,
            scope: 'aws.cognito.signin.user.admin',
            redirectUri: this.router.url + '/callback',
          },
          redirect: {
            success: '/pages/dashboard',
          },
          token: {
            endpoint: '/oauth2/token',
            grantType: NbOAuth2GrantType.AUTHORIZATION_CODE,
            class: NbAuthOAuth2Token,
            redirectUri: this.router.url + '/callback',
          },
          refresh: {
            endpoint: 'refresh-token',
            grantType: NbOAuth2GrantType.REFRESH_TOKEN,
          },
        }),
       ],
    }),
    AppRoutingModule,
  ],
  bootstrap: [AppComponent],
  providers: [
    AuthGuard,
    { provide: APP_BASE_HREF, useValue: '/' },
  ],
})
export class AppModule {
  constructor(private router: Router) {}
}

我也尝试过使用 redirectUri: window.location.origin + '/callback',它在本地工作,但在为生产构建时为 null。

请注意,class level decorators 在创建 class 的任何实例之前应用于构造函数。因此路由器 属性 不可用于装饰器。例子中的this.router.url + '/callback'反而引用了全局的this,奇怪的是没有编译错误。

关于window.location,在aot编译模式下,这是prod构建的默认模式,装饰器中的表达式在编译时由Angular编译器执行,所以window.location 在那里不可用。看看这个 GitHub 问题:AOT replaces window.location object to null

作为解决方法,您可以动态初始化 NbOAuth2AuthStrategy,例如:

@NgModule({
  imports: [
    ...
    NbAuthModule.forRoot({
      strategies: [
        NbOAuth2AuthStrategy.setup({
          name: 'cognito'
        })
      ],
      ...
    })
  ],
  ...
})
export class AppModule {
  constructor(
    authService: NbAuthService, // force construction of the auth service
    oauthStrategy: NbOAuth2AuthStrategy
  ) {
    // window.location should be available here
    this.oauthStrategy.setOpitions({
      name: 'cognito',
      ...
    });
  }
}

我发现,将 NbAuthService 添加到构造函数参数以及 NbOAuth2AuthStrategy 很重要。看来service在构造的时候就初始化了strategy,所以应该在strategy初始化之前先构造一下。

另请注意,setOptions() 方法完全覆盖了模块装饰器的选项,因此整个策略初始化应从装饰器移至构造器。

我还发现了 this GitHub 问题,这帮助我找到了正确的解决方案。

如果你想做类似的事情,你可以使用注入令牌并创建一个 returns 你想要的值的工厂函数。这将是 运行 在浏览器中,您将看到您想要的值。

const REDIRECT_URI = new InjectionToken('REDIRECT_URI');

export function redirectUriFactory {
  return `${location.protocol}/${location.host}/callback`
}
    @NgModule(...)
    class MyModule {
      forRoot() {
        return {
          ngModule: MyModule,
          providers: [
            { provide: REDIRECT_URI, useFactory: redirectUriFactory }
          ]
        }
      }
    }

我没有测试这个,但是 InjectionToken with Factory 是进入 AOT 的必经之路

更多信息https://github.com/angular/angular-cli/issues/10957

在您提供的代码示例中,您似乎试图在实例化 class 'AppModule' 之前在 @NgModule 注释中访问路由器对象 this.router .

实例变量在注释中不可用。 Angular 使用 [依赖注入][1] 通过构造函数提供对象实例。

看你的情况,虽然我不太了解 NbOAuth2AuthStrategy 模块,但你可以在 import 中定义模块后从构造函数中寻找配置身份验证策略的选项@NgModule 注释部分。考虑这个代码片段,它可能对你有帮助。在下面用 << >>

标记的代码中查找占位符

    NbAuthModule.forRoot({
        forms: {},
      strategies: [
        NbOAuth2AuthStrategy.setup({
          baseEndpoint: environment.authUrl,
          name: 'cognito',
          clientId: environment.clientId,
          authorize: {
            endpoint: '/oauth2/authorize',
            responseType: NbOAuth2ResponseType.CODE,
            scope: 'aws.cognito.signin.user.admin',
            redirectUri: '<<SET SOME DEFAULT URL>>',
          },
          redirect: {
            success: '/pages/dashboard',
          },
          token: {
            endpoint: '/oauth2/token',
            grantType: NbOAuth2GrantType.AUTHORIZATION_CODE,
            class: NbAuthOAuth2Token,
            redirectUri: '<<SET SOME DEFAULT URL>>',
          },
          refresh: {
            endpoint: 'refresh-token',
            grantType: NbOAuth2GrantType.REFRESH_TOKEN,
          },
        }),
       ...


    export class AppModule {
      constructor(private router: Router) {
        <<Reconfigure your NbOAuth2AuthStrategy>>
        NbOAuth2AuthStrategy.setup....
      }
    }


Hope the solution works for you.



  [1]: https://angular.io/guide/dependency-injection

您的解决方案将不起作用,因为正如 Valeriy Katkov 所提到的,class 级别装饰器在创建 class 的任何实例之前应用于构造函数。因此,您将无法将路由器注入装饰器。

为了能够注入路由器,您需要将您的实现移动到 class 中。可以通过 NbAuthStrategy 实例的 setOptions 方法,但是,有一些问题需要克服,例如 here or here。 为了使其工作,您应该将策略配置移动到扩展 NbAuthComponent(这很重要)的组件,例如:

export class AppComponent extends NbAuthComponent {

  constructor(auth: NbAuthService,
              location: Location,
              private router: Router,
              authStrategy: NbPasswordAuthStrategy) {
    super(auth, location);

    authStrategy.setOptions({
      name: 'username',
      login: {
        alwaysFail: false,
        endpoint: 'test',
        method: 'post',
        requireValidToken: false,
        redirect: {
          success: this.router.url + '/success-callback',
          failure: this.location.path() + '/callback'
        },
        defaultErrors: ['Damn'],
        defaultMessages: ['Great'],
      },
    });
  }
}

此外,我建议您使用 this.location.path() 而不是 this.router.url,因为 this.router.url 不会为您提供您所在的 url,而是 url的组件级别。 this.location.path() 将为您提供您所在页面的完整路径。

这是一个 StackBlitz example 的有效解决方案。

请让我知道您是否仍然不清楚或需要一些额外的细节。