import { ComponentFactory , ComponentFactoryResolver } from '@angular/core' ; import { Location } from '@angular/common' ; import { App } from '../components/app/app' ; import { DIRECTION_BACK , NavLink , NavSegment , TransitionDoneFn , convertToViews , isNav , isTab , isTabs } from './nav-util' ; import { ModuleLoader } from '../util/module-loader' ; import { isArray , isPresent } from '../util/util' ; import { Tab , Tabs } from './nav-interfaces' ; import { NavigationContainer } from './navigation-container' ; import { NavController } from './nav-controller' ; import { UrlSerializer , formatUrlPart } from './url-serializer' ; import { ViewController } from './view-controller' ; hidden export class DeepLinker { internal _history : string [ ] = [ ] ; internal _indexAliasUrl : string ; constructor ( public _app , public _serializer , public _location , public _moduleLoader , public _baseCfr ) { } internal init ( ) { const browserUrl = normalizeUrl ( this . _location . path ( ) ) ; console . debug ( `DeepLinker, init load: ${ browserUrl }` ) ; this . _historyPush ( browserUrl ) ; this . _location . subscribe ( ( locationChg ) => { this . _urlChange ( normalizeUrl ( locationChg . url ) ) ; } ) ; } internal _urlChange ( browserUrl ) { if ( ! this . _isCurrentUrl ( browserUrl ) ) { let isGoingBack = true ; if ( this . _isBackUrl ( browserUrl ) ) { console . debug ( `DeepLinker, browser urlChange, back to: ${ browserUrl }` ) ; this . _historyPop ( ) ; } else { isGoingBack = false ; console . debug ( `DeepLinker, browser urlChange, forward to: ${ browserUrl }` ) ; this . _historyPush ( browserUrl ) ; } const activeNavContainers = this . _app . getActiveNavContainers ( ) ; if ( activeNavContainers && activeNavContainers . length ) { if ( browserUrl === '/' ) { if ( isPresent ( this . _indexAliasUrl ) ) { browserUrl = this . _indexAliasUrl ; } else { activeNavContainers . forEach ( ( navContainer ) => { navContainer . goToRoot ( { updateUrl : false , isNavRoot : true } ) ; } ) ; return ; } } const segments = this . getCurrentSegments ( browserUrl ) ; segments . map ( segment => { for ( const navContainer of activeNavContainers ) { const nav = getNavFromTree ( navContainer , segment . navId ) ; if ( nav ) { return { segment : segment , navContainer : nav } ; } } } ) . filter ( pair => ! ! pair ) . forEach ( pair => { this . _loadViewForSegment ( pair . navContainer , pair . segment , ( ) => { } ) ; } ) ; } } } getCurrentSegments ( browserUrl ? ) { if ( ! browserUrl ) { browserUrl = normalizeUrl ( this . _location . path ( ) ) ; } return this . _serializer . parse ( browserUrl ) ; } internal navChange ( direction ) { if ( direction ) { const activeNavContainers = this . _app . getActiveNavContainers ( ) ; for ( const activeNavContainer of activeNavContainers ) { if ( isTabs ( activeNavContainer ) || ( activeNavContainer as NavController ) . isTransitioning ( ) ) { return ; } } let segments = [ ] ; const navContainers = this . _app . getRootNavs ( ) ; for ( const navContainer of navContainers ) { const segmentsForNav = this . getSegmentsFromNav ( navContainer ) ; segments = segments . concat ( segmentsForNav ) ; } segments = segments . filter ( segment => ! ! segment ) ; if ( segments . length ) { const browserUrl = this . _serializer . serialize ( segments ) ; this . _updateLocation ( browserUrl , direction ) ; } } } getSegmentsFromNav ( nav ) { let segments = [ ] ; if ( isNav ( nav ) ) { segments . push ( this . getSegmentFromNav ( nav as NavController ) ) ; } else if ( isTab ( nav ) ) { segments . push ( this . getSegmentFromTab ( nav ) ) ; } nav . getActiveChildNavs ( ) . forEach ( child => { segments = segments . concat ( this . getSegmentsFromNav ( child ) ) ; } ) ; return segments ; } getSegmentFromNav ( nav , component ? , data ? ) { if ( ! component ) { const viewController = nav . getActive ( true ) ; if ( viewController ) { component = viewController . component ; data = viewController . data ; } } return this . _serializer . serializeComponent ( nav , component , data ) ; } getSegmentFromTab ( navContainer , component ? , data ? ) { if ( navContainer && navContainer . parent ) { const tabsNavContainer = navContainer . parent as NavigationContainer ; const activeChildNavs = tabsNavContainer . getActiveChildNavs ( ) ; if ( activeChildNavs && activeChildNavs . length ) { const activeChildNav = activeChildNavs [ 0 ] ; const viewController = ( activeChildNav as NavController ) . getActive ( true ) ; if ( viewController ) { component = viewController . component ; data = viewController . data ; } return this . _serializer . serializeComponent ( tabsNavContainer , component , data ) ; } } } internal _updateLocation ( browserUrl , direction ) { if ( this . _indexAliasUrl === browserUrl ) { browserUrl = '/' ; } if ( direction === DIRECTION_BACK && this . _isBackUrl ( browserUrl ) ) { console . debug ( `DeepLinker, location.back(), url: '${ browserUrl }'` ) ; this . _historyPop ( ) ; this . _location . back ( ) ; } else if ( ! this . _isCurrentUrl ( browserUrl ) ) { console . debug ( `DeepLinker, location.go('${ browserUrl }')` ) ; this . _historyPush ( browserUrl ) ; this . _location . go ( browserUrl ) ; } } getComponentFromName ( componentName ) { const link = this . _serializer . getLinkFromName ( componentName ) ; if ( link ) { return this . getNavLinkComponent ( link ) ; } return Promise . reject ( `invalid link: ${ componentName }` ) ; } getNavLinkComponent ( link ) { if ( link . component ) { return Promise . resolve ( link . component ) ; } if ( link . loadChildren ) { return this . _moduleLoader . load ( link . loadChildren ) . then ( ( response ) => { link . component = response . component ; return response . component ; } ) ; } return Promise . reject ( `invalid link component: ${ link . name }` ) ; } internal resolveComponent ( component ) { let cfr = this . _moduleLoader . getComponentFactoryResolver ( component ) ; if ( ! cfr ) { cfr = this . _baseCfr ; } return cfr . resolveComponentFactory ( component ) ; } internal createUrl ( navContainer , nameOrComponent , _data , prepareExternalUrl = true ) { const segment = this . _serializer . createSegmentFromName ( navContainer , nameOrComponent ) ; const allSegments = this . getCurrentSegments ( ) ; if ( segment ) { for ( let i = 0 ; i < allSegments . length ; i ++ ) { if ( allSegments [ i ] . navId === navContainer . name || allSegments [ i ] . navId === navContainer . id ) { allSegments [ i ] = segment ; const url = this . _serializer . serialize ( allSegments ) ; return prepareExternalUrl ? this . _location . prepareExternalUrl ( url ) : url ; } } } return '' ; } internal getSegmentByNavIdOrName ( navId , name ) { const browserUrl = normalizeUrl ( this . _location . path ( ) ) ; const segments = this . _serializer . parse ( browserUrl ) ; for ( const segment of segments ) { if ( segment . navId === navId || segment . navId === name ) { return segment ; } } return null ; } internal initViews ( segment ) { const link = this . _serializer . getLinkFromName ( segment . name ) ; return this . getNavLinkComponent ( link ) . then ( ( component ) => { segment . component = component ; const view = new ViewController ( component , segment . data ) ; view . id = segment . id ; if ( isArray ( segment . defaultHistory ) ) { return convertToViews ( this , segment . defaultHistory ) . then ( views => { views . push ( view ) ; return views ; } ) ; } return [ view ] ; } ) ; } internal _isBackUrl ( browserUrl ) { return ( browserUrl === this . _history [ this . _history . length - 2 ] ) ; } internal _isCurrentUrl ( browserUrl ) { return ( browserUrl === this . _history [ this . _history . length - 1 ] ) ; } internal _historyPush ( browserUrl ) { if ( ! this . _isCurrentUrl ( browserUrl ) ) { this . _history . push ( browserUrl ) ; if ( this . _history . length > 30 ) { this . _history . shift ( ) ; } } } internal _historyPop ( ) { this . _history . pop ( ) ; if ( ! this . _history . length ) { this . _historyPush ( this . _location . path ( ) ) ; } } internal _getTabSelector ( tab ) { if ( isPresent ( tab . tabUrlPath ) ) { return tab . tabUrlPath ; } if ( isPresent ( tab . tabTitle ) ) { return formatUrlPart ( tab . tabTitle ) ; } return `tab-${ tab . index }` ; } internal _loadViewForSegment ( navContainer , segment , done ) { if ( ! segment ) { return done ( false , false ) ; } if ( isTabs ( navContainer ) || ( isTab ( navContainer ) && navContainer . parent ) ) { const tabs = < Tabs > < any > ( isTabs ( navContainer ) ? navContainer : navContainer . parent ) ; const selectedIndex = tabs . _getSelectedTabIndex ( segment . secondaryId ) ; const tab = tabs . getByIndex ( selectedIndex ) ; tab . _segment = segment ; tabs . select ( tab , { updateUrl : false , animate : false } , true ) ; return done ( false , false ) ; } const navController = < NavController > < any > navContainer ; const numViews = navController . length ( ) - 1 ; for ( let i = numViews ; i >= 0 ; i -- ) { const viewController = navController . getByIndex ( i ) ; if ( viewController && ( viewController . id === segment . id || viewController . id === segment . name ) ) { if ( i === numViews ) { return done ( false , false ) ; } else { return navController . popTo ( viewController , { animate : false , updateUrl : false , } , done ) ; } } } return navController . setRoot ( segment . component || segment . name , segment . data , { id : segment . id , animate : false , updateUrl : false } , done ) ; } } export function setupDeepLinker ( app , serializer , location , moduleLoader , cfr ) { const deepLinker = new DeepLinker ( app , serializer , location , moduleLoader , cfr ) ; deepLinker . init ( ) ; return deepLinker ; } export function normalizeUrl ( browserUrl ) { browserUrl = browserUrl . trim ( ) ; if ( browserUrl . charAt ( 0 ) !== '/' ) { browserUrl = '/' + browserUrl ; } if ( browserUrl . length > 1 && browserUrl . charAt ( browserUrl . length - 1 ) === '/' ) { browserUrl = browserUrl . substr ( 0 , browserUrl . length - 1 ) ; } return browserUrl ; } export function getNavFromTree ( nav , id ) { while ( nav ) { if ( nav . id === id || nav . name === id ) { return nav ; } nav = nav . parent ; } return null ; }