Activities of "JanneHarju"

I tried to replace all those APP_INITIALIZER and for some reason it seems that if fixed problem. After I changed every modules route.provider.ts to use provideAppInitializer I got error that says something like this "providers required eather Provider or Type and you provided [...,...,...,...]" . So i tried to spread those SOME_MODULE_ROUTE_PROVIDERS with ... and then I got almost same error. Then I removed spread ... and just removed array from all ROUTE_PROVIDERS that one route.provider.ts had. So result was like this in one of ROUTE_PROVIDERS:

import { eLayoutType, RoutesService } from '@abp/ng.core';
import { inject, provideAppInitializer } from '@angular/core';
import { eIntegrationRouteNames } from '../enums/route-names';

export const INTEGRATION_ROUTE_PROVIDERS = provideAppInitializer(() => {
  configureRoutes(inject(RoutesService));
});

export function configureRoutes(routes: RoutesService) {
  routes.add([
    {
      path: '/integration',
      name: eIntegrationRouteNames.Integration,
      iconClass: 'fas fa-book',
      parentName: 'AbpUiNavigation::Menu:Administration',
      requiredPolicy: 'Integration.Menu',
      layout: eLayoutType.application,
      order: 3,
    },
  ]);
}

So I only removed [ ]. Related ConfigModule stayed unmodified. After that all started to work for some reason. Do you have any clue what might be reason that this fixed my problem?

I still have styles broken and then I have some backend problem after update but they are another story.

Ok. Now I understand your question better. Here is example of how I'm using custom styles in styles.scss

@use 'kendo-theme.scss';
@use 'projects/theme-lepton/dist/global/styles/lepton7.min.css';

I started to use @use because @import was deprecated and my angular application didn't work if I wouldn't change to use @use. I was forced to make some changes where some scss variables were and how they were used, but after awile I think I succeeded.

Then I have some other custom styles in angular.json

"styles": [
              {
                "input": "node_modules/@swimlane/ngx-datatable/index.css",
                "inject": true,
                "bundleName": "ngx-datatable-index"
              },
              {
                "input": "node_modules/@swimlane/ngx-datatable/assets/icons.css",
                "inject": true,
                "bundleName": "ngx-datatable-icons"
              },
              {
                "input": "node_modules/@swimlane/ngx-datatable/themes/material.css",
                "inject": true,
                "bundleName": "ngx-datatable-material"
              },
              {
                "input": "node_modules/@fortawesome/fontawesome-pro/css/all.min.css",
                "inject": true,
                "bundleName": "fontawesome-all.min"
              },
              {
                "input": "node_modules/@fortawesome/fontawesome-pro/css/v4-shims.min.css",
                "inject": true,
                "bundleName": "fontawesome-v4-shims.min"
              },
              {
                "input": "node_modules/flag-icons/css/flag-icons.min.css",
                "inject": true,
                "bundleName": "flag-icons.min"
              },
              {
                "input": "node_modules/ng-zorro-antd/tree/style/index.min.css",
                "inject": false,
                "bundleName": "ng-zorro-antd-tree"
              },
              "src/styles.scss"
            ],

But how styles can affect how dynamic layout component is selecting which layout to use?

I noticed that in my code there is still some APP_INITIALIZER in use. I will try to refactor them to use new provideAppInitializer instead. Many of those places where APP_INITIALIZER is still in use is those route.provider.ts files where layout is set to each route.

Hello this is whole styles provider ts.

import {
  ConfigStateService,
  CONTENT_STRATEGY,
  CurrentCultureDto,
  DOM_STRATEGY,
  DomInsertionService,
  ReplaceableComponentsService,
  StyleContentStrategy,
} from '@abp/ng.core';
import { inject, Injector, provideAppInitializer, RendererFactory2 } from '@angular/core';
import { combineLatest, Observable, of } from 'rxjs';
import { distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
import { AccountLayoutComponent } from '../components/account-layout/account-layout.component';
import { ApplicationLayoutComponent } from '../components/application-layout/application-layout.component';
import { LEPTON_STYLE_ELEMENT_ID } from '../constants/lepton-constants';
import styles from '../constants/styles';
import { eThemeLeptonComponents } from '../enums/components';
import { LayoutStateService } from '../services/layout-state.service';
import { CUSTOM_STYLE } from '../tokens/custom-style.token';
import { EmptyLayoutComponent } from '../components';

export const LEPTON_THEME_STYLES_PROVIDERS = [
  provideAppInitializer(() => {
    let injector = inject(Injector);
    injectStyle(injector);
    initLeptonStyleHandler(injector);
    initLayouts(injector);
  }),
];

export function injectStyle(injector: Injector) {
  const rendererFactory = injector.get(RendererFactory2);
  const domInsertion = injector.get(DomInsertionService);

  rendererFactory
    .createRenderer(document.body, null)
    .addClass(document.body, 'abp-application-layout');
    const appStyles: HTMLElement = document.querySelector('link[rel="stylesheet"][href*="styles"]');
  let content: StyleContentStrategy;
  if (appStyles) {
    const domStrategy = DOM_STRATEGY.BeforeElement(appStyles);
    content = new StyleContentStrategy(styles, domStrategy, undefined, {
      id: LEPTON_STYLE_ELEMENT_ID,
    });
  } else {
    content = CONTENT_STRATEGY.AppendStyleToHead(styles, { id: LEPTON_STYLE_ELEMENT_ID });
  }

  domInsertion.insertContent(content);
}

export const getLeptonStyle = (type: number, suffix: string): Observable<string> => {
  return of(`lepton${type}${suffix}`);
};

export function initLeptonStyleHandler(injector: Injector) {
  const layoutState = injector.get(LayoutStateService);
  const configStateService = injector.get(ConfigStateService);
  const customStyle = injector.get(CUSTOM_STYLE);

  if (customStyle) {
    removeLeptonLoader();
    return;
  }

  const removeLeptonStyles = (element: HTMLLinkElement, injector: Injector) => {
    const domInsertionService = injector.get(DomInsertionService);
    if (element) { domInsertionService.removeContent(element); };
  };

  const style$ = configStateService
    .getSetting$('Volo.Abp.LeptonTheme.Style')
    .pipe(map((style: string) => Number((style || 'Style1').replace('Style', ''))));
  const suffix$ = configStateService
    .getDeep$('localization.currentCulture')
    .pipe(map((currentLang: CurrentCultureDto) => (currentLang?.isRightToLeft ? '.rtl' : '')));

  combineLatest([style$, suffix$])
    .pipe(
      distinctUntilChanged((prev, curr) => prev[0] === curr[0] && prev[1] === curr[1]),
      tap(removeLeptonLoader),
      switchMap(([style, suffix]) =>
        loadLeptonStyle(style, suffix).pipe(map(element => ({ element, style }))),
      ),
    )
    .subscribe(result => {
      const styleElement = layoutState.get('styleElement') as HTMLLinkElement;
      removeLeptonStyles(styleElement, injector);
      layoutState.patch({ style: result.style, styleElement: result.element });
    });
}

export function loadLeptonStyle(type: number, suffix: string): Observable<HTMLLinkElement> {
  const leptonStyles: HTMLElement = document.querySelector(
    `style[id="${LEPTON_STYLE_ELEMENT_ID}"]`,
  );
  const domStrategy = leptonStyles
    ? DOM_STRATEGY.BeforeElement(leptonStyles)
    : DOM_STRATEGY.AppendToHead();

  return getLeptonStyle(type, suffix).pipe(
    map(bundleName => {
      const linkElem = document.createElement('link');
      linkElem.setAttribute('rel', 'stylesheet');
      linkElem.setAttribute('href', `${bundleName}.css`);
      linkElem.setAttribute('id', `lepton${type}${suffix}`);
      domStrategy.insertElement(linkElem);
      return linkElem;
    }),
  );
}

export function removeLeptonLoader() {
  const loader: HTMLElement = document.querySelector('#lp-page-loader');
  if (!loader) {return;}
  loader.style.background = 'var(--background)';
  loader.parentNode?.removeChild(loader);
}

export function initLayouts(injector: Injector) {
  const replaceableComponents = injector.get(ReplaceableComponentsService);
  replaceableComponents.add({
    key: eThemeLeptonComponents.ApplicationLayout,
    component: ApplicationLayoutComponent,
  });
  replaceableComponents.add({
    key: eThemeLeptonComponents.AccountLayout,
    component: AccountLayoutComponent,
  });
  replaceableComponents.add({
    key: eThemeLeptonComponents.EmptyLayout,
    component: EmptyLayoutComponent,
  });
}

Here is app.module.ts

@NgModule({
  declarations: [AppComponent, HelpComponent],
  bootstrap: [AppComponent],
  imports: [
    AngularSvgIconModule.forRoot(),
    BrowserModule,
    AppRoutingModule,
    CoreModule,
    ThemeSharedModule,
    AccountAdminConfigModule,
    IdentityConfigModule,
    SaasConfigModule,
    AuditLoggingConfigModule,
    SettingManagementConfigModule,
    MenuSearchModule.forRoot({ limit: 3 }),
    ThemeLeptonModule.forRoot({
      customStyle: true,
    }),
    CommercialUiConfigModule,
    MasterDataManagementConfigModule.forRoot(),
    IntegrationConfigModule.forRoot(),
    ControlsConfigModule.forRoot(),
    SettingsConfigModule.forRoot(),
    AccountConfigModule.forRoot(),
    KendoFormsModule,
    ...dev,
    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled: !isDevMode(),
      // Register the ServiceWorker as soon as the application is stable
      // or after 30 seconds (whichever comes first).
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
  providers: [
    APP_ROUTE_PROVIDER,
    USER_MENU_ADDED_ITEMS,
    KendoIntlService,
    provideAbpCore(
      withOptions({
        environment,
        registerLocaleFn: registerLocale(),
      }),
    ),
    provideHttpClient(
      withInterceptorsFromDi(),
      withXsrfConfiguration({
        cookieName: 'XSRF-TOKEN',
        headerName: 'RequestVerificationToken',
      }),
    ),
    provideAbpThemeShared(
      withHttpErrorConfig({
        errorScreen: {
          component: HttpErrorComponent,
          forWhichErrors: [401, 403, 404, 500],
          hideCloseIcon: true,
        },
      }),
    ),
    provideAnimations(),
    provideAbpOAuth(),
    provideIdentityConfig(),
    provideSettingManagementConfig(),
    provideAccountAdminConfig(),
    provideAccountPublicConfig(),
    provideCommercialUiConfig(),
    provideSaasConfig(),
    provideAuditLoggingConfig(),
    provideOpeniddictproConfig(),
    provideTextTemplateManagementConfig(),
    provideLanguageManagementConfig(),
    {
      provide: IntlService,
      useExisting: KendoIntlService,
    },
    {
      provide: LOCALE_ID,
      deps: [LocalizationService],
      useFactory: (localizationService: LocalizationService) => localizationService.currentLang,
    },
    { provide: MessageService, useExisting: KendoMessagesService },
    { provide: PAGE_RENDER_STRATEGY, useClass: ScmPageRenderStrategy },
    {
      provide: VALIDATION_BLUEPRINTS,
      useValue: {
        ...DEFAULT_VALIDATION_BLUEPRINTS,
        pattern: 'AbpValidation::InvalidPatternPleaseReviewYourInput',
        min: 'AbpValidation::ThisFieldMustBeGreaterThanOrEqualTo{0}[{{ min }}]',
        max: 'AbpValidation::ThisFieldMustBeLessThanOrEqualTo{0}[{{ max }}]',
        calculationPeriodStartDate: 'AbpValidation::StartDateCanNotBeInFuture',
        duplicatePriority: 'AbpValidation::PriorityIsAlreadyInUse',
        duplicateName: 'AbpValidation::NameIsAlreadyInUse',
        dateIsInPast: 'AbpValidation::DateCanNotBeInPast',
        maxDecimals: 'AbpValidation::ThisFieldMustContainLessThanOrEqualDecimals{0}[{{ maxDecimals }}]',
        greaterThanField: 'AbpValidation::ThisFieldMustBeGreaterThan{0}[{{ field }}]',
        greaterThanZero: 'AbpValidation::ThisFieldMustBeGreaterThan{0}[0]',
        lessThanField: 'AbpValidation::ThisFieldMustBeLessThan{0}[{{ field }}]',
        invalidValues: 'AbpValidation::InvalidValues{0}ValidValues{1}[{{ invalidField }},{{ validField }}]',
        missingRequiredValues: 'AbpValidation::MissingRequiredValues{0}[{{ field }}]',
        dateIsNotInPastOrToday: 'AbpValidation::DateCanNotBeInPastOrToday',
      },
    },
    { provide: CUSTOM_ERROR_HANDLERS, useExisting: SCMCustomErrorHandlerService, multi: true },
    { provide: ErrorHandler, useClass: ApplicationInsightsErrorHandler },
    {
      provide: NAVIGATE_TO_MANAGE_PROFILE,
      deps: [Router],
      useFactory: (router: Router) => () => router.navigate(['/account/manage']),
    },
    TawkService,
    BeamerService,
  ],
})
export class AppModule {}

I debugged this part and no exceptions occured. Execution goes inside if statement. What about my latest comment about this change.

replaceableComponents.add({
    key: eThemeLeptonComponents.EmptyLayout,
    component: ApplicationLayoutComponent,
  });

Where I replace empty component for empty key by ApplicationLayoutComponent. This makes the sidebar to show. But this seems to me pretty forced solution. But this might help you understand where problem might be.

I just tried how this would work and it seems that it fixed issue but I don't think this is correct solution. So I replaced empty layout component with application lyout component.

export function initLayouts(injector: Injector) {
  const replaceableComponents = injector.get(ReplaceableComponentsService);
  replaceableComponents.add({
    key: eThemeLeptonComponents.ApplicationLayout,
    component: ApplicationLayoutComponent,
  });
  replaceableComponents.add({
    key: eThemeLeptonComponents.AccountLayout,
    component: AccountLayoutComponent,
  });
  replaceableComponents.add({
    key: eThemeLeptonComponents.EmptyLayout,
    component: ApplicationLayoutComponent,
  });
}

Here is what I get from application-configuration related to lepton theme So I didn't figure out yet how layout is selected (empty, application, account)

Do you mean that how I provide LEPTON_THEME_FEATURES_PROVIDERS? Because SET_LEPTON_THEME_SETTING_TAB_VISIBILITY and LEPTON_THEME_FEATURES are only used in this features.token.ts file. ThemeLeptonModule is only one where LEPTON_THEME_FEATURES_PROVIDERS is in provided list.

Do you believe that problem might be related to those injection tokens what I provided earlier? This was how they were earlier before I removed APP_INITIALIZER usage.

export const LEPTON_THEME_FEATURES_PROVIDERS = [
  {
    provide: APP_INITIALIZER,
    useFactory: noop,
    deps: [SET_LEPTON_THEME_SETTING_TAB_VISIBILITY],
    multi: true,
  },
];

I have used that new angular build system already in 8.3.4 version. I have read all those related migration guides. Our project is so big so for now we are staying with module style and not going to switch to standalone components. I have tried that angular standalone migration tool but it didn't made good work so it would be huge work to migrate rest by hand and figure out what modules/components need to be imported into each component. We have over 320 components and over 184 modules. It would be quite hard to provide any minimal reproducible example because we are no going to share all of our code and I have no idea what parts are related. Maybe theme-lepton module? I do not have list about what customizations we have done to lepton-theme.

Problem might be related to how layout is selected. Now there is empty layout in use. Are these keys changed at your side?

And features.token.ts looks like this

import { ConfigStateService, featuresFactory } from '@abp/ng.core';
import { inject, InjectionToken, provideAppInitializer } from '@angular/core';
import { ModuleVisibility, setModuleVisibilityFactory } from '@volo/abp.commercial.ng.ui/config';
import { SettingTabsService } from '@abp/ng.setting-management/config';
import { Observable } from 'rxjs';
import { eLeptonThemeSettingTabNames } from '../enums/setting-tab-names';

export const LEPTON_THEME_FEATURES = new InjectionToken<Observable<ModuleVisibility>>(
  'LEPTON_THEME_FEATURES',
  {
    providedIn: 'root',
    factory: () => {
      const configState = inject(ConfigStateService);
      const featureKey = 'LeptonManagement.Enable';
      const mapFn = features => ({
        enable: features[featureKey].toLowerCase() !== 'false',
      });

      return featuresFactory(configState, [featureKey], mapFn);
    },
  },
);

export const SET_LEPTON_THEME_SETTING_TAB_VISIBILITY = new InjectionToken(
  'SET_LEPTON_THEME_SETTING_TAB_VISIBILITY',
  {
    providedIn: 'root',
    factory: () => {
      const settingTabs = inject(SettingTabsService);
      const stream = inject(LEPTON_THEME_FEATURES);

      setModuleVisibilityFactory(
        stream,
        settingTabs,
        eLeptonThemeSettingTabNames.LeptonThemeManagement,
      ).subscribe();
    },
  },
);

export const LEPTON_THEME_FEATURES_PROVIDERS = [
  provideAppInitializer(() => {
    inject(SET_LEPTON_THEME_SETTING_TAB_VISIBILITY);
    return;
  }),
];

Here is our ThemeLeptonModule if problem is related to it:

import { APP_INIT_ERROR_HANDLERS, CoreModule } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import {
  NgxValidateCoreModule,
  VALIDATION_ERROR_TEMPLATE,
  VALIDATION_INVALID_CLASSES,
  VALIDATION_TARGET_SELECTOR,
} from '@ngx-validate/core';
import { PROFILE_PICTURE_PROVIDERS } from '@volo/abp.commercial.ng.ui/config';
import { AccountLayoutComponent } from './components/account-layout/account-layout.component';
import { AccountLogoComponent } from './components/account-layout/account-logo/account-logo.component';
import { AuthWrapperComponent } from './components/account-layout/auth-wrapper/auth-wrapper.component';
import { TenantBoxComponent } from './components/account-layout/tenant-box/tenant-box.component';
import { ApplicationLayoutComponent } from './components/application-layout/application-layout.component';
import { CurrentUserImageComponent } from './components/current-user-image/current-user-image.component';
import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.component';
import { FooterComponent } from './components/footer/footer.component';
import { HeaderComponent } from './components/header/header.component';
import { HttpErrorComponent } from './components/http-error/http-error.component';
import { LogoComponent } from './components/logo/logo.component';
import { CurrentUserComponent } from './components/nav-items/current-user.component';
import { FullScreenComponent } from './components/nav-items/full-screen.component';
import { LanguagesComponent } from './components/nav-items/languages.component';
import { NavItemsComponent } from './components/nav-items/nav-items.component';
import { NavbarMobileComponent } from './components/navbar-mobile/navbar-mobile.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { PageAlertContainerComponent } from './components/page-alert-container/page-alert-container.component';
import { RoutesComponent } from './components/routes/routes.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SidebarComponent } from './components/sidebar/sidebar.component';
import { ValidationErrorComponent } from './components/validation-error/validation-error.component';
import { LEPTON_THEME_NAV_ITEM_PROVIDERS } from './providers/nav-item.provider';
import { LEPTON_THEME_SETTING_TAB_PROVIDERS } from './providers/setting-tab.provider';
import { LEPTON_THEME_STYLES_PROVIDERS, removeLeptonLoader } from './providers/styles.provider';
import { LEPTON_THEME_USER_MENU_PROVIDERS } from './providers/user-menu.provider';
import { CUSTOM_STYLE } from './tokens/custom-style.token';
import { LEPTON_THEME_FEATURES_PROVIDERS } from './tokens/features.token';
import { CONTENT_AFTER_ROUTES, CONTENT_BEFORE_ROUTES } from './tokens/routes-content.token';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { FeatureBadgeModule } from '@SCM/common/base-forms/components/feature-badge/feature-badge.module';
import { Options } from './models/theme-lepton';

export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent];

const COMPONENTS = [
  SettingsComponent,
  ValidationErrorComponent,
  HttpErrorComponent,
  LogoComponent,
  RoutesComponent,
  NavItemsComponent,
  CurrentUserImageComponent,
  LanguagesComponent,
  CurrentUserComponent,
  FullScreenComponent,
  NavbarComponent,
  NavbarMobileComponent,
  HeaderComponent,
  FooterComponent,
  SidebarComponent,
  PageAlertContainerComponent,
  AuthWrapperComponent,
  TenantBoxComponent,
  AccountLogoComponent,
];

@NgModule({
  declarations: [...LAYOUTS, ...COMPONENTS],
  exports: [...LAYOUTS, ...COMPONENTS],
  imports: [
    CoreModule,
    ThemeSharedModule,
    NgbDropdownModule,
    NgxValidateCoreModule,
    AngularSvgIconModule,
    FeatureBadgeModule
  ],
})
export class BaseThemeLeptonModule { }

@NgModule({
  exports: [BaseThemeLeptonModule],
  imports: [BaseThemeLeptonModule],
})
export class ThemeLeptonModule {
  static forRoot(options = {} as Options): ModuleWithProviders<ThemeLeptonModule> {
    return {
      ngModule: ThemeLeptonModule,
      providers: [
        LEPTON_THEME_NAV_ITEM_PROVIDERS,
        LEPTON_THEME_USER_MENU_PROVIDERS,
        LEPTON_THEME_STYLES_PROVIDERS,
        LEPTON_THEME_SETTING_TAB_PROVIDERS,
        PROFILE_PICTURE_PROVIDERS,
        LEPTON_THEME_FEATURES_PROVIDERS,
        {
          provide: VALIDATION_ERROR_TEMPLATE,
          useValue: ValidationErrorComponent,
        },
        {
          provide: VALIDATION_INVALID_CLASSES,
          useValue: 'input-validation-error',
        },
        {
          provide: VALIDATION_TARGET_SELECTOR,
          useValue: '.form-control',
        },
        {
          provide: CONTENT_AFTER_ROUTES,
          useValue: options.contentAfterRoutes || [],
        },
        {
          provide: CONTENT_BEFORE_ROUTES,
          useValue: options.contentBeforeRoutes || [],
        },
        {
          provide: CUSTOM_STYLE,
          useValue: options.customStyle || false,
        },
        {
          provide: APP_INIT_ERROR_HANDLERS,
          useValue: removeLeptonLoader,
          multi: true,
        },
      ],
    };
  }
}
Showing 11 to 20 of 104 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on December 12, 2025, 10:36
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.