File

projects/core/src/lib/action-abstract/action-abstract.ts

Description

ActionAbstract is extended by all action implementations Provides all the behaviors, shared between each action e.g. title, icon, visibility, disabled, active state

Example

export class ActionCustom extends ActionAbstract<ActionCustomOptions, ActionCustomEvent> {
// Abstract properties need to be implemented by derived class
fire$: Observable<ActionCustomEvent>;
changes$: Observable<ActionCustomOptions>;
// A custom observable, specific to this action implementation
custom$: Observable<number>;

// A custom subject that is used to bridge reactive and non reactive world
protected custom: Subject<number>;

constructor(options: ActionCustomOptions,
component?: Type<ActionCustomComponentImpl>) {
this.fire = new Subject();
this.custom = new Subject();

this.fire$ = this.handleLivecycle(this.fire.asObservable(), false);
this.custom$ = this.handleLivecycle(this.custom.asObservable());
this.changes$ = this.handleLivecycle(Observable.merge(
this.title$.pipe(map(title => (<ActionCustomOptions>{ title }))),
this.icon$.pipe(map(icon => (<ActionCustomOptions>{ icon }))),
this.visible$.pipe(map(visible => (<ActionCustomOptions>{ visible }))),
this.disabled$.pipe(map(disabled => (<ActionCustomOptions>{ disabled }))),
this.custom$.pipe(map(custom => (<ActionCustomOptions>{ custom })))
));
}

// Abstract method trigger needs to be implemented by every derived class
trigger(): this {
this.fire.next({ action: this });
return this;
}

// A custom method to trigger custom subject and custom$ observable
fireCustom(): this {
this.custom.next(Math.random());
return this;
}
}

Index

Properties
Methods

Constructor

constructor(options: Options, component?: Type<ActionAbstractComponentImpl>)

Abstract action constructor. It will:

  • Extend default options with derived default options and custom options
  • Create all private subjects that are used to leverage reactive world with non reactive
  • Create observable for each private subject
  • Assign forced component, that is going to be used by ActionOutletDirective
Parameters :
Name Type Optional Description
options Options No

Options for ActionAbstract

component Type<ActionAbstractComponentImpl> Yes

Optional custom Component

Properties

Readonly ariaLabel$
Type : Observable<string>

Observable that notifies subscribers when the ariaLabel changes.

Abstract Readonly changes$
Type : Observable<Options>

Abstract property, holding Observable Each derived class should implement it's own changes$ observable, merging all public observables, notifying for every change doen to action

Readonly disabled$
Type : Observable<boolean>

Observable that notifies subscriptions when disabled state changes

Abstract Readonly fire$
Type : Observable<FireEvent>

Abstract property, holding Observable Each derived class should implement it's own fire$ observable, with it's own specific implementation

Readonly icon$
Type : Observable<string>

Observable that notifies subscriptions when icon changes

Readonly state$
Type : Observable<ActionState>

Observable that notifies subscriptions when action state changes e.g. Active, Inactive, Destroyed

Readonly title$
Type : Observable<string>

Observable that notifies subscriptions when title changes

Readonly visible$
Type : Observable<boolean>

Observable that notifies subscriptions when visibility state changes (visible or hidden)

Methods

activate
activate()

Will activate all observables in current action, unless action is already destroyed

deactivate
deactivate()

Will deactivate all observables in current action, unless action is already destroyed

destroy
destroy()

Will set action state to Destroyed, which will complete all observables

disable
disable()

Will disable action, if prevously enabled

enable
enable()

Will enable action, if prevously disabled

getAriaLabel
getAriaLabel()

Returns current action ariaLabel

Returns : string
getForcedComponent
getForcedComponent()

Returns a Component, that is provided as forced component via action constructor This component should be used by ActionOutletDirective, to represent the action in DOM, instead the component, provided via Angular Injector

Returns : Type | undefined
getIcon
getIcon()

Returns current action icon

Returns : string
getParent
getParent()

Returns current parent of the action

getTitle
getTitle()

Returns current action title

Returns : string
hide
hide()

Will nide the action, if previously visible

isActive
isActive()

Returns boolean defining whether action has state ActionState.Active

Returns : boolean
isDestroyed
isDestroyed()

Returns boolean defining whether action has state ActionState.Destroyed

Returns : boolean
isDisabled
isDisabled()

Returns boolean defining whether action is disabled

Returns : boolean
isEnabled
isEnabled()

Returns boolean defining whether action is enabled

Returns : boolean
isHidden
isHidden()

Returns boolean defining whether action is hidden

Returns : boolean
isInactive
isInactive()

Returns boolean defining whether action has state ActionState.Inactive

Returns : boolean
isVisible
isVisible()

Returns boolean defining whether action is visible

Returns : boolean
setAriaLabel
setAriaLabel(ariaLabel: string)

Will set the new ariaLabel and notify all ariaLabel subscribers

Parameters :
Name Type Optional Description
ariaLabel string No

The new action title

setIcon
setIcon(icon: string)

Will set the new icon and notify all icon subscriptions

Parameters :
Name Type Optional Description
icon string No

The new action icon

setTitle
setTitle(title: string)

Will set the new title and notify all title subscriptions

Parameters :
Name Type Optional Description
title string No

The new action title

setVisibility
setVisibility(visibility: boolean)

Will show or hide the action depending from the provided visibility boolean

Parameters :
Name Type Optional Description
visibility boolean No

The new visibility

show
show()

Will show the action, if previously hidden

Abstract trigger
trigger()

Abstract method trigger should be implemented by each derived class, in combination with fire subject and fire$ observable

import { Type } from '@angular/core';
import { BehaviorSubject, Observable, NEVER } from 'rxjs';
import { distinctUntilChanged, filter, switchMap, takeUntil } from 'rxjs/operators';

import {
  ActionAbstractComponentImpl,
  ActionAbstractEvent,
  ActionAbstractOptions,
} from '../action-abstract/action-abstract.model';
import { ActionGroup } from '../action-group/action-group';

/**
 * Default options for `ActionAbstract` - shared between **all** actions
 * Extended by provided options in action `constructor`
 */
const defaultAbstractOptions: Required<ActionAbstractOptions> = {
  title: '',
  ariaLabel: '',
  icon: '',
  visible: true,
  disabled: false,
};

/**
 * The state of the action
 * Can be `Active`, `Inactive` or `Destroyed`
 */
export const enum ActionState {
  Active,
  Inactive,
  Destroyed,
}

/**
 * @internal
 *
 * Used to uniquelly identify the action
 */
let increment = 0;

/**
 * `ActionAbstract` is extended by **all** action implementations
 * Provides all the behaviors, shared between **each** action
 * *e.g. title, icon, visibility, disabled, active state*
 *
 * ## Example
 *
```typescript
export class ActionCustom extends ActionAbstract<ActionCustomOptions, ActionCustomEvent> {
    // Abstract properties need to be implemented by derived class
    fire$: Observable<ActionCustomEvent>;
    changes$: Observable<ActionCustomOptions>;
    // A custom observable, specific to this action implementation
    custom$: Observable<number>;

    // A custom subject that is used to bridge reactive and non reactive world
    protected custom: Subject<number>;

    constructor(options: ActionCustomOptions,
                component?: Type<ActionCustomComponentImpl>) {
        this.fire = new Subject();
        this.custom = new Subject();

        this.fire$ = this.handleLivecycle(this.fire.asObservable(), false);
        this.custom$ = this.handleLivecycle(this.custom.asObservable());
        this.changes$ = this.handleLivecycle(Observable.merge(
            this.title$.pipe(map(title => (<ActionCustomOptions>{ title }))),
            this.icon$.pipe(map(icon => (<ActionCustomOptions>{ icon }))),
            this.visible$.pipe(map(visible => (<ActionCustomOptions>{ visible }))),
            this.disabled$.pipe(map(disabled => (<ActionCustomOptions>{ disabled }))),
            this.custom$.pipe(map(custom => (<ActionCustomOptions>{ custom })))
        ));
    }

    // Abstract method trigger needs to be implemented by every derived class
    trigger(): this {
        this.fire.next({ action: this });
        return this;
    }

    // A custom method to trigger custom subject and custom$ observable
    fireCustom(): this {
        this.custom.next(Math.random());
        return this;
    }
}
```
 */
export abstract class ActionAbstract<
  Options extends ActionAbstractOptions,
  FireEvent extends ActionAbstractEvent | null,
> {
  /**
   * @internal
   *
   * Used to uniquelly identify the action
   */
  readonly _actionId = increment++;
  /**
   * `Observable` that notifies subscriptions when title changes
   */
  readonly title$: Observable<string>;
  /**
   * `Observable` that notifies subscriptions when icon changes
   */
  readonly icon$: Observable<string>;
  /**
   * `Observable` that notifies subscriptions when visibility state changes
   * (visible or hidden)
   */
  readonly visible$: Observable<boolean>;
  /**
   * `Observable` that notifies subscriptions when disabled state changes
   */
  readonly disabled$: Observable<boolean>;
  /**
   * `Observable` that notifies subscriptions when action state changes
   * e.g. `Active`, `Inactive`, `Destroyed`
   */
  readonly state$: Observable<ActionState>;
  /**
   * `Observable` that notifies subscribers when the ariaLabel changes.
   */
  readonly ariaLabel$: Observable<string>;
  /**
   * **Abstract** property, holding `Observable`
   * Each derived class **should** implement it's own `fire$` observable,
   * with it's own specific implementation
   */
  abstract readonly fire$: Observable<FireEvent>;
  /**
   * **Abstract** property, holding `Observable`
   * Each derived class **should** implement it's own `changes$` observable,
   * merging all public observables, notifying for every change doen to action
   */
  abstract readonly changes$: Observable<Options>;

  /**
   * Options of action instance
   * **Merged** default options from derived class, default options from abstract class,
   * and options provided on action creation to `constructor`
   */
  protected readonly options: Options;
  /**
   * `Component`, provided to action `constructor`
   * Should be forced and used instead of component in `Injector`
   * That is handled by `ActionOutletDirective`
   */
  protected readonly forcedComponent?: Type<ActionAbstractComponentImpl>;
  /**
   * Title `BehaviorSubject`, used **internally** to notify on title change
   * Used to leverage reactive world with non reactive
   */
  protected readonly title: BehaviorSubject<string>;
  /**
   * Icon `BehaviorSubject`, used **internally** to notify on icon change
   * Used to leverage reactive world with non reactive
   */
  protected readonly icon: BehaviorSubject<string>;
  /**
   * Visibility `BehaviorSubject`, used **internally** to notify on visibility state change
   * Used to leverage reactive world with non reactive
   */
  protected readonly visible: BehaviorSubject<boolean>;
  /**
   * Desabled state `BehaviorSubject`, used **internally** to notify on disabled state change
   * Used to leverage reactive world with non reactive
   */
  protected readonly disabled: BehaviorSubject<boolean>;
  /**
   * Action livecycle state `BehaviorSubject`, used **internally** to notify on action state change
   * Used to leverage reactive world with non reactive
   */
  protected readonly state: BehaviorSubject<ActionState>;
  /**
   * `Observable` that fires, when state matches `ActionState.Destroyed`
   * Used to complete all **internal** subjects
   */
  protected readonly finish: Observable<ActionState>;
  /**
   * `BehaviorSubject`, used to notify subscribers on aria label changes.
   */
  protected readonly _ariaLabel$: BehaviorSubject<string>;

  /**
   * Parent of current action. This is a parent action,
   * to whom current action belongs to, and renders into
   */
  private parent?: ActionGroup;

  /**
   * Abstract action `constructor`. It will:
   * - **Extend** default options with derived default options and custom options
   * - **Create** all private subjects that are used to leverage reactive world with non reactive
   * - **Create** observable for each private subject
   * - **Assign** forced component, that is going to be used by `ActionOutletDirective`
   *
   * @param options Options for `ActionAbstract`
   * @param component Optional custom `Component`
   */
  constructor(options: Options, component?: Type<ActionAbstractComponentImpl>) {
    const { title, icon, visible, disabled, ariaLabel } = (this.options = { ...defaultAbstractOptions, ...options });

    this.title = new BehaviorSubject(title);
    this.icon = new BehaviorSubject(icon);
    this.visible = new BehaviorSubject(visible);
    this.disabled = new BehaviorSubject(disabled);
    this._ariaLabel$ = new BehaviorSubject(ariaLabel);
    this.state = new BehaviorSubject(<ActionState>ActionState.Inactive);
    this.finish = this.state.pipe(filter(state => state === ActionState.Destroyed));

    this.title$ = this.handleLivecycleDistinct(this.title.asObservable());
    this.icon$ = this.handleLivecycleDistinct(this.icon.asObservable());
    this.ariaLabel$ = this.handleLivecycleDistinct(this._ariaLabel$.asObservable());
    this.visible$ = this.handleLivecycleDistinct(this.visible.asObservable());
    this.disabled$ = this.handleLivecycleDistinct(this.disabled.asObservable());
    this.state$ = this.state.asObservable().pipe(distinctUntilChanged());

    this.forcedComponent = component;
  }

  /**
   * Abstract method trigger should be implemented by **each** derived class, in
   * combination with `fire` subject and `fire$` observable
   */
  abstract trigger(): this;

  /**
   * Used **internally** to handle livecycle of observables
   * It will handle action state(`Active`, `Inactive` - **paused**, `Destroyed`),
   * and will notify **only** when value or reference **changes**
   *
   * @param observable `Observable` to handle live cycle
   * @param shouldPause Defining, whether it should be possible to pause provided observable. True by default
   */
  protected handleLivecycleDistinct<T>(observable: Observable<T>, shouldPause?: boolean): Observable<T> {
    return this.handleLivecycle(observable, shouldPause).pipe(distinctUntilChanged());
  }

  /**
   * Used internally to handle livecycle of observables
   * It will handle action state(`Active`, `Inactive` - **paused**, `Destroyed`)
   *
   * @param observable `Observable` to handle live cycle
   * @param shouldPause Defining, whether it should be possible to pause provided observable. True by default
   */
  protected handleLivecycle<T>(observable: Observable<T>, shouldPause: boolean = true): Observable<T> {
    const source = observable.pipe(takeUntil(this.finish));

    if (!shouldPause) {
      return source;
    }

    return this.handleActivateState(source);
  }

  /**
   * Used **internally** to handle pausing of observables
   * Will deactivate observable, whenever state of the action changes to `Inactive`,
   * and will activate observable again, when it switches back to `Active`
   *
   * @param observable `Observable` to handle pausing
   */
  protected handleActivateState<T>(observable: Observable<T>): Observable<T> {
    return this.state.pipe(switchMap(state => (state === ActionState.Inactive ? NEVER : observable)));
  }

  /**
   * Will **activate** all observables in current action,
   * **unless action is already destroyed**
   */
  activate(): this {
    if (this.isDestroyed()) {
      return this;
    }

    this.state.next(ActionState.Active);
    return this;
  }

  /**
   * Will **deactivate** all observables in current action,
   * **unless action is already destroyed**
   */
  deactivate(): this {
    if (this.isDestroyed()) {
      return this;
    }

    this.state.next(ActionState.Inactive);
    return this;
  }

  /**
   * Will set action state to `Destroyed`, which will
   * complete all observables
   */
  destroy(): this {
    this.state.next(ActionState.Destroyed);
    this.state.complete();
    return this;
  }

  /**
   * Returns boolean defining whether action has state `ActionState.Active`
   */
  isActive(): boolean {
    return this.state.getValue() === ActionState.Active;
  }

  /**
   * Returns boolean defining whether action has state `ActionState.Inactive`
   */
  isInactive(): boolean {
    return this.state.getValue() === ActionState.Inactive;
  }

  /**
   * Returns boolean defining whether action has state `ActionState.Destroyed`
   */
  isDestroyed(): boolean {
    return this.state.getValue() === ActionState.Destroyed;
  }

  /**
   * Will set the new title and notify all title subscriptions
   *
   * @param title The new action title
   */
  setTitle(title: string): this {
    this.title.next(title);
    return this;
  }

  /**
   * Returns current action title
   */
  getTitle(): string {
    return this.title.getValue();
  }

  /**
   * Will set the new ariaLabel and notify all ariaLabel subscribers
   *
   * @param ariaLabel The new action title
   */
  setAriaLabel(ariaLabel: string): this {
    this._ariaLabel$.next(ariaLabel);
    return this;
  }

  /**
   * Returns current action ariaLabel
   */
  getAriaLabel(): string {
    return this._ariaLabel$.getValue();
  }

  /**
   * Will set the new icon and notify all icon subscriptions
   *
   * @param icon The new action icon
   */
  setIcon(icon: string): this {
    this.icon.next(icon);
    return this;
  }

  /**
   * Returns current action icon
   */
  getIcon(): string {
    return this.icon.getValue();
  }

  /**
   * Will show the action, **if previously hidden**
   */
  show(): this {
    this.visible.next(true);
    return this;
  }

  /**
   * Will nide the action, **if previously visible**
   */
  hide(): this {
    this.visible.next(false);
    return this;
  }

  /**
   * Will show or hide the action depending from the provided visibility boolean
   *
   * @param visibility The new visibility
   */
  setVisibility(visibility: boolean): this {
    this.visible.next(visibility);
    return this;
  }

  /**
   * Returns boolean defining whether action is visible
   */
  isVisible(): boolean {
    return this.visible.getValue();
  }

  /**
   * Returns boolean defining whether action is hidden
   */
  isHidden(): boolean {
    return !this.visible.getValue();
  }

  /**
   * Will enable action, **if prevously disabled**
   */
  enable(): this {
    this.disabled.next(false);
    return this;
  }

  /**
   * Will disable action, **if prevously enabled**
   */
  disable(): this {
    this.disabled.next(true);
    return this;
  }

  /**
   * Returns boolean defining whether action is disabled
   */
  isDisabled(): boolean {
    return this.disabled.getValue();
  }

  /**
   * Returns boolean defining whether action is enabled
   */
  isEnabled(): boolean {
    return !this.disabled.getValue();
  }

  /**
   * Returns a `Component`, that is provided as forced component via action `constructor`
   * This component should be used by `ActionOutletDirective`, to represent
   * the action in DOM, instead the component, provided via Angular `Injector`
   */
  getForcedComponent(): Type<ActionAbstractComponentImpl> | undefined {
    return this.forcedComponent;
  }

  /**
   * Returns current parent of the action
   */
  getParent(): ActionGroup | undefined {
    return this.parent;
  }

  /**
   * @internal
   *
   * **Internal** method to set parent of current action. It will:
   * - **Set** parent, but only if currently not defined
   * - **Disable** current action if parent is disabled
   * - **Activate** current action if parent is active
   */
  _setParent(parent: ActionGroup): this {
    if (this.parent === undefined) {
      this.parent = parent;
      if (this.parent.isDisabled()) {
        this.disable();
      }
      if (this.parent.isActive()) {
        this.activate();
      }
    }

    return this;
  }

  /**
   * @internal
   *
   * **Internal** method to unset parent of current action. It will:
   * - **Set** parent to undefined
   * - **Enable** current action
   * - **Deactivate** current action
   */
  _unsetParent(): this {
    this.parent = undefined;
    this.enable();
    this.deactivate();
    return this;
  }
}

results matching ""

    No results matching ""