UNPKG

ng2-bootstrap-base-modified

Version:

Native Angular Bootstrap Components Typeahead modified

242 lines (204 loc) 7.58 kB
// todo: add delay support // todo: merge events onShow, onShown, etc... // todo: add global positioning configuration? import { NgZone, ViewContainerRef, ComponentFactoryResolver, Injector, Renderer, ElementRef, ComponentRef, ComponentFactory, Type, TemplateRef, EventEmitter, Provider, ReflectiveInjector } from '@angular/core'; import { ContentRef } from './content-ref.class'; import { PositioningService, PositioningOptions } from '../positioning'; import { listenToTriggers } from '../utils/triggers'; export interface ListenOptions { target?: ElementRef; triggers?: string; show?: Function; hide?: Function; toggle?: Function; } export class ComponentLoader<T> { public onBeforeShow: EventEmitter<any> = new EventEmitter(); public onShown: EventEmitter<any> = new EventEmitter(); public onBeforeHide: EventEmitter<any> = new EventEmitter(); public onHidden: EventEmitter<any> = new EventEmitter(); public instance: T; public _componentRef: ComponentRef<T>; private _providers: Provider[] = []; private _componentFactory: ComponentFactory<T>; private _elementRef: ElementRef; private _zoneSubscription: any; private _contentRef: ContentRef; private _viewContainerRef: ViewContainerRef; private _injector: Injector; private _renderer: Renderer; private _ngZone: NgZone; private _componentFactoryResolver: ComponentFactoryResolver; private _posService: PositioningService; private _unregisterListenersFn: Function; public get isShown(): boolean { return !!this._componentRef; }; /** * Placement of a component. Accepts: "top", "bottom", "left", "right" */ private attachment: string; /** * A selector specifying the element the popover should be appended to. * Currently only supports "body". */ private container: string | ElementRef | any; /** * Specifies events that should trigger. Supports a space separated list of * event names. */ private triggers: string; /** * Do not use this directly, it should be instanced via * `ComponentLoadFactory.attach` * @internal * @param _viewContainerRef * @param _elementRef * @param _injector * @param _renderer * @param _componentFactoryResolver * @param _ngZone * @param _posService */ // tslint:disable-next-line public constructor(_viewContainerRef: ViewContainerRef, _renderer: Renderer, _elementRef: ElementRef, _injector: Injector, _componentFactoryResolver: ComponentFactoryResolver, _ngZone: NgZone, _posService: PositioningService) { this._ngZone = _ngZone; this._injector = _injector; this._renderer = _renderer; this._elementRef = _elementRef; this._posService = _posService; this._viewContainerRef = _viewContainerRef; this._componentFactoryResolver = _componentFactoryResolver; } public attach(compType: Type<T>): ComponentLoader<T> { this._componentFactory = this._componentFactoryResolver .resolveComponentFactory<T>(compType); return this; } // todo: add behaviour: to target element, `body`, custom element public to(container?: string): ComponentLoader<T> { this.container = container || this.container; return this; } public position(opts?: PositioningOptions): ComponentLoader<T> { this.attachment = opts.attachment || this.attachment; this._elementRef = opts.target as ElementRef || this._elementRef; return this; } public provide(provider: Provider): ComponentLoader<T> { this._providers.push(provider); return this; } public show(opts: {content?: string | TemplateRef<any>, [key:string]: any} = {}): ComponentRef<T> { this._subscribePositioning(); if (!this._componentRef) { this.onBeforeShow.emit(); this._contentRef = this._getContentRef(opts.content); const injector = ReflectiveInjector.resolveAndCreate(this._providers, this._injector); this._componentRef = this._viewContainerRef .createComponent(this._componentFactory, 0, injector, this._contentRef.nodes); this.instance = this._componentRef.instance; Object.assign(this._componentRef.instance, opts); if (this.container === 'body' && typeof document !== 'undefined') { document.querySelector(this.container as string) .appendChild(this._componentRef.location.nativeElement); } // we need to manually invoke change detection since events registered // via // Renderer::listen() are not picked up by change detection with the // OnPush strategy this._componentRef.changeDetectorRef.markForCheck(); this.onShown.emit(this._componentRef.instance); } return this._componentRef; } public hide(): ComponentLoader<T> { if (this._componentRef) { this.onBeforeHide.emit(this._componentRef.instance); this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._componentRef.hostView)); this._componentRef = null; if (this._contentRef.viewRef) { this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef)); this._contentRef = null; } this._componentRef = null; this.onHidden.emit(); } return this; } public toggle(): void { if (this.isShown) { this.hide(); return; } this.show(); } public dispose(): void { if (this.isShown) { this.hide(); } this._unsubscribePositioning(); if (this._unregisterListenersFn) { this._unregisterListenersFn(); } } public listen(listenOpts: ListenOptions): ComponentLoader<T> { this.triggers = listenOpts.triggers || this.triggers; listenOpts.target = listenOpts.target || this._elementRef; listenOpts.show = listenOpts.show || (() => this.show()); listenOpts.hide = listenOpts.hide || (() => this.hide()); listenOpts.toggle = listenOpts.toggle || (() => this.isShown ? listenOpts.hide() : listenOpts.show()); this._unregisterListenersFn = listenToTriggers( this._renderer, listenOpts.target.nativeElement, this.triggers, listenOpts.show, listenOpts.hide, listenOpts.toggle); return this; } private _subscribePositioning(): void { if (this._zoneSubscription || !this.attachment) { return; } this._zoneSubscription = this._ngZone .onStable.subscribe(() => { if (!this._componentRef) { return; } this._posService.position({ element: this._componentRef.location, target: this._elementRef, attachment: this.attachment, appendToBody: this.container === 'body' }); }); } private _unsubscribePositioning(): void { if (!this._zoneSubscription) { return; } this._zoneSubscription.unsubscribe(); this._zoneSubscription = null; } private _getContentRef(content: string | TemplateRef<any>): ContentRef { if (!content) { return new ContentRef([]); } if (content instanceof TemplateRef) { const viewRef = this._viewContainerRef .createEmbeddedView<TemplateRef<T>>(content); return new ContentRef([viewRef.rootNodes], viewRef); } return new ContentRef([[this._renderer.createText(null, `${content}`)]]); } }