How to decorate a Web Component class - javascript

I'm creating a #Component decorator that intercedes the constructor of a class to carry out some work after construction. As can be seen in the following code, the work is implemented in an init method.
export function Component (Cls) {
function Class (...args) {
let self = new Cls (...args); // (1)
init (self, ...args);
return self;
}
Class.prototype = Cls.prototype;
return Class;
}
When I test this code on a regular class all works fine. This is a working example:
class Base { ... }
#Component
class Core extends Base {
constructor () {
super (); // init is invoked
}
fx () { console.log ('Core.fx') }
fy () { console.log ('Core.fy') }
}
Nevertheless, when I try to decorate a web component a TypeError: Illegal constructor message is obtained.
#Component
class Core extends HTMLElement {
constructor () {
super ();
}
fx () { console.log ('Core.fx') }
fy () { console.log ('Core.fy') }
}
customElements.define ('x-core', Core);
let coreX = document.createElement ('x-core');
document.body.appendChild (coreX);
I realise the problem is that HTMLElement's do not support direct construction through new operator - see (1) on first listing - but I need a procedure to decorate constructor of any class even though they are custom elements.
Some Idea?
Working Settings: Chrome 68 ยท Babel 7.0.0-beta.51 with babel-plugin-transform-decorators-legacy

You can return a class to avoid direct new.
function Component(cls) {
class c extends cls {
constructor() {
super()
console.log(this)//init
}
}
return c
}

Related

i want to extend from one class but delete some property

I have some class for crud API service class
I want to extend it but spear few properties
for exam this is my class
class Parent {
public propertyToKeep: any;
public propertyToDelete: any;
constructor() { }
}
this is the child class
class Child extends Parent {
constructor() {
super();
}
}
Another file
where I don't want to see and get access to
export class comeComponent {
constructor(private child: Child) {
this.child.propertyToKeep // work
this.child.propertyToDelete // error and I can't even see it
}
}
I just came across the same use case as you and here's how I did it:
const Omit = <T, K extends keyof T>(Class: new () => T, keys: K[]): new () => Omit<T, typeof keys[number]> => Class;
Then you can use it like so :
class Child extends Omit(Parent, ['propertyToDelete']) {}
As you can see child only has one property now (it also works with methods).
The package from #nestjs/swagger has some nice helpers if you're dealing with a NestJS API. Their implementation is more complex so I guess they are keeping other stuff like their own property decorators (I am pretty new to Typescript so maybe I miss the point of all they are doing).
P.S: French guy tried to answer for first time with imperfect English so please be kind ^^
Here is one way to do it:
class Parent {
propertyToKeep = 'hi';
propertyToDelete = 'bye';
constructor() {}
}
class Child extends Parent {
constructor() {
super();
delete this.propertyToDelete;
}
}
const myObject = new Child();
console.log(myObject);
/* OUTPUT:
{
"propertyToKeep": "hi"
}
*/
You need to use Object.defineProperty function, for make a restriction in descriptor, enumerable to false and getter, setter with specific condition, here a complete example:
//A small example of how to make an invisible property in Child class.
class Parent{
constructor(){
this.propertyToKeep = "Visible";
this.propertyToDelete = "Not visible in subclass child";
}
}
Object.defineProperty(Parent.prototype, "propertyToDelete", {enumerable: false,
configurable: true,
get: function(){
if(!(this instanceof Child)){
return this._propertyToDelete;
}
},
set: function(v){
if(!(this instanceof Child)){
this._propertyToDelete = v;
}
}});
Object.freeze(Parent.prototype);
class Child extends Parent {
constructor() {
super();
}
}
//console.log(Child.prototype);
let chd = new Child();
console.log("Child: --------------------------------");
console.log(chd);
console.log(chd.propertyToDelete); //Undefined
console.log("Parent: -------------------------------");
let prt = new Parent();
console.log(prt);
console.log(prt.propertyToDelete); //"Not visible in subclass child"
/*let chdObj = Object.getOwnPropertyDescriptors(Child.prototype);
console.log(chdObj);*/
class SomeComponent{
#child;
constructor(child) {
this.#child = child;
console.log(this.#child); //{propertyToKeep: "Visible"}
console.log(this.#child.propertyToKeep /*work*/);
console.log(this.#child.propertyToDelete /*undefined*/);
}
}
//Now i will invoke SomeComponent
console.log("SomeComponent: -------------------------");
let sc = new SomeComponent(new Child());
That's not possible. If you declare an attribute in a parent class, you can't restrict his visibility in a child class.
From the model design point of view, that doesn't make sense as well. The problem you are exposing here indicates that your class hierarchy is not well designed and you must rethink and redesign it.

Calling parent constructor in javascript

Wonder if anyone can help?
I'm trying to call a parent's constructor or at a minimum get a reference to the parent class in some way without hardcoding anything.
class A {
static foo(options) {
parent::__construct(options); <- this is how you would get the parent in php
}
}
class B extends A {
}
Is this possible?
In a javascript class (and OOP in general), a static method is not part of an instance and therefore the object it resides in does not have a constructor.
You should avoid using static method for this sort of thing and use a standard constructor and call super() to call the parent constructor.
class A {
constructor(options) {
console.log('Options are:');
console.log(options);
}
}
class B extends A {
constructor(options) {
super(options);
}
}
const item = new B({item1: 'abc'});
Further reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
You can use super() to call parent constructor
class A {
constructor() {
console.log('I\'m parent ');
}
foo(){
console.log('Class A: Called foo');
}
}
class B extends A {
constructor() {
super();
}
foo(){
super.foo()
}
}
const b = new B();
b.foo();

How to create a decorator that adds a single function to the component class?

I should preface this by saying I understand very little about es7 decorators. Basically what I want is a decorator called #model that adds a function to a component called model. So for example I'd call it like
#model class FooBar extends Component { }
and then the FooBar class would now have the model function.
Here's what I tried:
Model.js
export default function reactModelFactory( ctx ){
return (key)=>{
return {
onChange: (e)=>ctx.setState({[key]:e.target.value}),
value: ctx.state[key],
name: key
};
};
};
function modelDecorator() {
return function(ctx){
return class extends ctx{
constructor(...args){
super(...args);
this.model = reactModelFactory(this);
}
}
}
}
export { modelDecorator as model };
Login.js
import React,{PureComponent} from 'react';
import {model} from './Model';
#model class Login extends PureComponent{}
React throws with error message:
TypeError: Super expression must either be null or a function, not object
I have no idea what this means. I'm looking for some help in getting my decorator working, and a bonus would be to understand the concept of decorators at all.
To add to #dfsq's answer (I'm assuming it does what you want), you can go a step further in terms of interface performance by adding model() to the prototype instead of to each instance like this:
export default function reactModelFactory() {
return function model (key) {
return {
onChange: (e) => this.setState({ [key]: e.target.value }),
value: this.state[key],
name: key
};
};
};
function modelDecorator(Class) {
Object.defineProperty(Class.prototype, 'model', {
value: reactModelFactory(),
configurable: true,
writable: true
});
return Class;
}
This is much better for performance as it causes the decorator to modify the existing class's prototype a single time with the model member method, rather than attaching a scoped copy of the model method within the anonymous extended class's constructor each time a new instance is constructed.
To clarify, this means that in #dfsq's answer, reactModelFactory() is invoked each time a new instance is constructed, while in this answer, reactModelFactory() is only invoked a single time when the decorator is activated on the class.
The reason I used configurable and writable in the property descriptor is because that's how the class { } syntax natively defines member methods on the prototype:
class Dummy {
dummy () {}
}
let {
configurable,
writable,
enumerable
} = Object.getOwnPropertyDescriptor(Dummy.prototype, 'dummy');
console.log('configurable', configurable);
console.log('enumerable', enumerable);
console.log('writable', writable);
Your model decorator should not return new function. ctx will be passed to modelDecorator itself. So you really just need to return newly extended class from it:
function modelDecorator(ctx) {
return class extends ctx {
constructor(...args) {
super(...args);
this.model = reactModelFactory(this);
}
}
}
Note, that the syntax you tried would work if your decorator was supposed to be used like this (Angular style decorators):
#model({ modelName: 'user' })
class Login extends PureComponent {}
Then you would need extra closure to keep passed parameters into your decorator:
function modelDecorator({ modelName }) {
return (ctx) => {
console.log('model name', modelName)
return class extends ctx {
constructor(...args) {
super(...args);
this.model = reactModelFactory(this);
}
}
}
}

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.

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!

Categories

Resources