That "createSaasRoutes" fixed that TenantComponent problem. Should I change all abp routes to this kind?
I ahve similar problem with this one https://abp.io/support/questions/8997/NullInjectorError-No-provider-for-InjectionToken-undefined-in-Standalone-Component But those suggested solutions didn't work for me.
Here is version that I'm using at frontend: node version 22.20.0 yarn 1.22.22 abp 9.3.6 angular 20.3.7
I updated from 8.3.4 to 9.3.6 and followed your migration guides.
We will stay with module based structure because our application is quite big. Maybe later but not now.
So problem occures when I'm trying to navigate to 'saas/tenants' page. Page stays empty and I got console error that says: No provider found for InjectionToken undefined. Source: Standalone[_TenantsComponent].
I have tried to clean all places: node_modules, .angular and yarn.lock.
Am I missing some module or provider in my app module.
Here is my app.module.ts(I cleaned away our own modules):
import { PAGE_RENDER_STRATEGY } from '@abp/ng.components/page';
import {
CoreModule,
LazyLocalizationPipe,
LocalizationPipe,
LocalizationService,
NAVIGATE_TO_MANAGE_PROFILE,
provideAbpCore,
withOptions,
} from '@abp/ng.core';
import { provideSettingManagementConfig, SettingManagementConfigModule } from '@abp/ng.setting-management/config';
import {
CUSTOM_ERROR_HANDLERS,
DEFAULT_VALIDATION_BLUEPRINTS,
provideAbpThemeShared,
ThemeSharedModule,
withHttpErrorConfig,
} from '@abp/ng.theme.shared';
import { registerLocaleData } from '@angular/common';
import { provideHttpClient, withInterceptorsFromDi, withXsrfConfiguration } from '@angular/common/http';
import localeFi from '@angular/common/locales/fi';
import { ErrorHandler, LOCALE_ID, NgModule, importProvidersFrom, isDevMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { VALIDATION_BLUEPRINTS } from '@ngx-validate/core';
// https://www.telerik.com/kendo-angular-ui/components/globalization/internationalization/locale-changes/
import { IntlService } from '@progress/kendo-angular-intl';
import '@progress/kendo-angular-intl/locales/en/all';
import '@progress/kendo-angular-intl/locales/fi/all';
import { MessageService } from '@progress/kendo-angular-l10n';
import { CommercialUiConfigModule, provideCommercialUiConfig } from '@volo/abp.commercial.ng.ui/config';
import { AccountAdminConfigModule, provideAccountAdminConfig } from '@volo/abp.ng.account/admin/config';
import { AccountPublicConfigModule, provideAccountPublicConfig } from '@volo/abp.ng.account/public/config';
import { AuditLoggingConfigModule, provideAuditLoggingConfig } from '@volo/abp.ng.audit-logging/config';
import { provideOpeniddictproConfig } from '@volo/abp.ng.openiddictpro/config';
import { IdentityConfigModule, provideIdentityConfig } from '@volo/abp.ng.identity/config';
import { registerLocale } from '@volo/abp.ng.language-management/locale';
import { provideSaasConfig, SaasConfigModule } from '@volo/abp.ng.saas/config';
import { HttpErrorComponent, ThemeLeptonModule } from '@volo/abp.ng.theme.lepton';
import { MenuSearchModule } from '@volo/abp.ng.theme.lepton/extensions';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { KendoFormsModule } from './common/kendo-forms/kendo-forms.module';
import { HelpComponent } from './components/help/help.component';
import { ControlsConfigModule } from './controls/controls-config.module';
import { ComponentsConfigModule } from './demo/components/components-config.module';
import { TablesConfigModule } from './demo/tables/tables-config.module';
import { ItemPlanningConfigModule } from './item-planning/item-planning-config.module';
import { APP_ROUTE_PROVIDER } from './route.provider';
import { ScmPageRenderStrategy } from './scm-page-render-strategy';
import { SettingsConfigModule } from './settings/settings-config.module';
import { ApplicationInsightsErrorHandler } from './shared/services/application-insights-service/application-insights-error-handler';
import { KendoMessagesService } from './shared/services/kendo-messages-service/kendo-messages.service';
import { TawkService } from './tawk-service';
import { provideAbpOAuth } from '@abp/ng.oauth';
import { BeamerService } from './beamer.service';
import { ServiceWorkerModule } from '@angular/service-worker';
import { SCMCustomErrorHandlerService } from './shared/services/custom-error-handler-service/scm-custom-error-handler.service';
import { ForecastingConfigModule } from 'projects/SCM.Forecasting/config/src/forecasting-config.module';
import { USER_MENU_ADDED_ITEMS } from './account/user-menu-extended.routes';
import { AccountConfigModule } from './account/account-config.module';
import { Router } from '@angular/router';
import { KendoIntlService } from './shared/services/kendo-intl.service';
import { XmasModule } from './shared/components/special-events/xmas/xmas.module';
import { provideLanguageManagementConfig } from '@volo/abp.ng.language-management/config';
import { provideTextTemplateManagementConfig } from '@volo/abp.ng.text-template-management/config';
import { provideAnimations } from '@angular/platform-browser/animations';
// Finnish locale needs to be registered always since we have scm currency pipe that might use different locale from localization service.
// If it is not registered fi-FI locale is not found if the language is not fi
registerLocaleData(localeFi);
const dev = environment.production ? [] : [ComponentsConfigModule.forRoot(), TablesConfigModule.forRoot()];
@NgModule({
declarations: [AppComponent, HelpComponent],
bootstrap: [AppComponent],
imports: [
AngularSvgIconModule.forRoot(),
BrowserModule,
AppRoutingModule,
CoreModule,
ThemeSharedModule,
AccountAdminConfigModule,
IdentityConfigModule,
SaasConfigModule,
AuditLoggingConfigModule,
SettingManagementConfigModule,
MenuSearchModule.forRoot({ limit: 3 }),
ThemeLeptonModule.forRoot({
// DISABLED Search and Help main navigation items, left here for easy re-enabling:
// contentBeforeRoutes: [MenuSearchComponent],
// contentAfterRoutes: [HelpComponent]
// If this customStyle is true, the style selection box is not included in the theme
// settings form and ThemeLeptonModule does not load its own styles. In this case,
// a custom style file must be added to the styles array in angular.json or must be imported by style.scss.
// https://docs.abp.io/en/commercial/7.1/themes%2Flepton%2Fcustomizing-lepton-theme?UI=NG
customStyle: true,
}),
CommercialUiConfigModule,
ItemPlanningConfigModule.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',
}),
XmasModule,
],
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,
},
}),
),
importProvidersFrom(
AccountPublicConfigModule.forRoot(),
AccountAdminConfigModule.forRoot(),
IdentityConfigModule.forRoot(),
SaasConfigModule.forRoot(),
),
provideAnimations(),
provideAbpOAuth(),
provideIdentityConfig(),
provideSettingManagementConfig(),
provideAccountAdminConfig(),
provideAccountPublicConfig(),
provideCommercialUiConfig(),
provideSaasConfig(),
provideAuditLoggingConfig(),
provideOpeniddictproConfig(),
provideTextTemplateManagementConfig(),
provideLanguageManagementConfig(),
{
provide: LocalizationPipe,
useExisting: LazyLocalizationPipe,
},
{
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 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,
});
}