import { Injectable , TemplateRef } from '@angular/core' ; import { BehaviorSubject , isObservable , Observable , Observer , of , Subject } from 'rxjs' ; import { first } from 'rxjs/operators' ; import { OverlayTrigger } from '../tooltip/index' ; import { HierarchyBarNodeChildren } from './interfaces/hierarchy-bar-node-children.interface' ; import { HierarchyBarIconContext } from './interfaces/hierarchy-bar-node-icon-context.interface' ; import { HierarchyBarNode } from './interfaces/hierarchy-bar-node.interface' ; @ Injectable ( ) export class HierarchyBarService { /** Define the list of selected nodes */ nodes$ = new BehaviorSubject < HierarchyBarNode [ ] > ( [ ] ) ; /** Define a custom loading indicator */ loadingIndicator : TemplateRef < any > ; /** Define a custom overflow template */ overflowTemplate : TemplateRef < any > ; /** Define the events that show the popover when interacting with the arrows */ popoverShowTriggers : OverlayTrigger [ ] = [ 'click' ] ; /** Define the events that hide the popover when interacting with the arrows */ popoverHideTriggers : OverlayTrigger [ ] = [ 'click' , 'clickoutside' , 'escape' ] ; /** Emit the selected node when it changes */ selection$ = new Subject < HierarchyBarNode > ( ) ; /** Define the aria label for the show siblings popover button */ showSiblingsAriaLabel : string = 'Show Siblings' ; /** Allow a custom icon template to be specified */ icon : TemplateRef < HierarchyBarIconContext > ; /** Store the root node */ private _root : HierarchyBarNode ; /** Store nodes as a flattened list */ private _nodes : HierarchyBarNode [ ] = [ ] ; /**
     * Store the root node of the hierarchy tree
     */ setRootNode ( root ) { if ( ! root ) { return ; } this . _root = root ; this . _nodes = this . getNodeList ( root ) ; this . nodes$ . next ( this . getSelectedChildren ( root ) ) ; } /**
     * Select a node. This causes all nodes to be
     * deselected and the path to the selected node
     * to be selected
     */ selectNode ( node ) { this . deselectAll ( ) ; if ( ! node ) { return ; } this . select ( node ) ; this . nodes$ . next ( this . getSelectedChildren ( this . _root ) ) ; this . selection$ . next ( node ) ; } /**
     * Handles getting children with support for both arrays and observables
     */ getChildren ( node ) { if ( Array . isArray ( node . children ) ) { return of ( { loading : false , children : node . children } ) ; } const children$ = node . children ; return Observable . create ( ( observer ) => { observer . next ( { loading : true , children : [ ] } ) ; children$ . pipe ( first ( ) ) . subscribe ( children => { node . children = children ; this . setRootNode ( this . _root ) ; observer . next ( { loading : false , children } ) ; observer . complete ( ) ; } ) ; } ) ; } /**
     * Utility function to get the sibling nodes, taking into account that
     * a node may be a root node and may not have a parent.
     */ getSiblings ( node ) { return node . parent ? this . getChildren ( node . parent ) : of ( { loading : false , children : [ ] } ) ; } /**
     * Traverses all the parents to ensure they are selected
     */ private select ( node ) { node . selected = true ; if ( node . parent ) { this . select ( node . parent ) ; } } /**
     * Deselects all nodes
     */ private deselectAll ( ) { this . _nodes . forEach ( node => node . selected = false ) ; } /**
     * Gets all the nodes in the tree as a flat array.
     * It also stores the parent node in a parent property
     * on the node for easy traversal in both directions
     */ private getNodeList ( node ) { if ( ! node . children || isObservable ( node . children ) || node . children . length === 0 ) { return [ node ] ; } node . children . forEach ( child => child . parent = node ) ; const descendants = node . children . reduce < HierarchyBarNode [ ] > ( ( nodes , current ) => [ ... nodes , ... this . getNodeList ( current ) ] , [ ] ) ; return [ node , ... descendants ] ; } /**
     * Gets all selected nodes from the parent node.
     */ private getSelectedChildren ( node ) { if ( isObservable ( node . children ) ) { return [ node ] ; } const children = node . children || [ ] ; const child = children . find ( _child => _child . selected ) ; return child ? [ node , ... this . getSelectedChildren ( child ) ] : [ node ] ; } }