/**
* @module lib/for-angular-common.module
* @description Core Angular module and providers for Decaf's for-angular package.
* @summary Provides the shared Angular module, injection tokens and helper functions used
* by the for-angular integration. This module wires up common imports (forms, translation)
* and exposes helper providers such as DB adapter registration and logger utilities.
* @link {@link ForAngularCommonModule}
*/
import {
NgModule,
ModuleWithProviders,
InjectionToken,
Provider,
EnvironmentProviders,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule, TranslatePipe } from '@ngx-translate/core';
import { Logger, Logging } from '@decaf-ts/logging';
import { I18nToken } from './engine/interfaces';
import { getOnWindow, setOnWindow } from './utils/helpers';
import {
DecafRepository,
FunctionLike,
DecafRepositoryAdapter,
KeyValue
} from './engine/types';
import { Model, Primitives } from '@decaf-ts/decorator-validation';
import { Repository } from '@decaf-ts/core';
import { Constructor, uses } from '@decaf-ts/decoration';
import { AnimationController, provideIonicAngular } from '@ionic/angular/standalone';
export const DB_ADAPTER_PROVIDER = 'DB_ADAPTER_PROVIDER';
/**
* @description Injection token for registering the database adapter provider.
* @summary Used to inject the database adapter instance that implements DecafRepositoryAdapter.
* This token allows the framework to locate and use the application's specific database implementation.
* @const {InjectionToken<DecafRepositoryAdapter>}
* @memberOf module:lib/for-angular-common.module
*/
export const DB_ADAPTER_PROVIDER_TOKEN =
new InjectionToken<DecafRepositoryAdapter>('DB_ADAPTER_PROVIDER_TOKEN');
/**
* @description Injection token for the root path of locale translation files.
* @summary Used to configure the base path where i18n translation files are located.
* This allows the translation loader to locate JSON files for different languages.
* @const {InjectionToken<string>}
* @memberOf module:lib/for-angular-common.module
* @example
* // Typical usage when providing the token
* { provide: LOCALE_ROOT_TOKEN, useValue: './assets/i18n/' }
*/
export const LOCALE_ROOT_TOKEN = new InjectionToken<string>(
'LOCALE_ROOT_TOKEN'
);
/* Generic token for injecting on class constructors */
/**
* @description Generic injection token for providing arbitrary values to constructors.
* @summary Used to inject classes, strings, or any other value into component or service constructors.
* This is a flexible token that can be used to provide any type of dependency when more specific
* tokens are not appropriate. The actual type and purpose of the injected value is determined by
* the provider configuration.
* @const {InjectionToken<unknown>}
* @memberOf module:lib/for-angular-common.module
* @example
* // Inject a string value
* { provide: CPTKN, useValue: 'some-config-value' }
*
* // Inject a class
* { provide: CPTKN, useClass: MyService }
*
* // Inject any arbitrary value
* { provide: CPTKN, useValue: { key: 'value', data: [1, 2, 3] } }
*/
export const CPTKN = new InjectionToken<unknown>('CPTKN', {
providedIn: 'root',
factory: () => '',
});
/**
* @description Injection token for i18n resource configuration.
* @summary Used to provide configuration for internationalization resources, including
* translation file locations and supported languages. This token configures how the
* application loads and manages translation resources.
* @const {InjectionToken<I18nToken>}
* @memberOf module:lib/for-angular-common.module
*/
export const I18N_CONFIG_TOKEN = new InjectionToken<I18nToken>(
'I18N_CONFIG_TOKEN'
);
/**
* @description Provides an array of component types for dynamic rendering.
* @summary Helper function to package component constructors for registration with the
* rendering engine. This function accepts component classes and returns them as an array
* suitable for use with the CPTKN injection token.
* @param {...Constructor[]} components - Component constructor classes to register
* @return {Constructor} Array of component constructors
* @memberOf module:lib/for-angular-common.module
* @example
* // Register multiple custom components
* providers: [
* { provide: CPTKN, useValue: provideDynamicComponents(MyComponent, AnotherComponent) }
* ]
*/
export function provideDynamicComponents(
...components: unknown[]
): Constructor<unknown>[] {
return components as Constructor<unknown>[];
}
/**
* @description Retrieves the repository instance for a given model.
* @summary Creates or retrieves a DecafRepository instance for the specified model. This function
* resolves the model by name or class, locates the registered database adapter, and returns
* a fully initialized repository instance for performing CRUD operations.
* @param {Model | string} model - The model class or model name string
* @return {DecafRepository<Model>} Repository instance for the model
* @throws {InternalError} If model is not found or not registered with @model decorator
* @memberOf module:lib/for-angular-common.module
* @example
* // Get repository by model class
* const userRepo = getModelAndRepository(User);
*
* // Get repository by model name
* const productRepo = getModelAndRepository('Product');
*
* // Use repository for queries
* const users = await userRepo.findAll();
*/
export function getModelAndRepository(
model: Model | string
): { repository: DecafRepository<Model>, model: Model, pk: string } | undefined {
try {
const modelName = (
typeof model === Primitives.STRING
? model
: (model as Model).constructor.name
) as string;
const constructor = Model.get(
(modelName.charAt(0).toUpperCase() + modelName.slice(1)) as string
);
if (!constructor) return undefined;
const dbAdapterFlavour = getOnWindow(DB_ADAPTER_PROVIDER) || undefined;
if (dbAdapterFlavour)
uses(dbAdapterFlavour as string)(constructor);
const repository = Repository.forModel(constructor);
model = new constructor() as Model;
const pk = Model.pk(repository.class as Constructor<Model>);
if (!pk)
return undefined;
return { repository, model, pk };
} catch (error: unknown) {
getLogger(getModelAndRepository).warn((error as Error)?.message || (error as string));
return undefined;
}
}
/**
* @description Provides a database adapter for dependency injection.
* @summary Creates an Angular provider that registers a database adapter instance. This function
* instantiates the adapter class, registers its flavour globally, and returns a provider object
* for use in Angular's dependency injection system.
* @template DbAdapter - The database adapter class type extending {flavour: string}
* @param {Constructor<DbAdapter>} adapterClass - Database adapter constructor class
* @param {KeyValue} [options={}] - Configuration options passed to adapter constructor
* @param {string} [flavour] - Optional flavour override; uses adapter.flavour if not provided
* @return {Provider} Angular provider object for DB_ADAPTER_PROVIDER_TOKEN
* @memberOf module:lib/for-angular-common.module
* @example
* // Register a SQLite adapter
* providers: [
* provideDbAdapter(SqliteAdapter, { database: 'myapp.db' }, 'sqlite')
* ]
*
* // Register with default flavour from adapter
* providers: [
* provideDbAdapter(PostgresAdapter, { host: 'localhost', port: 5432 })
* ]
*/
export function provideDbAdapter<DbAdapter extends { flavour: string }>(
clazz: Constructor<DbAdapter>,
options: KeyValue = {},
flavour?: string
): Provider {
const adapter = new clazz(options);
if (flavour) flavour = adapter.flavour;
getLogger(provideDbAdapter).info(`Using ${adapter.constructor.name} ${flavour} as Db Provider`);
setOnWindow(DB_ADAPTER_PROVIDER, flavour);
return {
provide: DB_ADAPTER_PROVIDER_TOKEN,
useValue: adapter,
};
}
/**
* Creates a custom page transition animation using the Ionic `AnimationController`.
*
* @param baseEl - The base HTML element for the animation.
* @param opts - Optional parameters for the animation, including:
* - `enteringEl`: The HTML element that is entering the view.
* - `leavingEl`: The HTML element that is leaving the view.
*
* @returns An object containing the `navAnimation`, which is a composed animation
* of the entering and leaving animations.
*
* The entering animation fades in and slides the element upwards, while the leaving
* animation fades out and slides the element downwards. Both animations use a cubic-bezier
* easing function for smooth transitions.
*/
export const decafPageTransition = (baseEl: HTMLElement, opts?: KeyValue) => {
const animationCtrl = new AnimationController();
const enteringAnimation = animationCtrl
.create()
.addElement(opts?.['enteringEl'])
.duration(280)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.fromTo('opacity', '0.01', '1')
.fromTo('transform', 'translateY(40px)', 'translateY(0)');
const leavingAnimation = animationCtrl
.create()
.addElement(opts?.['leavingEl'])
.duration(200)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.fromTo('opacity', '1', '0')
.fromTo('transform', 'translateY(0)', 'translateY(20px)');
return animationCtrl.create().addAnimation([enteringAnimation, leavingAnimation]);
};
export function provideDecafPageTransition(): EnvironmentProviders {
return provideIonicAngular({
navAnimation: decafPageTransition
});
};
/**
* @const {Logger}
* @private
* @description Base logger instance for the for-angular module.
* @memberOf module:lib/for-angular-common.module
*/
const log = Logging.for('for-angular');
/**
* @description Retrieves a logger instance for the given context.
* @summary Creates or retrieves a namespaced logger instance using the Decaf logging system.
* The logger is automatically namespaced under "for-angular" and can be further scoped
* to a specific instance, function, or string identifier.
* @param {string | FunctionLike | unknown} instance - The instance, function, or string to scope the logger to
* @return {Logger} Logger instance for the specified context
* @memberOf module:lib/for-angular-common.module
* @example
* // Get logger for a class
* const logger = getLogger(MyComponent);
* logger.info('Component initialized');
*
* // Get logger with string identifier
* const serviceLogger = getLogger('UserService');
* serviceLogger.error('Operation failed', error);
*/
export function getLogger(instance: string | FunctionLike | unknown): Logger {
return log.for(instance as string | FunctionLike);
}
const CommonModules = [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule,
TranslatePipe,
];
/**
* @description Main Angular module for the Decaf framework.
* @summary The ForAngularCommonModule provides the core functionality for integrating Decaf with Angular applications.
* It imports and exports common Angular and Ionic components and modules needed for Decaf applications,
* including form handling, translation support, and Ionic UI components. This module can be imported
* directly or via the forRoot() method for proper initialization in the application's root module.
* @class ForAngularCommonModule
* @memberOf module:lib/for-angular-common.module
* @example
* // In your app module:
* @NgModule({
* imports: [
* ForAngularCommonModule.forRoot(),
* // other imports
* ],
* // ...
* })
* export class AppModule {}
*/
@NgModule({
imports: CommonModules,
declarations: [],
exports: CommonModules,
schemas: [],
providers: [],
})
export class ForAngularCommonModule {
/**
* @description Creates a module with providers for root module import.
* @summary This static method provides the proper way to import the ForAngularCommonModule in the application's
* root module. It returns a ModuleWithProviders object that includes the ForAngularCommonModule itself.
* Using forRoot() ensures that the module and its providers are properly initialized and only
* instantiated once in the application.
* @return {ModuleWithProviders<ForAngularCommonModule>} The module with its providers
* @memberOf ForAngularCommonModule
* @static
* @example
* // Import in root module
* @NgModule({
* imports: [ForAngularCommonModule.forRoot()],
* // ...
* })
* export class AppModule {}
*/
static forRoot(): ModuleWithProviders<ForAngularCommonModule> {
return {
ngModule: ForAngularCommonModule,
};
}
}
Source