Skip to content

Instantly share code, notes, and snippets.

@bathos
Last active September 21, 2022 18:03
Show Gist options
  • Save bathos/ea9e5fcac668a0fa522b to your computer and use it in GitHub Desktop.
Save bathos/ea9e5fcac668a0fa522b to your computer and use it in GitHub Desktop.
decorators
// CLASS DECORATORS ////////////////////////////////////////////////////////////
/*
These can be thrown on to augment the class with the static properties method
and $arguments, which makes it possible to generically attach them to an
angular module without the angular module "directory" file needing to know any
particulars about the individual providers:
import hnAuth from './services/hn-auth';
import hnPoop from './directives/hn-poop';
const module = angular.module('hn', []);
const providers = [ hnAuth, hnPoop ];
for (const provider of providers)
module[provider.$method](...provider.$arguments);
However, this isn’t quite ideal still: one still needs to *have* a master module
index file, which entails importing these members & then putting them in an
array. I think there is still a better approach to be found, but I suspect this
fundamental idea (of making module members entirely self-descriptive and
generic) will be foundational to a more sophisticated Misha-authored build step.
One should only need to specify a minimal spec of *pages* in order to define a
module, which should be 1:1 with a site target, e.g. enterprise:
const pages = [ each, unique, page, in, enterprise ];
const module = angular.module('hn-enterprise', getExtDependencies(pages));
for (const provider of getAllProviders(pages))
module[provider.$method](...provider.$arguments);
In other words, the provider decorators might be expanded to shadow the angular
DI system in a way that would be walkable. Just an idea; it may simply not be
worth the trouble of working out precisely how this would be done.
*/
const providerTemplate = method => fn => {
fn.$method = method;
fn.$arguments = [ fn.name, fn ];
return fn;
};
export const animation = providerTemplate('animation');
export const controller = providerTemplate('controller');
export const directive = providerTemplate('directive');
export const factory = providerTemplate('factory');
export const filter = providerTemplate('filter');
export const provider = providerTemplate('provider');
export const service = providerTemplate('service');
// SIMPLE PROVIDER FUNCTIONS ///////////////////////////////////////////////////
/*
These are regular functions taking two args, unlike the class decorators; but
they return objects with the same interface, so that they are interchangeable.
*/
const valueTemplate = method => (name, value) =>
({ $method: method, $arguments: [ name, value ] });
export const constant = valueTemplate('constant');
export const value = valueTemplate('value');
// POOP INJECTION //////////////////////////////////////////////////////////////
/*
This is the cool bit: decorator-driven angular DI. Most libs that do this still
expect you to take the injections through the constructor arguments, which seems
no better than simply using pre-processing like ng-annotate. Instead, injections
are automatically provisioned as properties on instantiation. There is an
existing lib that does this, but in other regards it does not meet our needs
very well.
This can also work with providers that are not given as classes, though in that
case you'd be invoking it as a regular function instead of a decorator, and the
function in question will need a standard angular DI function signature (though
it would not need those arguments to have the same names as the injections).
export default
@service
@inject('$interval', 'Poop')
class Butthole {
poopForever() {
this.$interval(
() => this.emit('poop', new this.Poop({ smelly: true })),
7000
);
}
}
*/
const inject = (...names) => fn => {
const $inject = fn.$inject || [];
$inject.push(...names);
if (!fn.$inject) fn.$inject = $inject;
if (fn.$injected) return;
fn.$injected = true;
if (fn.constructor != Function) {
return class InjectedClass {
constructor(...args) {
const injections = args.slice(0, $inject.length);
const realArgs = args.slice($inject.length);
injections.forEach((value, i) =>
Object.defineProperty(fn.prototype, $inject[i], { value })
);
return new fn(realArgs);
// FATALITY !!!!
}
static get $inject() { return fn.$inject; }
static get $injected() { return true; }
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment