projects/core/src/lib/action-abstract/action-abstract.ts
ActionAbstract
is extended by all action implementations
Provides all the behaviors, shared between each action
e.g. title, icon, visibility, disabled, active state
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;
}
}
Properties |
Methods |
constructor(options: Options, component?: Type<ActionAbstractComponentImpl>)
|
||||||||||||
Abstract action
Parameters :
|
Readonly ariaLabel$ |
Type : Observable<string>
|
|
Readonly disabled$ |
Type : Observable<boolean>
|
|
Abstract Readonly fire$ |
Type : Observable<FireEvent>
|
Abstract property, holding |
Readonly icon$ |
Type : Observable<string>
|
|
Readonly state$ |
Type : Observable<ActionState>
|
|
Readonly title$ |
Type : Observable<string>
|
|
Readonly visible$ |
Type : Observable<boolean>
|
|
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 |
disable |
disable()
|
Will disable action, if prevously enabled |
enable |
enable()
|
Will enable action, if prevously disabled |
getAriaLabel |
getAriaLabel()
|
Returns current action ariaLabel
Returns :
string
|
getIcon |
getIcon()
|
Returns current action icon
Returns :
string
|
getParent |
getParent()
|
Returns current parent of the action
Returns :
ActionGroup | undefined
|
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
Returns :
boolean
|
isDestroyed |
isDestroyed()
|
Returns boolean defining whether action has state
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
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 :
|
setIcon | ||||||||
setIcon(icon: string)
|
||||||||
Will set the new icon and notify all icon subscriptions
Parameters :
|
setTitle | ||||||||
setTitle(title: string)
|
||||||||
Will set the new title and notify all title subscriptions
Parameters :
|
setVisibility | ||||||||
setVisibility(visibility: boolean)
|
||||||||
Will show or hide the action depending from the provided visibility boolean
Parameters :
|
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 |
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;
}
}