UNPKG

@this-dot/route-config

Version:

A library containing directives and services for configuring components via Route's routeData property

349 lines (337 loc) 18.4 kB
import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Optional, Inject, Pipe, Directive, Input, NgModule } from '@angular/core'; import { filter, map, startWith, switchMap, distinctUntilChanged, tap, takeUntil } from 'rxjs/operators'; import * as i1 from '@angular/router'; import { ActivationEnd } from '@angular/router'; import { combineLatest, BehaviorSubject, Subject } from 'rxjs'; import { isTruthy } from '@this-dot/utils'; const ROUTE_DATA_DEFAULT_VALUE = new InjectionToken('Route Data default value'); const gatherRoutes = (activatedRoute) => { const routes = activatedRoute.pathFromRoot; let route = activatedRoute.firstChild; while (route) { routes.push(route); route = route.firstChild; } return routes; }; class RouteConfigService { get injectedDefaultValue() { return this._injectedDefaultValue || {}; } constructor(activatedRoute, router, _injectedDefaultValue) { this.activatedRoute = activatedRoute; this.router = router; this._injectedDefaultValue = _injectedDefaultValue; } /** * Returns an Observable which emits the route config set for the activated route. * * @example * export class AppComponent { * data$ = this.routeConfigService.getActivatedRouteConfig(); * dataWithDefaultValue$ = this.routeConfigService.getActivatedRouteConfig({ * routeTags: ['defaultTag'], * title: 'Default Title', * }); * } * * @param defaultValue - the default value that should be returned, it allows overriding the injected default values. * * @returns Observable<Partial<C>> */ getActivatedRouteConfig(defaultValue = {}) { return this.router.events.pipe(filter((event) => event instanceof ActivationEnd), map(() => this.activatedRoute), startWith(this.activatedRoute), map(gatherRoutes), switchMap((routes) => combineLatest(routes.map(({ data }) => data)).pipe(map((dataArr) => Object.assign({}, this.injectedDefaultValue, defaultValue, ...dataArr))))); } getLeafConfig(paramName, defaultValue) { return this.getActivatedRouteConfig(defaultValue ? { [paramName]: defaultValue, } : {}).pipe(map((data) => data[paramName] || defaultValue)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigService, deps: [{ token: i1.ActivatedRoute }, { token: i1.Router }, { token: ROUTE_DATA_DEFAULT_VALUE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i1.ActivatedRoute }, { type: i1.Router }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [ROUTE_DATA_DEFAULT_VALUE] }] }]; } }); /** * Pipes an array of route tags and returns an Observable<boolean> that emits a boolean. * * The boolean is true if any of the values in the provided array is in the configured route tags of the activated route. * * @example * <!-- Use it chained with the async pipe --> * <ng-container *ngIf="routesWhereTheElementIsDisplayed | inRouteTags$ | async"> * The contents of this ng-container * </ng-container> */ class InRouteTags$Pipe { constructor(routeTagService) { this.routeTagService = routeTagService; this.routeTags$ = this.routeTagService.getLeafConfig('routeTags', []); } transform(tags) { return this.routeTags$.pipe(map((routeTags) => !!tags.find((tag) => routeTags.includes(tag)))); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: InRouteTags$Pipe, deps: [{ token: RouteConfigService }], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.1.7", ngImport: i0, type: InRouteTags$Pipe, name: "inRouteTags$" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: InRouteTags$Pipe, decorators: [{ type: Pipe, args: [{ name: 'inRouteTags$', }] }], ctorParameters: function () { return [{ type: RouteConfigService }]; } }); class RouteDataHasService { setTags(tags) { const tagArray = Array.isArray(tags) ? tags : [tags]; this.tags$.next(tagArray); } setPropName(propName) { this.propName$.next(propName); } setElseTemplate(elseTemplate) { this.elseTemplate$.next(elseTemplate); } constructor(routeConfigService) { this.routeConfigService = routeConfigService; this.tags$ = new BehaviorSubject([]); this.propName$ = new BehaviorSubject(undefined); this.elseTemplate$ = new BehaviorSubject(null); this.destroy$ = new Subject(); this.display$ = combineLatest([ this.tags$, this.propName$.pipe(filter(isTruthy), switchMap((propName) => this.routeConfigService.getLeafConfig(propName, []))), ]).pipe(map(([tags, routeValues]) => !!tags.find((tag) => routeValues.includes(tag))), distinctUntilChanged()); this.createView$ = combineLatest([this.display$, this.elseTemplate$]).pipe(tap(() => this.viewContainer.clear()), tap(([show, elseTemplate]) => show ? void this.viewContainer.createEmbeddedView(this.template) : void (elseTemplate && this.viewContainer.createEmbeddedView(elseTemplate)))); } init(template, viewContainer) { this.template = template; this.viewContainer = viewContainer; this.createView$.pipe(takeUntil(this.destroy$)).subscribe(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataHasService, deps: [{ token: RouteConfigService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataHasService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataHasService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: RouteConfigService }]; } }); /** * Retrieves the provided route config property and binds it into a template variable * * @example * <!-- Display an element only if the "show" routeTag is truthy --> * <p *tdRouteTag="'show'"> * This text is only visible, if there is a 'show' tag in the route data's `routeTags` Array * </p> * * @example * <!-- Display an element only if the "show" routeTag is truthy and display a fallback template if it is falsy --> * <p *tdRouteTag="'show'; else noShowTag"> * This text is only visible, if there is a 'show' tag in the route data's `routeTags` Array * </p> * <ng-template #noShowTag> * <p>There is no 'show' tag in this route's config</p> * </ng-template> */ class RouteTagDirective { set tdRouteTag(tags) { this.routeDataHasService.setTags(tags); } set tdRouteTagElse(elseTemplate) { this.routeDataHasService.setElseTemplate(elseTemplate); } constructor(template, viewContainer, routeDataHasService) { this.template = template; this.viewContainer = viewContainer; this.routeDataHasService = routeDataHasService; } ngOnInit() { this.routeDataHasService.setPropName('routeTags'); this.routeDataHasService.init(this.template, this.viewContainer); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteTagDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: RouteDataHasService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: RouteTagDirective, selector: "[tdRouteTag]", inputs: { tdRouteTag: "tdRouteTag", tdRouteTagElse: "tdRouteTagElse" }, providers: [RouteDataHasService], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteTagDirective, decorators: [{ type: Directive, args: [{ selector: '[tdRouteTag]', providers: [RouteDataHasService] }] }], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: RouteDataHasService }]; }, propDecorators: { tdRouteTag: [{ type: Input }], tdRouteTagElse: [{ type: Input }] } }); /** * This directive allows for access to the whole `data` property defined in the current [Route](https://angular.io/api/router/Route#data) from a Component's template. * * @example * <!-- We can use it as following: --> * <h1 *tdRouteData="let data"> * Current title is: {{ data.title }} * </h1> * * @example * <!-- It is also possible to pass a default value so that if a property is not defined in the Route we will still receive some value: --> * <h1 *tdRouteData="let data; defaultValue: { title: 'DefaultTitle', routeTags: ['defaultTag'] }"> * Current title is: {{ data.title }} * </h1> * * @example * <!-- If you want to access multiple properties in one component's template it is **recommended** to wrap the whole template with only one `*tdRouteData` directive. This approach follows DRY principle and is efficient as it only creates one subscription per template. --> * <ng-container *tdRouteData="let data; defaultValue: { title: 'DefaultTitle', routeTags: ['defaultTag'] }"> * <h1> * Current title is: {{ data.title }} * </h1> * <p> * Current route contains the following tags: {{ data.routeTags | json }} * </p> * </ng-container> */ class RouteDataDirective { set tdRouteDataDefaultValue(defaultValue) { this.defaultValue$.next(defaultValue); } constructor(routeConfigService, template, entry) { this.routeConfigService = routeConfigService; this.template = template; this.entry = entry; this.destroy$ = new Subject(); this.defaultValue$ = new BehaviorSubject({}); this.data$ = this.defaultValue$.pipe(switchMap((defaultValue) => this.routeConfigService.getActivatedRouteConfig(defaultValue)), distinctUntilChanged()); this.createView$ = this.data$.pipe(tap((data) => { this.view.context = { $implicit: data, }; this.view.markForCheck(); })); } ngOnInit() { this.view = this.entry.createEmbeddedView(this.template, { $implicit: this.defaultValue$.value, }); this.createView$.pipe(takeUntil(this.destroy$)).subscribe(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataDirective, deps: [{ token: RouteConfigService }, { token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: RouteDataDirective, selector: "[tdRouteData]", inputs: { tdRouteDataDefaultValue: "tdRouteDataDefaultValue" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataDirective, decorators: [{ type: Directive, args: [{ selector: '[tdRouteData]', }] }], ctorParameters: function () { return [{ type: RouteConfigService }, { type: i0.TemplateRef }, { type: i0.ViewContainerRef }]; }, propDecorators: { tdRouteDataDefaultValue: [{ type: Input }] } }); class RouteDataModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.7", ngImport: i0, type: RouteDataModule, declarations: [RouteDataDirective], imports: [CommonModule], exports: [RouteDataDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataModule, decorators: [{ type: NgModule, args: [{ declarations: [RouteDataDirective], imports: [CommonModule], exports: [RouteDataDirective], }] }] }); /** * If you need to use a different route data property to store the tags, you can use the `*tdRouteDataHas` directive. It works very similar to `*tdRouteTag` directive but provides a way to use different properties as a source of data. * * @example * <!-- You can configure the directive to use the `customDataProperty` property of the route data object, where it looks for the `customShow` property to determine if the element needs to show: * <p *tdRouteDataHas="'customShow'; propName: 'customDataProperty'"> * This text is only visible, if there is a 'show' tag in the route data's `customDataProperty` Array * </p> * * @example * <!-- `*tdRouteDataHas` also provides a way do display a fallback template if a given tag is not present --> * <p *tdRouteDataHas="'customShow'; propName: 'customDataProperty'; else noShowTag"> * This text is only visible, if there is a 'customShow' tag in the route data's `customDataProperty` Array * </p> * <ng-template #noShowTag> * <p>There is no 'customShow' tag in this route's config</p> * </ng-template> */ class RouteDataHasDirective { set tdRouteDataHas(tags) { this.routeDataHasService.setTags(tags); } set tdRouteDataHasPropName(propName) { this.routeDataHasService.setPropName(propName); } set tdRouteDataHasElse(elseTemplate) { this.routeDataHasService.setElseTemplate(elseTemplate); } constructor(template, viewContainer, routeDataHasService) { this.template = template; this.viewContainer = viewContainer; this.routeDataHasService = routeDataHasService; } ngOnInit() { this.routeDataHasService.init(this.template, this.viewContainer); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataHasDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: RouteDataHasService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: RouteDataHasDirective, selector: "[tdRouteDataHas]", inputs: { tdRouteDataHas: "tdRouteDataHas", tdRouteDataHasPropName: "tdRouteDataHasPropName", tdRouteDataHasElse: "tdRouteDataHasElse" }, providers: [RouteDataHasService], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteDataHasDirective, decorators: [{ type: Directive, args: [{ selector: '[tdRouteDataHas]', providers: [RouteDataHasService], }] }], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: RouteDataHasService }]; }, propDecorators: { tdRouteDataHas: [{ type: Input }], tdRouteDataHasPropName: [{ type: Input }], tdRouteDataHasElse: [{ type: Input }] } }); class RouteConfigModule { /** * Registers the RouteConfigModule and sets the providers globally. * Make sure you call the forRoot method in your root module. * * @remarks You still need to import the module without calling the forRoot method in other modules so you can use the pipes and directives from this module. */ static forRoot() { return { ngModule: RouteConfigModule, providers: [RouteConfigService], }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigModule, declarations: [RouteTagDirective, InRouteTags$Pipe, RouteDataHasDirective], imports: [CommonModule, RouteDataModule], exports: [RouteTagDirective, InRouteTags$Pipe, RouteDataModule, RouteDataHasDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigModule, imports: [CommonModule, RouteDataModule, RouteDataModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: RouteConfigModule, decorators: [{ type: NgModule, args: [{ declarations: [RouteTagDirective, InRouteTags$Pipe, RouteDataHasDirective], imports: [CommonModule, RouteDataModule], exports: [RouteTagDirective, InRouteTags$Pipe, RouteDataModule, RouteDataHasDirective], }] }] }); /** * Generated bundle index. Do not edit. */ export { InRouteTags$Pipe, ROUTE_DATA_DEFAULT_VALUE, RouteConfigModule, RouteConfigService, RouteDataDirective, RouteDataHasDirective, RouteDataModule, RouteTagDirective }; //# sourceMappingURL=this-dot-route-config.mjs.map