@wizdm/teleport
Version:
Template teleporting
203 lines (196 loc) • 7.98 kB
JavaScript
import { InjectionToken, ɵɵdefineInjectable, ɵɵinject, Injectable, Optional, Inject, EventEmitter, SimpleChange, Directive, TemplateRef, ViewContainerRef, Input, Output, NgModule } from '@angular/core';
import { NgTemplateOutlet, CommonModule } from '@angular/common';
import { shareReplay, filter, scan, map, distinctUntilChanged, switchMap, delay } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
const TeleportConfigToken = new InjectionToken('wizdm.teleport.config');
class TeleportService {
constructor(config) {
this.inner$ = new BehaviorSubject(null);
this.beam$ = this.inner$.pipe(shareReplay((config === null || config === void 0 ? void 0 : config.bufferSize) || 1));
}
/** Beam observable streaming the template to render as they occur */
beam(name) {
return this.beam$.pipe(
// Filters only those instances targetting the requested portal
filter(payload => !payload || payload.target === name),
// Buffers the requests
scan((history, payload) => {
// Clears all the history
if (!payload || payload.action === 'clearAll') {
return [];
}
// Seeks for the given template in the history first
const index = history.findIndex(value => value.template === payload.template);
// Removes the template from the history
if (index >= 0 && payload.action === 'clear') {
return history.splice(index, 1), history;
}
// Activates the new template
if (payload.action === 'activate') {
// If already existing, replaces the payload in place
if (index >= 0) {
return history.splice(index, 1, payload), history;
}
// Pushes the new template otherwise
history.push(payload);
}
return history;
}, []),
// Emits the last template in history or null
map(history => history[history.length - 1] || null), distinctUntilChanged());
}
/** Activates the template at the given target portal */
activate(target, template, data) {
this.inner$.next({ action: 'activate', target, template, data });
}
/** Clears the template from the target portal */
clear(target, template) {
this.inner$.next({ action: 'clear', target, template });
}
/** Clear all the portals */
clearAll() {
this.inner$.next({ action: 'clearAll' });
}
}
TeleportService.ɵprov = ɵɵdefineInjectable({ factory: function TeleportService_Factory() { return new TeleportService(ɵɵinject(TeleportConfigToken, 8)); }, token: TeleportService, providedIn: "root" });
TeleportService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] }
];
TeleportService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [TeleportConfigToken,] }] }
];
class PortalDirective extends NgTemplateOutlet {
constructor(teleport, host, container) {
super(container);
this.teleport = teleport;
this.host = host;
this.name$ = new BehaviorSubject('');
this.firstChange = true;
/** The actual template in use. NOTE for this event to bind correclty the directive must be used in its de-sugared form */
this.template = new EventEmitter();
/** True then the portal is active */
this.active = new EventEmitter();
/** Optional data passed along from the TeleportDirective back to the Portal */
this.data = new EventEmitter();
// Builds the template observable
this.sub = this.name$.pipe(
// Beams the content with the given name
switchMap(name => this.teleport.beam(name)),
// Wait the next round to avoid expressionChangedAfterItHasBeenChecked() exception
delay(0)).subscribe(payload => this.changeTemplate(payload || {}));
}
/** The portal name */
set name(name) { this.name$.next(name); }
changeTemplate({ template, data }) {
// Emits the new optional data, if any
if (typeof (data) !== undefined) {
this.data.emit(data);
}
/** Skips on no template changes */
if (template === this.ngTemplateOutlet) {
return;
}
// Creates a simple change object
const ngTemplateOutlet = new SimpleChange(this.ngTemplateOutlet, template || this.host, this.firstChange);
// Updates the parent's template variable accordingly
this.ngTemplateOutlet = ngTemplateOutlet.currentValue;
// Tracks for the first change
this.firstChange = false;
// Forces the change to occur
super.ngOnChanges({ ngTemplateOutlet });
// Emits the new template
this.template.emit(this.ngTemplateOutlet);
// Emits true whenever the template isn't the host one
this.active.emit(this.ngTemplateOutlet !== this.host);
}
// Intercepts the input changes
ngOnChanges(changes) {
// Gets rid of the name's SimpleChange object
if (changes.name) {
delete changes.name;
}
// Skips when no changes remains
if (Object.keys(changes).length === 0) {
return;
}
// Lets the context changes go through
super.ngOnChanges(changes);
}
// Disposes of the subscription on deletion
ngOnDestroy() { this.sub.unsubscribe(); }
}
PortalDirective.decorators = [
{ type: Directive, args: [{
selector: 'ng-template[wmPortal]',
exportAs: 'wmPortal'
},] }
];
PortalDirective.ctorParameters = () => [
{ type: TeleportService },
{ type: TemplateRef },
{ type: ViewContainerRef }
];
PortalDirective.propDecorators = {
name: [{ type: Input, args: ['wmPortal',] }],
ngTemplateOutletContext: [{ type: Input, args: ['wmPortalContext',] }],
template: [{ type: Output, args: ['wmPortalTemplate',] }],
active: [{ type: Output, args: ['wmPortalActive',] }],
data: [{ type: Output, args: ['wmPortalData',] }]
};
class TeleportDirective {
constructor(teleport, template) {
this.teleport = teleport;
this.template = template;
}
ngOnChanges(changes) {
const target = changes.target;
if (!target) {
return;
}
// Clears the previous target, if any
target.previousValue && this.teleport.clear(target.previousValue, this.template);
// Teleports the template to the new target portal
target.currentValue && this.teleport.activate(target.currentValue, this.template, this.data);
}
ngOnDestroy() {
// Clears the portal on destroy
this.target && this.teleport.clear(this.target, this.template);
}
}
TeleportDirective.decorators = [
{ type: Directive, args: [{
selector: 'ng-template[wmTeleport]'
},] }
];
TeleportDirective.ctorParameters = () => [
{ type: TeleportService },
{ type: TemplateRef }
];
TeleportDirective.propDecorators = {
target: [{ type: Input, args: ['wmTeleport',] }],
data: [{ type: Input, args: ['wmTeleportData',] }]
};
class TeleportModule {
static init(config) {
return {
ngModule: TeleportModule,
providers: [
{ provide: TeleportConfigToken, useValue: config }
]
};
}
}
TeleportModule.decorators = [
{ type: NgModule, args: [{
imports: [CommonModule],
declarations: [PortalDirective, TeleportDirective],
exports: [PortalDirective, TeleportDirective]
},] }
];
/**
* Generated bundle index. Do not edit.
*/
export { PortalDirective, TeleportConfigToken, TeleportDirective, TeleportModule, TeleportService };
//# sourceMappingURL=wizdm-teleport.js.map