File

projects/storefrontlib/cms-structure/services/cms-features.service.ts

Description

Service responsible for resolving cms config based feature modules.

Index

Properties
Methods

Constructor

constructor(configInitializer: ConfigInitializerService, featureModules: FeatureModulesService)
Parameters :
Name Type Optional
configInitializer ConfigInitializerService No
featureModules FeatureModulesService No

Methods

Private createFeatureInstance
createFeatureInstance(moduleRef: NgModuleRef<any>, feature: string)

Create feature instance from feature's moduleRef

Parameters :
Name Type Optional
moduleRef NgModuleRef<any> No
feature string No
Returns : FeatureInstance
getCmsMapping
getCmsMapping(componentType: string)

Return full CmsComponent mapping defined in feature module

Parameters :
Name Type Optional
componentType string No
Returns : Observable<CmsComponentMapping | undefined>
getModule
getModule(componentType: string)

Resolves feature module for provided component type

Parameters :
Name Type Optional
componentType string No
hasFeatureFor
hasFeatureFor(componentType: string)

Check if there is feature module configuration that covers specified component type

Parameters :
Name Type Optional
componentType string No
Returns : boolean
Private initFeatureMap
initFeatureMap()
Returns : void
Private resolveFeatureConfiguration
resolveFeatureConfiguration(featureInjector: Injector)

Returns configuration provided in feature module

Parameters :
Name Type Optional
featureInjector Injector No
Returns : CmsConfig
Private resolveFeatureInstance
resolveFeatureInstance(featureName: string)

Resolve feature based on feature name, if feature was not yet resolved

It will first resolve all module dependencies if defined

Parameters :
Name Type Optional
featureName string No
Returns : Observable<FeatureInstance>

Properties

Private componentFeatureMap
Type : Map<string | string>
Default value : new Map()
Private featureInstances
Type : Map<string | Observable<FeatureInstance>>
Default value : new Map()
Private Optional featureModulesConfig
Type : literal type
import { Injectable, InjectFlags, Injector, NgModuleRef } from '@angular/core';
import {
  CMSComponentConfig,
  CmsComponentMapping,
  CmsConfig,
  ConfigChunk,
  ConfigInitializerService,
  deepMerge,
  DefaultConfigChunk,
  FeatureModuleConfig,
  FeatureModulesService,
} from '@spartacus/core';
import { defer, Observable, of } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';

interface FeatureInstance extends FeatureModuleConfig {
  moduleRef?: NgModuleRef<any>;
  componentsMappings?: CMSComponentConfig;
}

/**
 * Service responsible for resolving cms config based feature modules.
 */
@Injectable({
  providedIn: 'root',
})
export class CmsFeaturesService {
  // feature modules configuration
  private featureModulesConfig?: {
    [featureName: string]: FeatureModuleConfig | string;
  };

  // maps componentType to feature
  private componentFeatureMap: Map<string, string> = new Map();

  /*
   * Contains either FeatureInstance or FeatureInstance resolver for not yet
   * resolved feature modules
   */
  private featureInstances: Map<string, Observable<FeatureInstance>> =
    new Map();

  constructor(
    protected configInitializer: ConfigInitializerService,
    protected featureModules: FeatureModulesService
  ) {
    this.initFeatureMap();
  }

  private initFeatureMap(): void {
    this.configInitializer
      .getStable('featureModules')
      .subscribe((config: CmsConfig) => {
        this.featureModulesConfig = config.featureModules ?? {};

        for (const [featureName, featureConfig] of Object.entries(
          this.featureModulesConfig
        )) {
          if (
            typeof featureConfig !== 'string' &&
            featureConfig?.module &&
            featureConfig?.cmsComponents?.length
          ) {
            for (const component of featureConfig.cmsComponents) {
              this.componentFeatureMap.set(component, featureName);
            }
          }
        }
      });
  }

  /**
   * Check if there is feature module configuration that covers specified
   * component type
   */
  hasFeatureFor(componentType: string): boolean {
    return this.componentFeatureMap.has(componentType);
  }

  /**
   * Return full CmsComponent mapping defined in feature module
   */
  getCmsMapping(
    componentType: string
  ): Observable<CmsComponentMapping | undefined> {
    const feature = this.componentFeatureMap.get(componentType);

    if (!feature) {
      return of(undefined);
    }

    return this.resolveFeatureInstance(feature).pipe(
      map(
        (featureInstance) => featureInstance.componentsMappings?.[componentType]
      )
    );
  }

  /**
   * Resolves feature module for provided component type
   *
   * @param componentType
   */
  getModule(componentType: string): NgModuleRef<any> | undefined {
    const feature = this.componentFeatureMap.get(componentType);

    if (!feature) {
      return undefined;
    }

    let module;

    // we are returning injectors only for already resolved features
    this.featureInstances
      .get(feature)
      ?.subscribe((featureInstance) => {
        module = featureInstance.moduleRef;
      })
      .unsubscribe();
    return module;
  }

  /**
   * Resolve feature based on feature name, if feature was not yet resolved
   *
   * It will first resolve all module dependencies if defined
   */
  private resolveFeatureInstance(
    featureName: string
  ): Observable<FeatureInstance> {
    return defer(() => {
      if (!this.featureInstances.has(featureName)) {
        this.featureInstances.set(
          featureName,
          this.featureModules.resolveFeature(featureName).pipe(
            map((moduleRef) =>
              this.createFeatureInstance(moduleRef, featureName)
            ),
            shareReplay()
          )
        );
      }

      return this.featureInstances.get(featureName);
    });
  }

  /**
   * Create feature instance from feature's moduleRef
   */
  private createFeatureInstance(
    moduleRef: NgModuleRef<any>,
    feature: string
  ): FeatureInstance {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    const featureConfig = this.featureModulesConfig![
      feature
    ] as FeatureModuleConfig;

    const featureInstance: FeatureInstance = {
      moduleRef,
      componentsMappings: {},
    };

    // resolve configuration for feature module
    const resolvedConfiguration = this.resolveFeatureConfiguration(
      moduleRef.injector
    );

    // extract cms components configuration from feature config
    for (const componentType of featureConfig.cmsComponents ?? []) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      featureInstance.componentsMappings![componentType] =
        resolvedConfiguration.cmsComponents?.[componentType] ?? {};
    }
    return featureInstance;
  }

  /**
   * Returns configuration provided in feature module
   */
  private resolveFeatureConfiguration(featureInjector: Injector): CmsConfig {
    // get config chunks from feature lib
    const featureConfigChunks = featureInjector.get<any[]>(
      ConfigChunk,
      [],
      InjectFlags.Self
    );
    // get default config chunks from feature lib
    const featureDefaultConfigChunks = featureInjector.get<any[]>(
      DefaultConfigChunk,
      [],
      InjectFlags.Self
    );

    return deepMerge(
      {},
      ...(featureDefaultConfigChunks ?? []),
      ...(featureConfigChunks ?? [])
    ) as CmsConfig;
  }
}

result-matching ""

    No results matching ""