forked from nativescript-community/ui-material-components
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbottomsheet.service.ts
149 lines (122 loc) · 5.82 KB
/
bottomsheet.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { ComponentFactoryResolver, ComponentRef, Injectable, Injector, Type, ViewContainerRef } from '@angular/core';
import { DetachedLoader } from '@nativescript/angular/common/detached-loader';
import { AppHostView } from '@nativescript/angular/app-host-view';
import { once } from '@nativescript/angular/common/utils';
import { BottomSheetOptions as MaterialBottomSheetOptions, ViewWithBottomSheetBase } from '../bottomsheet-common';
import { Observable, Subject } from 'rxjs';
import { filter, first, map } from 'rxjs/operators';
import { ProxyViewContainer } from '@nativescript/core/ui/proxy-view-container';
export type BaseShowBottomSheetOptions = Pick<MaterialBottomSheetOptions, Exclude<keyof MaterialBottomSheetOptions, 'closeCallback' | 'view'>>;
export interface BottomSheetOptions extends BaseShowBottomSheetOptions {
viewContainerRef?: ViewContainerRef;
}
export class BottomSheetParams {
context: any;
closeCallback: (...args) => void;
constructor(context, closeCallback) {
this.context = context;
this.closeCallback = closeCallback;
}
}
@Injectable({
providedIn: 'root'
})
export class BottomSheetService {
private detachedLoader: ComponentRef<DetachedLoader>;
private componentView: ViewWithBottomSheetBase;
private subject$: Subject<{ requestId: number; result: any }> = new Subject();
private currentId = 0;
show<T = any>(type: Type<any>, options: BottomSheetOptions): Observable<T> {
return this.showWithCloseCallback(type, options).observable;
}
showWithCloseCallback<T = any>(type: Type<any>, options: BottomSheetOptions): { observable: Observable<T>, closeCallback: () => void } {
if (!options.viewContainerRef) {
throw new Error('No viewContainerRef: Make sure you pass viewContainerRef in BottomSheetOptions.');
}
this.currentId++;
const requestId = this.currentId;
const parentView = this.getParentView(options.viewContainerRef);
const factoryResolver = this.getFactoryResolver(options.viewContainerRef);
const bottomSheetParams = this.getBottomSheetParams(options.context, requestId);
this.detachedLoader = this.createDetachedLoader(factoryResolver, bottomSheetParams, options.viewContainerRef);
this.loadComponent(type).then(componentView => {
parentView.showBottomSheet({
...options,
...bottomSheetParams,
view: componentView
});
});
return {
observable: this.subject$.pipe(
filter(item => item && item.requestId === requestId),
map(item => item.result),
first()
),
closeCallback: bottomSheetParams.closeCallback
};
}
private getParentView(viewContainerRef: ViewContainerRef): ViewWithBottomSheetBase {
let parentView = viewContainerRef.element.nativeElement;
if (parentView instanceof AppHostView && parentView.ngAppRoot) {
parentView = parentView.ngAppRoot;
}
// _ngDialogRoot is the first child of the previously detached proxy.
// It should have 'viewController' (iOS) or '_dialogFragment' (Android) available for
// presenting future bottomSheets views.
if (parentView._ngDialogRoot) {
parentView = parentView._ngDialogRoot;
}
return parentView;
}
private getFactoryResolver(componentContainer: ViewContainerRef): ComponentFactoryResolver {
// resolve from particular module (moduleRef)
// or from same module as parentView (viewContainerRef)
return componentContainer.injector.get(ComponentFactoryResolver);
}
private createChildInjector(bottomSheetParams: BottomSheetParams, containerRef: ViewContainerRef) {
return Injector.create({
providers: [
{
provide: BottomSheetParams,
useValue: bottomSheetParams
}
],
parent: containerRef.injector
});
}
private getBottomSheetParams(context: any, requestId: number) {
const closeCallback = once(args => {
this.subject$.next({ result: args, requestId });
if (!this.componentView) {
return;
}
this.componentView.closeBottomSheet();
this.detachedLoader.instance.detectChanges();
this.detachedLoader.destroy();
});
return new BottomSheetParams(context, closeCallback);
}
private createDetachedLoader(factoryResolver: ComponentFactoryResolver, bottomSheetParams: BottomSheetParams, viewContainerRef: ViewContainerRef): ComponentRef<DetachedLoader> {
const detachedLoaderFactory = factoryResolver.resolveComponentFactory(DetachedLoader);
const childInjector = this.createChildInjector(bottomSheetParams, viewContainerRef);
return viewContainerRef.createComponent(detachedLoaderFactory, 0, childInjector, null);
}
private async loadComponent(type: Type<any>): Promise<ViewWithBottomSheetBase> {
try {
const componentRef = await this.detachedLoader.instance.loadComponent(type);
const detachedProxy = <ProxyViewContainer>componentRef.location.nativeElement;
if (detachedProxy.getChildrenCount() > 1) {
throw new Error('BottomSheet content has more than one root view.');
}
this.componentView = detachedProxy.getChildAt(0) as ViewWithBottomSheetBase;
if (this.componentView.parent) {
(<any>this.componentView.parent)._ngDialogRoot = this.componentView;
(<any>this.componentView.parent).removeChild(this.componentView);
}
return this.componentView;
} catch (e) {
console.log(e);
return null;
}
}
}