Sunday, May 31, 2020

Compositon in JavaScript


Be sure to check out this JavaScript course.
Here is an example of Angular component using a template decorator in TypeScript:

@Component({
template: '<div>Woo a component!</div>',
})
export class ExampleComponent {
constructor() {
console.log('Hey I am a component!');
}
}


In JavaScript a decorator can be viewed as a composite with only one component and it isn’t intended for object aggregation. Here is the  Decorator pattern in action:

const setTemplate = (component) => {
// override
component.template += '<p>new information</p>';
}

const component = {
template: "<div>hello</div>",
};
setTemplate(component); // pass the whole object to the setTemplate function

console.log(component.template);


Enter Mixins:
They find good usage base for object aggregation as well as inside of multiple components, at the same time have some drawbacks:

const externalLib = {
// ... other functions we use
setTemplate: () => { console.log('overriding...'); } // overriding function
}


Introducing partial composition using inheritance mixin:

const myComponent = Object.assign(
Properties of the target object are overwritten by properties of the source object, if they have the same key. This way later sources' properties overwrite earlier ones.
{
setTemplate: () => { console.log('original'); } // initially in our object will be overriden from ExternalLib
},
externalLib
)
myComponent.setTemplate();

We can update the mixin code, but this solves just half way the problem, as this time our function will overwrite the externalLib functionality:

const myComponent = Object.assign({}, externalLib, {
setTemplate: () => { console.log('overriding externalLib...'); } // when composing objects using .assign() last properties take precedence
});


Composition: solving the override (fragile base) problem

const externalLib = {
// ... other functions we use
setTemplate: () => { console.log('overriding original...'); } // overriding function
}

const myComponent = {
setTemplate: () => { console.log('original'); },
externalLib
}

myComponent.setTemplate();

This way our object contains, rather than mixes, the library object. And now there's no more fragile base problem.


Piping example, keep in mind that it is mutable or mutates the properties of the composed object:
const pipe = (...funcs) => initialArg => funcs.reduce((acc, func) => func(acc), initialArg);

const setTemplate = () => {
return "<div>hello</div>"
};

const setName = key => arg => {
return `${arg} + ${key}!`;
};

const Component = pipe(
setTemplate,
setName('John'),
);

const component = Component();
console.log(component);


Updating the piping using states, making the state immutable:

const pipe = (...funcs) => initialArg => funcs.reduce((acc, func) => func(acc), initialArg);

const setTemplate = (state = {}) => { // create a state on the first run
return { // return copy of the state object (immutability!)
...state,
change: inputTemplate => { // template is input parameter
state.template = InputTemplate;
return state;
},
}
}

const setLogin = (state = {}) => { // receive the state as parameter or if not create an empty one
return {
...state,
login: () => {
console.log('Logged in!')
},
}
}


const createComponent = (name, template) => { // factory function
const component = { name, template } // initial object
return pipe(
setLogin,
setTemplate,
)(component)
}


let component = createComponent('hello_user', '<div></div>');
console.log("initial: " + JSON.stringify(component));

let newState = component.change("! new template !"); // immutable change creates a new object
console.log('changed to' + JSON.stringify(newState));
console.log('original component' + JSON.stringify(component));


Thanks for reading!

No comments:

Subscribe To My Channel for updates