|
| 1 | +import { OnDestroy, Directive, Type, TemplateRef, ViewContainerRef, EmbeddedViewRef } from '@angular/core'; |
| 2 | +import { ActivatedRoute, ActivatedRouteSnapshot, UrlSegment, Params, Data, Route, ParamMap, convertToParamMap } from '@angular/router'; |
| 3 | +import { Subscription } from 'rxjs'; |
| 4 | +import { distinctUntilChanged } from 'rxjs/operators'; |
| 5 | + |
| 6 | +interface RouteContext { |
| 7 | + $implicit: ActivatedRoute; |
| 8 | + snapshot: ActivatedRouteSnapshot; |
| 9 | + url: UrlSegment[]; |
| 10 | + params: Params; |
| 11 | + queryParams: Params; |
| 12 | + fragment: string; |
| 13 | + data: Data; |
| 14 | + outlet: string; |
| 15 | + component: Type<any> | string; |
| 16 | + routeConfig: Route; |
| 17 | + root: ActivatedRoute; |
| 18 | + parent: ActivatedRoute | null; |
| 19 | + firstChild: ActivatedRoute | null; |
| 20 | + children: ActivatedRoute[]; |
| 21 | + pathFromRoot: ActivatedRoute[]; |
| 22 | + paramMap: ParamMap; |
| 23 | + queryParamMap: ParamMap; |
| 24 | +} |
| 25 | + |
| 26 | +const ASYNC_FIELDS = ['url', 'params', 'queryParams', 'fragment', 'data', 'paramMap', 'queryParamMap']; |
| 27 | + |
| 28 | +@Directive({ selector: '[route]' }) |
| 29 | +export class RouteDirective implements OnDestroy { |
| 30 | + private context: RouteContext = { |
| 31 | + $implicit: this.route, |
| 32 | + get snapshot() { return this.route.snapshot; }, |
| 33 | + url: [], |
| 34 | + params: {}, |
| 35 | + queryParams: {}, |
| 36 | + fragment: null, |
| 37 | + data: null, |
| 38 | + get outlet() { return this.route.outlet; }, |
| 39 | + get component() { return this.route.component; }, |
| 40 | + get routeConfig() { return this.route.routeConfig; }, |
| 41 | + get root() { return this.route.root; }, |
| 42 | + get parent() { return this.route.parent; }, |
| 43 | + get firstChild() { return this.route.firstChild; }, |
| 44 | + get children() { return this.route.children; }, |
| 45 | + get pathFromRoot() { return this.route.pathFromRoot; }, |
| 46 | + paramMap: convertToParamMap({}), |
| 47 | + queryParamMap: convertToParamMap({}) |
| 48 | + }; |
| 49 | + private viewRef: EmbeddedViewRef<RouteContext> = |
| 50 | + this.viewContainerRef.createEmbeddedView(this.templateRef, this.context); |
| 51 | + private subscriptions: Subscription[] = this.attachFields(ASYNC_FIELDS); |
| 52 | + |
| 53 | + constructor( |
| 54 | + private templateRef: TemplateRef<RouteContext>, |
| 55 | + private viewContainerRef: ViewContainerRef, |
| 56 | + private route: ActivatedRoute |
| 57 | + ) { } |
| 58 | + |
| 59 | + ngOnDestroy() { |
| 60 | + this.subscriptions.forEach((subscription) => { |
| 61 | + subscription.unsubscribe(); |
| 62 | + }); |
| 63 | + this.subscriptions = null; |
| 64 | + } |
| 65 | + |
| 66 | + private attachFields(asyncFields: string[]): Subscription[] { |
| 67 | + return asyncFields.map(field => this.asyncAttach(field)); |
| 68 | + } |
| 69 | + |
| 70 | + private asyncAttach(field: string): Subscription { |
| 71 | + return this.route[field] |
| 72 | + .pipe(distinctUntilChanged()) |
| 73 | + .subscribe(value => { |
| 74 | + this.context[field] = value; |
| 75 | + this.viewRef.markForCheck(); |
| 76 | + }); |
| 77 | + } |
| 78 | +} |
0 commit comments