如何在角度延迟加载微前端中传递服务

问题描述 投票:0回答:1

我有一个微前端架构。其中,我有一个名为 central 的应用程序,并且我还有其他微前端,它们在 central 路由模块中定义为路由。所有 mfe 都从该 central 加载,并且该 central 应用程序在其 app.module.ts

中具有名为 LoginService 的服务

下面是central如何通过其路由加载其他微前端的示例

  {
    feature: featuresEnum.USER_OVERVIEW,
    props: {
      path: 'users',
      loadChildren: () => loadRemoteModule(getConfig('useroverview')).then(({ AppModule }) => AppModule),
      canActivate: [() => inject(RouteGuardService).canActivatePath({ permittedUse: permittedUseCasesEnum.MGMT })],
    },
  },
  {
    feature: featuresEnum.CONTACT,
    props: {
      path: 'user',
      loadChildren: () => loadRemoteModule(getConfig('contact')).then(({ ContactModule }) => ContactModule),
    },
  },
  {
    feature: featuresEnum.ADMINPANEL,
    props: {
      path: 'adminpanel',
      loadChildren: () => loadRemoteModule(getConfig('adminpanel')).then(({ AppModule }) => AppModule),
    },
  },
  {
    feature: featuresEnum.PERMISSIONS,
    props: {
      path: 'permissions',
      loadChildren: () => loadRemoteModule(getConfig('permissions')).then(({ UserPermissionsModule }) => UserPermissionsModule),
    },
  },
  {
    feature: featuresEnum.MFA,
    props: {
      path: 'mfa',
      loadChildren: () => loadRemoteModule(getConfig('mfa')).then(({ MfaModule }) => IModule),
      canMatch: [() => inject(RouteGuardService).canActivatePath({ permittedUse: permittedUseCasesEnum.MFA })],
    },
  },
  {
    feature: featuresEnum.PROFILE_INFORMATION,
    props: {
      path: 'profile',
      loadChildren: () =>
        loadRemoteModule(getConfig('profileinformation')).then(({ AppModule }) => AppModule),
      canMatch: [() => inject(RouteGuardService).canActivatePath({ permittedUse: permittedUseCasesEnum.USER_MGMT })],
    },
  },
];

现在,这个路由文件被称为ia.routing.module.ts

然后在名为 ia.module.ts 的文件中使用它,如下所示

  declarations: [IaComponent, NavigationComponent, LoginComponent, ProfileComponent, ErrorPageComponent],
  imports: [
    CommonModule,
    IaRoutingModule,
    TranslateModule.forChild(TRANSLATIONMODULE_CONFIG),
    BreadCrumbNavigationModule,
    IasApiModule.forRoot(environment.env),
    PfwApiModule.forRoot(environment.env),
    SharedDataStoreModule,
    EffectsModule.forRoot([]),
    StoreDevtoolsModule.instrument({ maxAge: 25 , connectInZone: true}),
  ],
  providers: [
    {
      provide: ToggleService,
      useFactory: () => new ToggleService(environment.env),
    },
    {
      provide: ENVIRONMENT,
      useValue: environment.env,
    }
    ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  bootstrap: [IamComponent],
})
export class IaModule {
}

然后,这个 IaModule 在 appModule 中使用,其中名为 LoginService 的服务在providers数组中提供,如下所示

  declarations: [AppComponent],
  providers: [
    LoginService,
    PermittedUseCasesService,
    AuthService,
    {
      provide: OAuthStorage,
      useValue: sessionStorage,
    },
    TranslatePipe
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    OAuthModule.forRoot(),
    TranslateModule.forRoot(),
    StoreModule.forRoot({}),
    RouterModule.forRoot([
      {
        path: 'ia',
        loadChildren: () => import('./ia.module').then((m) => m.IaModule),
        resolve: {
          loginService: LoginService,
          permittedUseCasesService: PermittedUseCasesService
        },
      },
    ]),
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  bootstrap: [AppComponent],
})
export class AppModule {

}

现在,这是暴露所有其他微叶的central应用程序。所有微前端都可以通过此 shell 通过前缀 /ia 路径进行访问。因此,如果 mfe 名称是 profile,则路径将为 /ia/profile。现在,在配置文件微前端中,我定义如下所示的路线

  {
    path: '',
    component: ProfileComponent,
  },
  {
    path: '',
    pathMatch: 'full',
    redirectTo: 'profile',
  },
  {
    path: '**',
    redirectTo: 'profile',
  },
];
@NgModule({
  declarations: [ProfileComponent],
  imports: [
    CommonModule,
    RouterModule.forChild(routes),
    TranslateModule.forRoot(TRANSLATIONMODULE_CONFIG),
    SharedModule,
    UnidApiModule.forRoot(environment.env),
    PfwApiModule.forRoot(environment.env),
  ],
  exports: [RouterModule],
  providers: [
    LoginService,
    {
      provide: FeatureToggleService,
      useFactory: () => new FeatureToggleService(environment.env),
    },
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class ProfileModule {
}

像下面这样延迟加载

  {
    path: '',
    component: AppComponent,
    children: [
      {
        path: '',
        loadChildren: () => import('./profile.module').then((m) => m.ProfileModule),
      },
    ],
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],

})
export class AppRoutingModule {}

然后在 Profile Microfrontend 的 AppModule 中导入 AppRoutingModule ,如下所示

  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    SharedModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

现在,在 Profile 模块中,我无法访问 LoginService。它似乎尚未初始化,并且我用于 LoginService 的函数得到空结果。如果我不延迟加载配置文件微前端,我可以访问 LoginService。值得一提的是,我无权访问 LoginService 源代码。这是我正在使用的 npm 依赖项。 LoginService 中有一个解析函数,如下所示

resolve(): Observable<LoginService>;

我尝试为它创建一个解析器,并在我的个人资料微前端的应用程序路由模块中使用它,如下所示,但它不起作用,并且这样做,我什至无法在浏览器中加载个人资料微前端,它只是卡住了,这使得感觉登录服务未解决

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { LoginService } from '@thirdparty/loginService';

@Injectable({
  providedIn: 'root'
})
export class LoginResolver implements Resolve<LoginService> {
  constructor(private loginService: LoginService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<LoginService> {
    return this.loginService.resolve();
  }
}

使用登录解析器

{
    path: '',
    component: AppComponent,
    children: [
      {
        path: '',
        loadChildren: () => import('./profile.module').then((m) => m.ProfileModule),
resolve: {
          loginService: LoginResolver
        }
      },
    ],
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],

})
export class AppRoutingModule {}
angular module lazy-loading micro-frontend angular-module-federation
1个回答
0
投票

您已经拥有 LoginService 作为某个图书馆的公民,这很好。模块联合的特点之一是它可以为不同的应用程序解析和重用相同的库。即,如果 shell 应用程序从“X”导入 LoginService,应用程序应该轻松地重用库(和服务)的完全相同的实例。

注射对您不起作用可能有不同的问题。让我们从最简单的开始,希望其他人不要申请。

您在配置文件模块中提供相同模块(和服务)的实例:

@NgModule({
  ...
  imports: [
    ...
    RouterModule.forChild(routes), // good forChild is used, and not forRoot
    TranslateModule.forRoot(TRANSLATIONMODULE_CONFIG), // this overrides TranslateModule. could be a problem, but depends on how related are shell and profile module translations
    SharedModule, // most likely an issue. all providers will be provided again
    UnidApiModule.forRoot(environment.env),  // most likely an issue. all providers will be provided again
    PfwApiModule.forRoot(environment.env), // most likely an issue. all providers will be provided again
  ],
  providers: [
    LoginService, // definetly a problem. It is overriding the provider and creating another copy
    {
      provide: FeatureToggleService,
      useFactory: () => new FeatureToggleService(environment.env),
    }, // most likely a problem. It is overriding the provider and creating another copy
  ],
})
export class ProfileModule {
}

重复的提供程序很可能对应用程序不利,因为在 ProfileModule 的子树中,将使用所有重新提供的服务的新的唯一实例。您应该从提供者数组中删除 LoginService(可能还有其他标记的模块)。这样你就能够以最简单的方式注入 LoginService,而不需要任何技巧。

class AnyComponentOrService {
  constructor(private loginService: LoginService) {}
}

此提供程序和

.forRoot
调用应在配置文件应用程序的 AppModule 中说明。 AppModule 的存在是为了“模拟 shell”,因此单独开发配置文件应用程序会更容易。大多数情况下,在实际部署环境中生成的 Frankenstein 联合应用程序中根本不使用此 AppModule。

乍一看固定的 ProfileModule 应该是这样的:

@NgModule({
  declarations: [ProfileComponent],
  imports: [
    CommonModule,
    RouterModule.forChild(routes),
    TranslateModule.forRoot(TRANSLATIONMODULE_CONFIG), // not sure if it should be here
    SharedModule,
  ],
  exports: [RouterModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class ProfileModule {
}

另一个问题是库的配置错误或版本不正确。这可能会导致运行时中存在同一库的不同实例。它还会导致无法在配置文件应用程序中注入 LoginService,即使它已经在 shell 中提供了

© www.soinside.com 2019 - 2024. All rights reserved.