File

projects/angular/components/ui-snackbar/src/ui-snackbar.component.ts

Metadata

Index

Properties
Accessors

Constructor

constructor(data: ISnackBarAlert, snackBarRef: MatSnackBarRef<UiSnackBarComponent>, _injector: Injector)
Parameters :
Name Type Optional
data ISnackBarAlert No
snackBarRef MatSnackBarRef<UiSnackBarComponent> No
_injector Injector No

Properties

Public data
Type : ISnackBarAlert
Decorators :
@Inject(MAT_SNACK_BAR_DATA)
Public snackBarRef
Type : MatSnackBarRef<UiSnackBarComponent>

Accessors

componentPortal
getcomponentPortal()
import {
    ComponentPortal,
    ComponentType,
} from '@angular/cdk/portal';
import {
    ChangeDetectionStrategy,
    Component,
    Inject,
    Injectable,
    InjectionToken,
    Injector,
    Optional,
    TemplateRef,
    ViewEncapsulation,
} from '@angular/core';
import {
    MatSnackBar,
    MatSnackBarConfig,
    MatSnackBarRef,
    MAT_SNACK_BAR_DATA,
    MAT_SNACK_BAR_DEFAULT_OPTIONS,
} from '@angular/material/snack-bar';

import { UiSnackbarIntl } from './ui-snackbar.intl';

interface ISnackBarAlert {
    /**
     * Alert message
     */
    message: string | TemplateRef<any> | ComponentType<unknown>;
    /**
     * Optional action button message,
     * will emit `dismissWithAction` on click
     */
    actionMessage?: string;
    /**
     * How long to remain on the screen
     */
    duration: number;
    /**
     * Material icon to be used in snackbar
     */
    icon?: string;
    /**
     * Aria label for screen-readers on close button
     */
    closeAriaLabel?: string;
    /**
     * Additional information to pass to components. Can be used by injecting `UI_MAT_SNACK_BAR_PAYLOAD`.
     */
    payload?: unknown;
    /**
     *  Extra CSS classes to be added to the snack bar container.
     */
    extraCssClasses?: string[];
}

@Component({
    templateUrl: './ui-snackbar.component.html',
    styleUrls: [
        './ui-snackbar.component.scss',
    ],
    encapsulation: ViewEncapsulation.None,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiSnackBarComponent {
    private _componentPortal?: ComponentPortal<unknown>;

    get componentPortal() {
        if (typeof this.data.message !== 'function') {
            throw new Error('`componentPortal` getter cannot be used when `data.message` is not a component type');
        }

        this._componentPortal ??= new ComponentPortal(
            this.data.message,
            null,
            Injector.create({
                providers: [
                    {
                        provide: UI_MAT_SNACK_BAR_PAYLOAD,
                        useValue: this.data.payload,
                    },
                ],
                parent: this._injector,

            }),
        );

        return this._componentPortal;
    }

    constructor(
        @Inject(MAT_SNACK_BAR_DATA)
        public data: ISnackBarAlert,
        public snackBarRef: MatSnackBarRef<UiSnackBarComponent>,
        private _injector: Injector,
    ) { }

    /**
     * @internal
     * @ignore
     */
    isPropertyString(property: unknown): property is string {
        return typeof property === 'string';
    }

    /**
     * @internal
     * @ignore
     */
    isTemplateRef(property: unknown): property is TemplateRef<unknown> {
        return property instanceof TemplateRef;
    }

    /**
     * @internal
     * @ignore
     */
    isComponentType(property: unknown): property is ComponentType<unknown> {
        return typeof property === 'function';
    }
}

export enum SnackBarType {
    Info = 'info',
    Error = 'error',
    Warning = 'warning',
    Success = 'success',
    None = 'none',
}

export const ICON_MAP: Map<SnackBarType, string> = new Map([
    [SnackBarType.Info, 'info'],
    [SnackBarType.Success, 'check_circle'],
    [SnackBarType.Warning, 'warning'],
    [SnackBarType.Error, 'error'],
]);
export type SnackbarAction = (
    message: string | TemplateRef<any> | ComponentType<unknown>,
    config?: {
        actionMessage?: string;
        duration?: number;
        payload?: unknown;
        extraCssClasses?: string[];
    },
) => MatSnackBarRef<UiSnackBarComponent>;

export class UiMatSnackBarConfig {
    restrictHtml = false;
}

export const UI_MAT_SNACK_BAR_DEFAULT_OPTIONS = new InjectionToken<UiMatSnackBarConfig>('UiMatSnackBarConfig');
export const UI_MAT_SNACK_BAR_PAYLOAD = new InjectionToken('UiSnackBarService Payload');

/**
 * Snackbar config options
 */
interface ISnackBarOptions extends Partial<Exclude<ISnackBarAlert, 'closeAriaLabel' | 'message'>> {
    /**
     * The type of the alert (info, success, warning or error)
     */
    type?: SnackBarType;
}

export const panelClass = (type: SnackBarType) =>
    `ui-snackbar-${type}`;

@Injectable({
    providedIn: 'root',
})
export class UiSnackBarService {

    /**
     * Display an info snackbar
     *
     * @param message The message to be displayed
     * @param config  `{ actionMessage, duration }`
     */
    info: SnackbarAction;
    /**
     * Display an error snackbar
     *
     * @param message The message to be displayed
     * @param config  `{ actionMessage, duration }`
     */
    error: SnackbarAction;
    /**
     * Display an success snackbar
     *
     * @param message The message to be displayed
     * @param config  `{ actionMessage, duration }`
     */
    success: SnackbarAction;
    /**
     * Display an warning snackbar
     *
     * @param message The message to be displayed
     * @param config  `{ actionMessage, duration }`
     */
    warning: SnackbarAction;
    private _ref?: MatSnackBarRef<UiSnackBarComponent>;

    constructor(
        private _snackBar: MatSnackBar,
        @Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS)
        private _options: MatSnackBarConfig,
        @Optional()
        private readonly _snackIntl: UiSnackbarIntl,
        @Inject(UI_MAT_SNACK_BAR_DEFAULT_OPTIONS)
        @Optional()
        private _additionalOptions?: UiMatSnackBarConfig,
    ) {
        this._snackIntl = this._snackIntl ||
            new UiSnackbarIntl();

        this.info = this._alertFactory(SnackBarType.Info);
        this.error = this._alertFactory(SnackBarType.Error);
        this.success = this._alertFactory(SnackBarType.Success);
        this.warning = this._alertFactory(SnackBarType.Warning);
    }

    /**
     * Display a snackbar (customizable)
     *
     * @param message The message to be displayed
     * @param options Customize default options: snackbar type, icon, display duration, and action message
     */
    show = (
        message: string | TemplateRef<any> | ComponentType<unknown>,
        { type, duration, icon, actionMessage, payload, extraCssClasses }: ISnackBarOptions = {},
    ) =>
        this._alert(type ?? SnackBarType.None, {
            message,
            icon: icon ?? ICON_MAP.get(type!),
            duration: duration || duration === 0 ? duration : this._options.duration!,
            actionMessage,
            payload,
            extraCssClasses,
        });

    /**
     * Dismiss the displayed snackbar
     */
    clear() {
        if (!this._ref) { return; }

        this._ref.dismiss();
    }

    private _alertFactory = (type: SnackBarType) =>
        (
            message: string | TemplateRef<any> | ComponentType<unknown>,
            config?: { actionMessage?: string; duration?: number; payload?: unknown; extraCssClasses?: string[] }) =>
            this._alert(type, {
                message,
                actionMessage: config?.actionMessage,
                icon: ICON_MAP.get(type),
                duration: config?.duration || config?.duration === 0
                    ? config.duration
                    : this._options.duration!,
                payload: config?.payload,
                extraCssClasses: config?.extraCssClasses,
            });

    private _alert(type: SnackBarType, options: ISnackBarAlert) {
        if (
            this._additionalOptions?.restrictHtml &&
            typeof options.message === 'string'
        ) {
            const span = document.createElement('span');
            span.innerText = options.message;
            options.message = span.innerHTML;
            span.remove();
        }

        const extraPanelClasses = options.extraCssClasses ?? [];
        this._ref = this._snackBar.openFromComponent(UiSnackBarComponent, {
            data: {
                closeAriaLabel: this._snackIntl.closeSnackbarShortcut,
                ...options,
            },
            duration: options.duration,
            panelClass: [panelClass(type), ...extraPanelClasses],
        });

        return this._ref;
    }
}
<div class="ui-snackbar-container">
    <div class="ui-snackbar-message">
        <mat-icon *ngIf="data.icon">{{data.icon}}</mat-icon>
        <span *ngIf="isPropertyString(data.message)"
              [innerHTML]="data.message | nl2br"></span>
        <ng-container *ngIf="isTemplateRef(data.message)"
                      [ngTemplateOutlet]="data.message"
                      [ngTemplateOutletContext]="{ $implicit: snackBarRef }"></ng-container>
        <ng-template *ngIf="isComponentType(data.message)"
                     [cdkPortalOutlet]="componentPortal"></ng-template>
    </div>

    <div class="ui-snackbar-dismiss">
        <button *ngIf="data.actionMessage"
                (click)="snackBarRef.dismissWithAction()"
                class="ui-snackbar-action"
                type="button"
                mat-button>
            {{ data.actionMessage }}
        </button>

        <button [attr.aria-label]="data.closeAriaLabel"
                (click)="snackBarRef.dismiss()"
                [shortcutKeys]="[['Delete', 'x']]"
                (shortcutPressed)="snackBarRef.dismiss()"
                uiKeyboardShortcut
                class="ui-snackbar-close"
                type="button"
                mat-icon-button>
            <mat-icon>close</mat-icon>
        </button>
    </div>
</div>

./ui-snackbar.component.scss

.ui-snackbar {
    &-container {
        display: flex;
        justify-content: space-between;
    }

    &-message,
    &-dismiss {
        display: flex;
        align-items: center;
    }

    &-action {
        margin-right: 10px;
    }

    &-message {
        mat-icon {
            margin-right: 10px;
        }
        span {
            overflow-y: auto;
            max-height: calc(100vh - 80px);
            font-size: 14.5px;
            line-height: 1.35em;
            word-break: break-word;
        }
    }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""