I wrote a small module in typescript that augments the angularjs registerComponent
function to provide a new way of default values for the component controller.
This module was written to solve the problem of not having a nice non-biolerplatey way of providing default values for components. Another problem it attempts to solve is to prevent the default value not applying if a value was provided trough bindings but the value is undefined or null.
The module works by using a static property on the component controller called $defaults
. Getter functions are also supported to provide dynamic defaults for each component instance.
I wrote it with dynamic getters in mind. Because it is a function you could do anything in the function body (for example call an API).
I am looking for some feedback on the code and its purpose. The source code is available.
(function() {
type Dict<T = any> = { [key: number]: T, [key: string]: T };
angular.module('ng-sane-defaults', []).config([
'$compileProvider',
function configure($compileProvider: angular.ICompileProvider) {
// get the original `registerComponent` function
const registerComponent = $compileProvider.component;
// override the original function
$compileProvider.component = component;
/**
*
* @param nameOrDict name or Dictionary of component names and their options
* @param options options for the component if the first parameter is the name
*/
function component(this: angular.ICompileProvider, nameOrDict: string|Dict<angular.IComponentOptions>, options?: angular.IComponentOptions) {
// make sure the function signature is correct, if not pass it trough to the original function to handle errors
if (typeof nameOrDict === 'string' && typeof options !== 'object')
return registerComponent.call(this, nameOrDict, options as any);
if (typeof nameOrDict !== 'string' && typeof nameOrDict !== 'object')
return registerComponent.call(this, nameOrDict, options as any);
// create the dictionary call signature as it is easier to work with
const components: Dict<angular.IComponentOptions> = (typeof nameOrDict === 'string') ? { [nameOrDict]: options! } : nameOrDict;
for (const key in components) {
const options = components[key];
// check if it has a controller function
if (typeof options.controller !== 'function')
continue;
// check if it has a `static $defaults` property, use `getOwnPropertyNames` to check for a getter function without invoking it
if (Object.getOwnPropertyNames(options.controller).indexOf('$defaults') === -1)
continue;
// get the original `$onInit` function or undefined if there was none
const onInit: Function|undefined = options.controller.prototype.$onInit;
// patch the `$onInit` function
options.controller.prototype.$onInit = function $onInit() {
const defaults = (options.controller as Dict).$defaults;
if (typeof defaults === 'object') {
for (const prop in defaults) {
if (this[prop] == null) {
this[prop] = defaults[prop];
}
}
}
// call the lifecycle hook if it existed
return (typeof onInit === 'function') ? onInit.call(this) : void 0;
};
}
return registerComponent.call(this, components);
}
}
]);
})();
You can check this plunkr to see it in action