Keep my code DRY for `$onInit` and `$onChanges` methods - javascript

I have a piece of code that is repeated in almost all of my components:
import {deviceCommands} from "../../../core/data/Commands";
let _generalProperties = {
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
};
export default class GeneralDeviceSettingsCtrl {
constructor($scope) {
let $ctrl = this;
$ctrl.$onChanges = function (changes) {
for(let prop in _generalProperties) {
$ctrl[prop] = $ctrl.test.vm.data[_generalProperties[prop]];
}
};
$ctrl.$onInit = function () {
for(let prop in _generalProperties) {
$scope.$watch(() => $ctrl.test.vm.data[_generalProperties[prop]],
(newValue, oldValue) => {
if (newValue !== oldValue) {
$ctrl[prop] = $ctrl.test.vm.data[_generalProperties[prop]];
}
});
}
};
}
}
The only thing that is different is the _generalProperties variable, which is specific to my view.
How can I do to keep it DRY?
Make a base class? use decorators?

I think there is plenty of different approaches to this but if you stick with classes and inheritance you can supply the _generalProperties to your parent constructor and reuse the whole implementation.
For example:
export class BaseSettingsComponent {
constructor(properties){
this._properties = properties;
}
$onInit(){ /* ... implement base component stuff*/ }
}
Then, on each of your pages that uses the same component logic you can extend the base class, provide the properties to the parent class and let the base class do the job.
import { BaseSettingsComponent } from '../common/base-settings-component.js';
let _generalProperties = {
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
};
export class GeneralDeviceSettingsCtrl extends BaseSettingsComponent {
constructor(){
super(_generalProperties);
}
$onInit(){ super.$onInit(); /* ... implement base component stuff*/ }
}
The one thing important to keep in mind when using this approach is that if you need to implement $onInit or $onChanges on the child classes you have to call the super.$onInit() otherwise you lose the parent behavior due to an override.
Finally, for an even cleaner code, you can also discard the declaration of _generalProperties supplying it directly into the super constructor.
import { BaseSettingsComponent } from '../common/base-settings-component.js';
export class GeneralDeviceSettingsCtrl extends BaseSettingsComponent {
constructor(){
super({
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
});
}
}

Related

Dynamically inherit from instance of the class in JavaScript

I am kind of struggling with inheritance in JavaScript. Let's say I have a following class:
class Parent {
constructor({ name, someOtherStuff } = {}) {
this.name = name;
this.someOtherStuff = someOtherStuff;
}
someMethod() {
// ...
}
}
and I would like to create a decorator that would allow me to do following:
#parent({
name: 'foo',
someOtherStuff: 'bar'
})
class MyClass extends Component {
myMethod() {
// ...
}
}
const instance = new MyClass();
// Those tests must pass
expect(instance.someMethod).toBeFunction();
expect(instance.name).toEqual('foo');
expect(instance.someOtherStuff).toEqual('bar');
expect(instance.myMethod).toBeFunction();
expect(instance instanceof Parent).toBe(true);
expect(instance instanceof MyClass).toBe(true);
Is there a way to create such decorator? I tried multiple solutions, but none of them really satisfies all the tests.
const parent = (...args) => (Target) => {
// Target corresponds to MyClass
const parent = new Parent(...args);
// ...
};
lodash is allowed.
Why use decorators? You can just extend parent class
class MyClass extends Parent {
constructor() {
super({name: 'foo', someOtherStuff: 'bar'});
}
}
You can use decorators to create a new class that inherits, apply some mixins, and go from there. JS classes don't have mutliple inheritance, so you can't do this directly, but you can combine the two manually or create a proxy that will do what you want.
I've been using wrapper classes for a decorator-based DI library by returning a class like so:
static wrapClass(target, {hook = noop} = {}) {
return class wrapper extends target {
static get wrappedClass() {
return target;
}
constructor(...args) {
super(...Injector.fromParams(args).getDependencies(wrapper).concat(args));
}
}
}
The decorator is really returning a new constructor with closure over the original, but that's enough for most purposes.

Add default properties when inheriting reactjs class in ES6

I am trying to add a default property that should refer to an instance function when I am inheriting a component class in ReactJS, and ES6. In detail, I have the datepicker from npm (react-day-picker) and want to make sure that two properties are always sent to the base class:
export default class DayPicker extends BaseDayPicker {
constructor(props) {
var { ...newProps } = props;
newProps.onMouseDown = this.onDayPickerMouseDown;
newProps.onMouseUp = this.onDayPickerMouseUp;
super(newProps);
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
window.addEventListener('mousedown', this.onPageClick, false);
}
componentWillUnmount() {
super.componentWillUnmount && super.componentWillUnmount();
window.addEventListener('mousedown', this.onPageClick, false);
}
onPageClick = (e) => {
if (!this.isDayPickerMouseDown) {
this.props.onPageClick && this.props.onPageClick();
}
};
onDayPickerMouseDown = (e) => {
this.isDayPickerMouseDown = true;
};
onDayPickerMouseUp = (e) => {
this.isDayPickerMouseDown = false;
};
render() {
return super.render();
}
}
The problem with the code above is that I get 'this' is not allowed before super().
I cannot find a way to solve this. If it is not possible to add default properties that must use this, is it possible to solve it in the render method?
Referencing my comment on another answer
You should lean away from inheritance, it is an anti-pattern.
React was designed for composition. What does that mean? If you have some functionality to share, then put it in a component and make it use props in different ways.
TL;DR You want to use Higher-Order Components for this type of situation.
Example:
BaseDayPicker = (RenderedComponent) => React.Component {
// just a function that takes a component, and returns a component.
onMouseDown() {
this.props.onMouseDown(doSomething());
}
onMouseUp() {
this.props.onMouseUp();
}
//...
// notice it renders the component that came in as a parameter.
render(){
return (<RenderedComponent
onMouseUp={this.onMouseUp}
onMouseDown={this.onMouseDown}
/>) // it also adds some props! super cool
}
}
class DayPicker extends React.Comnponent {
//...
onMouseDown() {
this.isDayPickerMouseDown = true;
this.props.onMouseDown();
}
onMouseUp() {
this.isDayPickerMouseDown = false;
this.props..onMouseUp();
}
//....
}
// NOTICE, WRAPPING ONE IN ANOTHER
export BaseDayPicker(DayPicker)
If you want to know WHY, here is a blog post explaining why react mixins are dead.
One of the possible solutions is to specify two empty functions in parent class and then you can override them in child class.
class BaseDayPicker extends React.Comnponent {
//...
onMouseDown() {
this.props.onMouseDown();
}
onMouseUp() {
this.props.onMouseUp();
}
//....
}
class DayPicker extends React.Comnponent {
//...
onMouseDown() {
this.isDayPickerMouseDown = true;
super.onMouseDown();
}
onMouseUp() {
this.isDayPickerMouseDown = false;
super.onMouseUp();
}
//....
}
So in this case you can call class method and function passed within props.
In your case it would be better to wrap BaseDayPicker into another component that would extend it functionality instead of trying to extend component itself.

spying on/mocking super class methods in es6

The below are examples but get at the gist of my problem ...
super class:
class Parent {
constructor(a) {
this._a = a;
}
doSomething() { ... implementation... }
}
child-class:
class Child extends Parent {
constructor() {
super('a');
}
doSomethingElse() { return super.doSomething(); }
}
I am using these classes in Angular, so the Parent class is DI'ed into the factory which provides the Child class, something like this:
function ChildDeps(Parent) {
return class Child extends Parent {
... Child class implementation here ...
};
}
ChildDeps.$inject = ['Parent']
Naively, I first tried something like this inside a provide before each clause:
beforeEach(module($provide => {
parent = {
doSomething: jasmine.createSpy('parent.doSomething')
};
Parent = jasmine.createSpy('Parent').and.returnValue(parent);
$provide.value('Parent', Parent);
}));
But this did not work ... saying that spy 'Parent' was never called.
Currently using jasmine/karma for testing. How can mock/spy the super class so I can make expectations about what the Parent class' constructor is called with and that the super.doSomething function is called?
Thanks!

ES6: How to Add a Static Class Property [duplicate]

Read the example below*, but don't pay too much attention to the EventEmitter inheritance, please – it just shows the utility of the class syntax.
I realize that the example is not correct ES2015, since there no such thing as a static class statement.
What would be the most syntactically lean way to make something like this work in ES2015?
class App extends EventEmitter {
addPage(name) {
this[name] = new App.Page;
this.emit("page-added");
}
static class Page extends EventEmitter {
constructor() {
super();
this._paragraphs = [];
}
addParagraph(text) {
this._paragraphs.push(text);
this.emit("paragraph-added");
}
}
}
Should I just split it up and use a class expression, like below? Seems less elegant.
class App extends EventEmitter {
addPage(name) {
this[name] = new App.Page;
this.emit("page-added");
}
}
App.Page = class extends EventEmitter {
constructor() {
super();
this._paragraphs = [];
}
addParagraph(text) {
this._paragraphs.push(text);
this.emit("paragraph-added");
}
};
Should I just split it up and use a class expression?
Yes, that's the way to go. If you insist on using a declaration, you'd have to make a App.Page = Page assignment afterwards.
You could have your class created inside addPage. It will create the "abstraction" I suppose you're looking for, but you'll pay in performance (each addPage call will be slower.
'use strict';
class EventEmitter {
emit(s) {
alert(s);
}
}
class App extends EventEmitter {
addPage(name) {
class Page extends EventEmitter {
constructor() {
super();
this._paragraphs = [];
}
addParagraph(text) {
this._paragraphs.push(text);
this.emit("paragraph-added");
}
}
this[name] = new Page;
this.emit("page-added");
}
}
var app = new App;
app.addPage("myPage");
app.myPage.addParagraph("Some content");
Alternatively, have both classes defined in a module, and only export the App class thus preventing pollution of the global scope.

ES6 circular dependency

This is an issue I run into fairly frequently, and I was hoping to discover the correct way to handle it.
So I have a setup like this:
parent.js:
export default {
x: 1
}
a.js:
import parent from 'parent.js'
export default parent.extend(a, { title: 'a' })
b.js:
import parent from 'parent.js'
export default parent.extend(b, { title: 'b' })
Cool, now I've got some children.
But I decide I would like to have a function in parent.js that checks if an object is an instance of a or b.
So I might do this:
parent.js:
import a from 'a'
import b from 'b'
export default {
x: 1,
checkType (obj) {
if (obj instanceof a) {
return 'a'
} else if (obj instanceof b) {
return 'b'
}
}
}
Well now that's a circular dependency. Is there an elegant way to handle this?
Having logic in the parent class that is aware of the subclasses is a serious anti-pattern. Instead, add methods in the subclasses that return the type. for instance, in a.js:
import parent from 'parent.js';
export default parent.extend(a, { title: 'a', checkObj() { return 'a'; }});
If the desired return from checkObj is always the value of the title property, then of course just:
// parent.js
export default {
x: 1,
checkObj() { return this.title; }
}
I don't know exactly what extend is doing here. I'm assuming it is some kind of subclassing mechanism.
In general, circular import dependencies, although there are ways to deal with them when truly necessary, are the universe trying to tell you that there is something wrong with the way you've structured your code.
If you're able to use es6 classes, then you can take advantage of the super() call in the constructor. I'll often do something like this:
Parent.js
export default class {
constructor(options, child) {
this.child = child;
this.x = 1;
}
checkType() {
return this.child;
}
}
A.js
import Parent from './Parent';
export default class extends Parent {
constructor(options) {
super(options, 'a');
}
}
B.js
import Parent from './Parent';
export default class extends Parent {
constructor(options) {
super(options, 'b');
}
}
If you don't want to use classes, maybe want a more FP style. You could make parent a function:
parent.js
export default function(child) {
return {
x: 1,
checkType (obj) {
return child;
}
extend (something) {
// assuming the returns something as you said
}
}
}
a.js
import parent from 'parent.js'
export default parent('a').extend(a, { title: 'a' })
b.js
import parent from 'parent.js'
export default parent('b').extend(b, { title: 'b' })

Categories

Resources