Skip to content

Commit

Permalink
fix: abstract injection token creation (#56)
Browse files Browse the repository at this point in the history
This PR adds `createInjectionToken` function to abstract the way we create `InjectionToken`

- `InjectFn` is strongly-typed and has overloads to properly support `{optional: true}`
- `ProvideFn` is strongly-typed version of `{provide, useValue}`
- `ProvideFnExisting` is strongly-typed version of `{provide, useExisting}`
- `TOKEN` is exposed for more manual use-cases
  • Loading branch information
nartc authored Nov 20, 2023
1 parent 9a98b50 commit 5b81a04
Show file tree
Hide file tree
Showing 26 changed files with 131 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HlmCommandDirective } from './hlm-command.directive';
})
export class HlmCommandDialogDirective {
private _stateProvider = injectExposesStateProvider({ host: true });
public state = this._stateProvider?.state ?? signal('closed').asReadonly();
public state = this._stateProvider.state ?? signal('closed').asReadonly();
private _renderer = inject(Renderer2);
private _element = inject(ElementRef);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ClassValue } from 'clsx';
export class HlmDialogContentDirective {
private _inputs: ClassValue = '';
private _statusProvider = injectExposesStateProvider({ host: true });
public state = this._statusProvider?.state ?? signal('closed').asReadonly();
public state = this._statusProvider.state ?? signal('closed').asReadonly();
private _renderer = inject(Renderer2);
private _element = inject(ElementRef);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export class HlmHoverCardContentDirective {

private _inputs: ClassValue = '';

public readonly state = this._statusProvider?.state ?? signal('closed').asReadonly();
public readonly side = this._sideProvider?.side ?? signal('bottom').asReadonly();
public readonly state = this._statusProvider.state ?? signal('closed').asReadonly();
public readonly side = this._sideProvider.side ?? signal('bottom').asReadonly();

constructor() {
effect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ClassValue } from 'clsx';
export class HlmPopoverContentDirective {
private _inputs: ClassValue = '';
private _stateProvider = injectExposesStateProvider({ host: true });
public state = this._stateProvider?.state ?? signal('closed');
public state = this._stateProvider.state ?? signal('closed');
private _renderer = inject(Renderer2);
private _element = inject(ElementRef);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class HlmSheetContentDirective {
private _inputs: ClassValue = '';
private _stateProvider = injectExposesStateProvider({ host: true });
private _sideProvider = injectExposedSideProvider({ host: true });
public state = this._stateProvider?.state ?? signal('closed');
public state = this._stateProvider.state ?? signal('closed');
private _renderer = inject(Renderer2);
private _element = inject(ElementRef);

Expand All @@ -39,7 +39,7 @@ export class HlmSheetContentDirective {
this._renderer.setAttribute(this._element.nativeElement, 'data-state', this.state());
});
effect(() => {
this._sideProvider?.side();
this._sideProvider.side();
this._class = this.generateClasses();
});
}
Expand All @@ -53,6 +53,6 @@ export class HlmSheetContentDirective {
}

private generateClasses() {
return hlm(sheetVariants({ side: this._sideProvider?.side() }), this._inputs);
return hlm(sheetVariants({ side: this._sideProvider.side() }), this._inputs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,17 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
forwardRef,
inject,
signal,
ViewEncapsulation,
} from '@angular/core';
import { CustomElementClassSettable, SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN } from '@spartan-ng/ui-core';
import { CustomElementClassSettable, provideCustomClassSettableExisting } from '@spartan-ng/ui-core';
import { BrnAccordionItemComponent } from './brn-accordion-item.component';

@Component({
selector: 'brn-accordion-content',
standalone: true,
providers: [
{
provide: SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN,
useExisting: forwardRef(() => BrnAccordionContentComponent),
},
],
providers: [provideCustomClassSettableExisting(() => BrnAccordionContentComponent)],
host: {
'[attr.data-state]': 'state()',
'[attr.aria-labelledby]': 'ariaLabeledBy',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { Component, ElementRef, forwardRef, inject, signal } from '@angular/core';
import { CustomElementClassSettable, SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN } from '@spartan-ng/ui-core';
import { Component, ElementRef, inject, signal } from '@angular/core';
import { CustomElementClassSettable, provideCustomClassSettableExisting } from '@spartan-ng/ui-core';
import { BrnAccordionItemComponent } from './brn-accordion-item.component';
import { BrnAccordionComponent } from './brn-accordion.component';

@Component({
selector: 'brn-accordion-trigger',
standalone: true,
providers: [
{
provide: SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN,
useExisting: forwardRef(() => BrnAccordionTriggerComponent),
},
],
providers: [provideCustomClassSettableExisting(() => BrnAccordionTriggerComponent)],
host: {
'[attr.data-state]': 'state()',
'[attr.aria-expanded]': 'state() === "open"',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { Directive, forwardRef } from '@angular/core';
import { EXPOSES_STATE_TOKEN } from '@spartan-ng/ui-core';
import { Directive } from '@angular/core';
import { provideExposesStateProviderExisting } from '@spartan-ng/ui-core';
import { BrnDialogContentDirective } from '@spartan-ng/ui-dialog-brain';

@Directive({
selector: '[brnAlertDialogContent]',
standalone: true,
providers: [
{
provide: EXPOSES_STATE_TOKEN,
useExisting: forwardRef(() => BrnAlertDialogContentDirective),
},
],
providers: [provideExposesStateProviderExisting(() => BrnAlertDialogContentDirective)],
})
export class BrnAlertDialogContentDirective<T> extends BrnDialogContentDirective<T> {}
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { ChangeDetectionStrategy, Component, forwardRef, ViewEncapsulation } from '@angular/core';
import { SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN } from '@spartan-ng/ui-core';
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { provideCustomClassSettableExisting } from '@spartan-ng/ui-core';
import { BrnDialogOverlayComponent } from '@spartan-ng/ui-dialog-brain';

@Component({
selector: 'brn-alert-dialog-overlay',
standalone: true,
providers: [
{
provide: SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN,
useExisting: forwardRef(() => BrnAlertDialogOverlayComponent),
},
],
providers: [provideCustomClassSettableExisting(() => BrnAlertDialogOverlayComponent)],
template: ``,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HlmCommandDirective } from './hlm-command.directive';
})
export class HlmCommandDialogDirective {
private _stateProvider = injectExposesStateProvider({ host: true });
public state = this._stateProvider?.state ?? signal('closed').asReadonly();
public state = this._stateProvider.state ?? signal('closed').asReadonly();
private _renderer = inject(Renderer2);
private _element = inject(ElementRef);

Expand Down
40 changes: 40 additions & 0 deletions libs/ui/core/src/lib/brain/create-injection-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { InjectionToken, Type, forwardRef, inject, type InjectOptions, type Provider } from '@angular/core';

type InjectFn<TTokenValue> = {
(): TTokenValue;
(injectOptions: InjectOptions & { optional?: false }): TTokenValue;
(injectOptions: InjectOptions & { optional: true }): TTokenValue | null;
};

type ProvideFn<TTokenValue> = {
(value: TTokenValue): Provider;
};

type ProvideExistingFn<TTokenValue> = {
(valueFactory: () => Type<TTokenValue>): Provider;
};

export type CreateInjectionTokenReturn<TTokenValue> = [
InjectFn<TTokenValue>,
ProvideFn<TTokenValue>,
ProvideExistingFn<TTokenValue>,
InjectionToken<TTokenValue>,
];

export function createInjectionToken<TTokenValue>(description: string): CreateInjectionTokenReturn<TTokenValue> {
const token = new InjectionToken<TTokenValue>(description);

const provideFn = (value: TTokenValue) => {
return { provide: token, useValue: value };
};

const provideExistingFn = (value: () => TTokenValue) => {
return { provide: token, useExisting: forwardRef(value) };
};

const injectFn = (options: InjectOptions = {}) => {
return inject(token, options);
};

return [injectFn, provideFn, provideExistingFn, token] as CreateInjectionTokenReturn<TTokenValue>;
}
12 changes: 7 additions & 5 deletions libs/ui/core/src/lib/brain/custom-element-class-settable.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { inject, InjectionToken, InjectOptions } from '@angular/core';
import { createInjectionToken } from './create-injection-token';

export interface CustomElementClassSettable {
setClassToCustomElement: (newClass: string) => void;
}

export const SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN: InjectionToken<CustomElementClassSettable> =
new InjectionToken<CustomElementClassSettable>('@spartan-ng SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN');

export const injectCustomClassSettable = (options: InjectOptions) => inject(SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN, options);
export const [
injectCustomClassSettable,
provideCustomClassSettable,
provideCustomClassSettableExisting,
SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN,
] = createInjectionToken<CustomElementClassSettable>('@spartan-ng SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN');
14 changes: 8 additions & 6 deletions libs/ui/core/src/lib/brain/exposes-side.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { inject, InjectionToken, InjectOptions, Signal } from '@angular/core';
import type { Signal } from '@angular/core';
import { createInjectionToken } from './create-injection-token';

export interface ExposesSide {
side: Signal<'top' | 'bottom' | 'left' | 'right'>;
}

export const EXPOSES_SIDE_TOKEN: InjectionToken<ExposesSide> = new InjectionToken<ExposesSide>(
'@spartan-ng EXPOSES_SIDE_TOKEN',
);

export const injectExposedSideProvider = (options: InjectOptions) => inject(EXPOSES_SIDE_TOKEN, options);
export const [
injectExposedSideProvider,
provideExposedSideProvider,
provideExposedSideProviderExisting,
EXPOSES_SIDE_TOKEN,
] = createInjectionToken<ExposesSide>('@spartan-ng EXPOSES_SIDE_TOKEN');
14 changes: 8 additions & 6 deletions libs/ui/core/src/lib/brain/exposes-state.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { inject, InjectionToken, InjectOptions, Signal } from '@angular/core';
import { Signal } from '@angular/core';
import { createInjectionToken } from './create-injection-token';

export interface ExposesState {
state: Signal<'open' | 'closed'>;
}

export const EXPOSES_STATE_TOKEN: InjectionToken<ExposesState> = new InjectionToken<ExposesState>(
'@spartan-ng EXPOSES_STATE_TOKEN',
);

export const injectExposesStateProvider = (options: InjectOptions) => inject(EXPOSES_STATE_TOKEN, options);
export const [
injectExposesStateProvider,
provideExposesStateProvider,
provideExposesStateProviderExisting,
EXPOSES_STATE_TOKEN,
] = createInjectionToken<ExposesState>('@spartan-ng EXPOSES_STATE_TOKEN');
13 changes: 7 additions & 6 deletions libs/ui/core/src/lib/brain/table-classes-settable.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { inject, InjectionToken, InjectOptions } from '@angular/core';
import { createInjectionToken } from './create-injection-token';

export interface TableClassesSettable {
setTableClasses: (classes: Partial<{ table: string; headerRow: string; bodyRow: string }>) => void;
}

export const SET_TABLE_CLASSES_TOKEN: InjectionToken<TableClassesSettable> = new InjectionToken<TableClassesSettable>(
'@spartan-ng SET_TABLE_CLASSES_TOKEN',
);

export const injectTableClassesSettable = (options: InjectOptions) => inject(SET_TABLE_CLASSES_TOKEN, options);
export const [
injectTableClassesSettable,
provideTableClassesSettable,
provideTableClassesSettableExisting,
SET_TABLE_CLASSES_TOKEN,
] = createInjectionToken<TableClassesSettable>('@spartan-ng SET_TABLE_CLASSES_TOKEN');
13 changes: 4 additions & 9 deletions libs/ui/dialog/brain/src/lib/brn-dialog-content.directive.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { Directive, forwardRef, inject, Input, TemplateRef } from '@angular/core';
import { EXPOSES_STATE_TOKEN } from '@spartan-ng/ui-core';
import { Directive, inject, Input, TemplateRef } from '@angular/core';
import { ExposesState, provideExposesStateProviderExisting } from '@spartan-ng/ui-core';
import { BrnDialogComponent } from './brn-dialog.component';

@Directive({
selector: '[brnDialogContent]',
standalone: true,
providers: [
{
provide: EXPOSES_STATE_TOKEN,
useExisting: forwardRef(() => BrnDialogContentDirective),
},
],
providers: [provideExposesStateProviderExisting(() => BrnDialogContentDirective)],
})
export class BrnDialogContentDirective<T> {
export class BrnDialogContentDirective<T> implements ExposesState {
private _brnDialog = inject(BrnDialogComponent);
private _template = inject(TemplateRef);
public state = this._brnDialog.state;
Expand Down
13 changes: 4 additions & 9 deletions libs/ui/dialog/brain/src/lib/brn-dialog-overlay.component.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { ChangeDetectionStrategy, Component, forwardRef, inject, Input, ViewEncapsulation } from '@angular/core';
import { SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN } from '@spartan-ng/ui-core';
import { ChangeDetectionStrategy, Component, inject, Input, ViewEncapsulation } from '@angular/core';
import { CustomElementClassSettable, provideCustomClassSettableExisting } from '@spartan-ng/ui-core';
import { BrnDialogComponent } from './brn-dialog.component';

@Component({
selector: 'brn-dialog-overlay',
standalone: true,
providers: [
{
provide: SET_CLASS_TO_CUSTOM_ELEMENT_TOKEN,
useExisting: forwardRef(() => BrnDialogOverlayComponent),
},
],
providers: [provideCustomClassSettableExisting(() => BrnDialogOverlayComponent)],
template: ``,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class BrnDialogOverlayComponent {
export class BrnDialogOverlayComponent implements CustomElementClassSettable {
private _brnDialog = inject(BrnDialogComponent);
@Input()
set class(newClass: string | null | undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ClassValue } from 'clsx';
export class HlmDialogContentDirective {
private _inputs: ClassValue = '';
private _statusProvider = injectExposesStateProvider({ host: true });
public state = this._statusProvider?.state ?? signal('closed').asReadonly();
public state = this._statusProvider.state ?? signal('closed').asReadonly();
private _renderer = inject(Renderer2);
private _element = inject(ElementRef);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { Directive, forwardRef, inject, TemplateRef } from '@angular/core';
import { EXPOSES_SIDE_TOKEN, EXPOSES_STATE_TOKEN, ExposesSide, ExposesState } from '@spartan-ng/ui-core';
import { Directive, inject, TemplateRef } from '@angular/core';
import {
ExposesSide,
ExposesState,
provideExposedSideProviderExisting,
provideExposesStateProviderExisting,
} from '@spartan-ng/ui-core';
import { BrnHoverCardContentService } from './brn-hover-card-content.service';

@Directive({
selector: '[brnHoverCardContent]',
standalone: true,
exportAs: 'brnHoverCardContent',
providers: [
{
provide: EXPOSES_STATE_TOKEN,
useExisting: forwardRef(() => BrnHoverCardContentDirective),
},
{
provide: EXPOSES_SIDE_TOKEN,
useExisting: forwardRef(() => BrnHoverCardContentDirective),
},
provideExposedSideProviderExisting(() => BrnHoverCardContentDirective),
provideExposesStateProviderExisting(() => BrnHoverCardContentDirective),
],
})
export class BrnHoverCardContentDirective implements ExposesState, ExposesSide {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export class HlmHoverCardContentDirective {

private _inputs: ClassValue = '';

public readonly state = this._statusProvider?.state ?? signal('closed').asReadonly();
public readonly side = this._sideProvider?.side ?? signal('bottom').asReadonly();
public readonly state = this._statusProvider.state ?? signal('closed').asReadonly();
public readonly side = this._sideProvider.side ?? signal('bottom').asReadonly();

constructor() {
effect(() => {
Expand Down
11 changes: 3 additions & 8 deletions libs/ui/popover/brain/src/lib/brn-popover-content.directive.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { Directive, forwardRef } from '@angular/core';
import { EXPOSES_STATE_TOKEN } from '@spartan-ng/ui-core';
import { Directive } from '@angular/core';
import { provideExposesStateProviderExisting } from '@spartan-ng/ui-core';
import { BrnDialogContentDirective } from '@spartan-ng/ui-dialog-brain';

@Directive({
selector: '[brnPopoverContent]',
standalone: true,
providers: [
{
provide: EXPOSES_STATE_TOKEN,
useExisting: forwardRef(() => BrnPopoverContentDirective),
},
],
providers: [provideExposesStateProviderExisting(() => BrnPopoverContentDirective)],
})
export class BrnPopoverContentDirective<T> extends BrnDialogContentDirective<T> {}
Loading

1 comment on commit 5b81a04

@vercel
Copy link

@vercel vercel bot commented on 5b81a04 Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

spartan – ./

spartan-goetzrobin.vercel.app
spartan-git-main-goetzrobin.vercel.app
spartan.ng
www.spartan.ng

Please sign in to comment.