UNPKG

ngx-decorate

Version:

Useful decorators for Angular 2 and above

437 lines (407 loc) 12.5 kB
/*! MIT License Copyright (c) 2018 Art Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Call .markForCheck() on the change detector ref whenever this property is written to. * * @param propName Property at which the change detector can be found * @param conf Optional configuration */ function CdrProp(propName, conf = {}) { return (target, key) => { const sym = Symbol(`value:${key.toString()}`); const defaultDescriptor = { configurable: true, enumerable: true }; Object.defineProperties(target, { [sym]: { configurable: false, enumerable: false, value: conf.default || target[key], writable: true }, [key]: Object.assign(defaultDescriptor, conf.desc || {}, { get() { return this[sym]; }, set(v) { this[sym] = v; if (!conf.destroyed || !this[conf.destroyed]) { this[propName].markForCheck(); } } }) }); }; } /** @internal */ function ensureSymbol(target, symbol, value) { if (!target[symbol]) { Object.defineProperty(target, symbol, { configurable: false, enumerable: false, value, writable: false }); } return target[symbol]; } /** @internal */ function defineImmutable(target, prop, value) { Object.defineProperty(target, prop, { configurable: false, enumerable: false, value, writable: false }); } /** @internal */ function toAbridged(lifecycle) { if (lifecycle === 2 /* POST_INIT */ || lifecycle === 0 /* PRE_INIT */) { return 0 /* INIT */; } return 1 /* DESTROY */; } /** @internal */ const _unsubscribe = Symbol('unsubscribe'); /** @internal */ const _complete = Symbol('complete'); /** @internal */ const _lazySubj = Symbol('lazySubj'); /** @internal */ const _destroyed = Symbol('destroyed'); /** @internal */ const _init = Symbol('init'); /** @internal */ const _setProp = Symbol('setProp'); /** @internal */ const _subscrTo = Symbol('subscrTo'); /** @internal */ const _subscrToSubs = Symbol('subscrToSubs'); /** @internal */ const _hookMgr = Symbol('hookMgr'); /** @internal */ function ngOnDestroy() { //mock } /** @internal */ function ngOnInit() { // mock } /** @internal */ class HookManager { constructor(proto) { this.proto = proto; this.postDestroys = new Set(); this.postInits = new Set(); this.preDestroys = new Set(); this.preInits = new Set(); this.origInit = proto.ngOnInit || ngOnInit; this.origDestroy = proto.ngOnDestroy || ngOnDestroy; } get newDestroy() { const value = this.newFn(this.preDestroys, this.postDestroys, this.origDestroy); defineImmutable(this, 'newDestroy', value); return value; } get newInit() { const value = this.newFn(this.preInits, this.postInits, this.origInit); defineImmutable(this, 'newInit', value); return value; } static for(proto) { if (!proto[_hookMgr]) { defineImmutable(proto, _hookMgr, new HookManager(proto)); } return proto[_hookMgr]; } add(lifecycle, hookFn) { this.hooks(lifecycle).add(hookFn); const type = toAbridged(lifecycle); if (type === 1 /* DESTROY */) { this.proto.ngOnDestroy = this.preDestroys.size || this.postDestroys.size ? this.newDestroy : this.origDestroy; } else { this.proto.ngOnInit = this.preInits.size || this.postInits.size ? this.newInit : this.origInit; } } hooks(lifecycle) { switch (lifecycle) { case 0 /* PRE_INIT */: return this.preInits; case 1 /* PRE_DESTROY */: return this.preDestroys; case 2 /* POST_INIT */: return this.postInits; default: return this.postDestroys; } } newFn(pres, posts, orig) { return function () { try { for (const fn of pres) { fn(this); } orig.apply(this, arguments); } finally { for (const fn of posts) { fn(this); } } }; } } defineImmutable(HookManager.prototype, Symbol.toStringTag, 'HookManager'); /** @internal */ function mkPropertyKeyDecorator(symbolKey, hook, lc) { return (target, prop) => { ensureSymbol(target, symbolKey, []).push(prop); HookManager.for(target).add(lc, hook); }; } /** @internal */ function unsubArray(props) { for (const sub of props) { sub.unsubscribe(); } } /** @internal */ function processProp(prop) { if (prop) { if (Array.isArray(prop)) { unsubArray(prop); } else { prop.unsubscribe(); } } } /** @internal */ function unsubscribe(self) { if (self[_unsubscribe]) { for (const prop of self[_unsubscribe]) { processProp(self[prop]); } } if (self[_subscrToSubs]) { for (const prop of self[_subscrToSubs]) { processProp(prop); } } } /** * Automatically unsubscribe from the subscription(s) present at this property. * The property can be either a single subscription or an array of subscriptions. */ function Unsubscribe() { return mkPropertyKeyDecorator(_unsubscribe, unsubscribe, 3 /* POST_DESTROY */); } /** @internal */ function completeArray(inp) { for (const c of inp) { c.complete(); } } /** @internal */ function processProp$1(prop) { if (prop) { if (Array.isArray(prop)) { completeArray(prop); } else { prop.complete(); } } } /** @internal */ function complete(self) { if (self[_complete]) { for (const prop of self[_complete]) { processProp$1(self[prop]); } } } /** * Automatically completes the subjects and event emitters at this property. * The property can be either a single object or an array of objects. */ function Complete() { return mkPropertyKeyDecorator(_complete, complete, 3 /* POST_DESTROY */); } /** @internal */ function lazySubj(self) { if (Array.isArray(self[_lazySubj])) { completeArray(self[_lazySubj]); } } /** * Decorate a getter that returns a subject. The subject will automatically get completed * when the component/service/pipe is destroyed. */ function LazySubject() { return (_target, key, desc) => { const orig = desc.get; if (!orig) { throw new Error('LazySubject can only decorate getters'); } desc.get = function () { const value = orig.apply(this, arguments); Object.defineProperty(this, key, { configurable: true, enumerable: desc.enumerable, value }); ensureSymbol(this, _lazySubj, []).push(value); return value; }; HookManager.for(_target).add(3 /* POST_DESTROY */, lazySubj); }; } /** @internal */ function destroyed(self) { if (self[_destroyed]) { self[self[_destroyed]] = true; } } /** Set the given property to true when the component is destroyed */ function TrackDestroyed() { return (target, value) => { defineImmutable(target, _destroyed, value); HookManager.for(target).add(1 /* PRE_DESTROY */, destroyed); }; } /** @internal */ function setProp(self) { if (self[_setProp]) { for (const p of self[_setProp]) { self[p.prop] = p.value; } } } /** * Mark the property as a subject setter. When written to, it will call .next(value) on the subject. * The decorator's <b>default</b> config option only works on items that make use of the OnInit hook, * i.e. it will <b>not</b> work for services and pipes. * * Because the <b>default</b> config option utilises the OnInit hook, it should not be used on * properties that are component inputs as the input would get overridden during the hook. * * @param subjectPropName Name of the property at which the subject resides * @param conf Optional configuration */ function SubjectSetter(subjectPropName, conf = {}) { return (target, prop) => { const symbolValue = Symbol(`subjectSetter:${prop.toString()}`); const defaultDescriptor = { configurable: true, enumerable: true }; const defaultValue = conf.default || target[prop]; if (defaultValue) { ensureSymbol(target, _setProp, []) .push({ prop, value: defaultValue }); HookManager.for(target).add(2 /* POST_INIT */, setProp); } Object.defineProperties(target, { [symbolValue]: { configurable: false, enumerable: false, value: defaultValue, writable: true }, [prop]: Object.assign(defaultDescriptor, conf.desc || {}, { get() { return this[symbolValue]; }, set(v) { this[symbolValue] = v; this[subjectPropName].next(v); } }) }); }; } /** @internal */ function init(self) { if (self[_init]) { self[self[_init]] = true; } } /** Set the given property to true when the component is initialised */ function TrackInit() { return (target, value) => { defineImmutable(target, _init, value); HookManager.for(target).add(0 /* PRE_INIT */, init); }; } /** @internal */ function processSource(self, source, subs) { let subscrFn; const cdrProp = source.cfg.cdrProp; if (cdrProp) { subscrFn = v => { self[source.target] = v; self[source.cfg.cdrProp].markForCheck(); }; } else { subscrFn = v => { self[source.target] = v; }; } subs.push(self[source.source].subscribe(subscrFn, console.error)); } /** @internal */ function subscribeTo(self) { const sources = self[_subscrTo]; if (sources) { const subs = ensureSymbol(self, _subscrToSubs, []); for (const source of sources) { if (self[source.source]) { processSource(self, source, subs); } } } } /** * Subscribe to an observable and set its last emitted value to this property * * This decorator requires the ngOnInit hook and therefore will only work with directives and components. * * @param prop Property at which the observable resides * @param cfg Optional configuration */ function SubscribeTo(prop, cfg = {}) { return (target, key) => { ensureSymbol(target, _subscrTo, []) .push({ cfg, source: prop, target: key }); const mgr = HookManager.for(target); mgr.add(2 /* POST_INIT */, subscribeTo); mgr.add(3 /* POST_DESTROY */, unsubscribe); }; } export { CdrProp, Unsubscribe, Complete, LazySubject, TrackDestroyed, SubjectSetter, TrackInit, SubscribeTo }; //# sourceMappingURL=fesm2015.js.map