How to deal with fn.call(this) replacing original this - javascript

To begin, I have an application that is used for managing employees. When the user creates a new instance of the application, I would like for them to have an option to submit a function that will run before anything else in the application does. The problem is that I need to add functionality to the end of that function so I need it passed back to the application.
However, if I use fn.call(this) in the StateManager.js class, it then overrides the this of state manager and gets rid of the functionality of StateManager. The exact error returned is Uncaught TypeError: this.onPreload is not a function
Essentially, when a new instance is created, I would like to take the user's preload function and pass it to StateManager.js where it will be adjusted.
Here is demonstration code:
class Application {
constructor(options = {}) {
return new User(options);
}
}
class User {
constructor(options) {
this._options = options;
this.state = new StateManager(this);
this.job = new Job(this);
this.init();
}
init() {
this.state.onPreload = this._options.preload;
this.state.preload.call(this);
}
}
class Job {
constructor(user) {
this.user = user;
}
changeTitle(title) {
this.user.jobTitle = title;
}
}
class StateManager {
constructor(user) {
this.user = user;
this.onPreload = null;
}
preload() {
this.onPreload();
}
}
const options = {
preload: preload
};
const app = new Application(options);
function preload() {
app.job.changeTitle('CEO');
}
index.js
import { Application } from './Application.js';
const options = {
preload: preload
};
const app = new Application(options);
function preload() {
// Access some irrelevant function in job that sets a new value
app.job.changeTitle('CEO');
}
app.js
import { User } from './User.js';
export class Application {
constructor(options = {}) {
return new User(options);
}
}
user.js
import { StateManager } from './StateManager.js';
import { Job } from './Job.js';
export class User {
constructor(options = {}) {
this._options = options;
this.state = new StateManager(this);
this.job = new Job(this);
this.init();
}
init() {
this.state.onPreload = this._options.preload;
this.state.preload.call(this);
}
}
statemanager.js
export class StateManager {
constructor(user) {
this.user = user;
this.onPreload = null;
}
preload() {
this.onPreload();
// My custom functionality to add at the end.
}
}

preload() is referring to the global variable app, but it's being called in the function used to initialize app in the first place. It needs to receive the User object being initialized, rather than referring to the global variable.
Use this.state.onPreload = this._options.preload.bind(this); to bind the context of the preload function to that object.
You could also change StateManager.preload() to use this.onPreload.call(this.user);. But this might create an inappropriate dependency that doesn't apply in all cases. If I understood all the relationships better, I might be able to decide this better.
class Application {
constructor(options = {}) {
return new User(options);
}
}
class User {
constructor(options) {
this._options = options;
this.state = new StateManager(this);
this.job = new Job(this);
this.init();
}
init() {
this.state.onPreload = this._options.preload.bind(this);
this.state.preload();
}
}
class Job {
constructor(user) {
this.user = user;
}
changeTitle(title) {
this.user.jobTitle = title;
}
}
class StateManager {
constructor(user) {
this.user = user;
this.onPreload = null;
}
preload() {
this.onPreload();
}
}
const options = {
preload: preload
};
const app = new Application(options);
console.log(app.jobTitle);
function preload() {
this.job.changeTitle('CEO');
}

Related

How to use use es6 classes in a VUE js app so that methods and data are bound to a class

I'm lookin for a better sulution to use ES6 classes in a VUE JS app.
I tried using es6 classes for my vue js app.
class App {
constructor(components) {
this.el = "#app";
this.data = {
dialog : new Dialog()
};
this.watch = {
"watcher": function (val) {
}
};
this.methods = {
get(){
console.log('get');
}
}
}
}
class Dialog {
constructor(components) {
this.title = "";
this.content = "";
}
}
var app = new Vue(new App());
Methods and data are not part of the class.
I am looking for a solution where it is posible to bind the data directly as properties and use the methods as you would do within a JS class.
class App {
constructor(components) {
this.dialog = new Dialog();
};
get(){
console.log('get');
}
}
Thanks in advance!

How to get global variable to stay in angular constructor & promise, calling a func that needs the data from dif component

Not an expert at angular far from it. But I've been looking deeply and i cant figure out why my other components that call the function types, runs before the constructor. and to solve it where do i put the " echo " function? everything works likes a charm except for the fact that echo is called before types. what or how do i make echo come first to run before any other function. i cant hook it up to the promise because it takes data from another component. i ran a if statement to check if the global variable exist and obviously doesn't because of the order of processes.
import { Injectable, OnInit, OnDestroy } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { Observable, of } from "rxjs";
import { Router, ActivatedRoute } from '#angular/router';
import { Location } from '#angular/common/'
import { DataService } from './products.service';
import { BehaviorSubject, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({ providedIn: "root" })
export class CartService implements OnInit, OnDestroy {
public data: any = { "productObjs": [] }
public array: any;
public datap: any;
private sub: Subscription;
//loop up the id if specexist remove thespec if empty remove id
constructor(public dataservice: DataService, private http: HttpClient) {
this.echo()
}
echo() {
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/json');
let prom = new Promise((resolve, reject) => {
this.http.get('../assets/productCategories/products.json', { headers }).toPromise().then((data: any) => {
console.log(data)
var dat = this.datap
resolve(dat)
this.datap = data
return dat
}).then((dat) => { this.nextt(dat) });
})
return this.datap;
}
nextt(datap) {
console.log(datap)
this.datap = datap
}
// types is called from another component and runs before promise finishes
types(type) {
if (this.datap) {
console.log(this.datap)
let that = this
var func = type.func
var rtype = type.type
var arr;
switch (func) {
case "random":
return that.sortByRandom()
break;
case "category":
return that.sortByCategory(rtype)
break;
default: "specific"
return that.sortBySpecific(rtype)
}
console.log(this.array)
console.log(this.array)
console.log(this.array)
console.log(this.datap)
return this.array;
}
}
getArray() {
if (this.datap) {
console.log(this.array)
return this.array
}
}
sortBySpecific(specific) {
let that = this
console.log(that.datap)
var type = that.datap.product.filter(function(objj) {
return objj.type === specific;
})
return type
}
sortByCategory(category) {
let that = this
var type = this.datap.product.filter(function(objj) {
return objj.productCategory === category;
})
return type
}
sortByRandom() {
var cats = []
var picked = []
var sproducts;
let that = this
this.datap.productObjs.forEach((obj) => {
var randomnum2 = Math.floor(Math.random() * this.datap.productObjs.length)
cats.push(obj.category)
})
var randomnum = Math.floor(Math.random() * cats.length)
var selectedCats = this.datap.product.filter(function(objj) {
return objj.productCategory === cats[randomnum];
});
sproducts = selectedCats
var x = sproducts[Math.floor(Math.random() * sproducts.length)]
picked.push(x)
that.array = picked
return picked
}
addToCart(ps, pobj) {
var checkarray = this.data.productObjs.filter(function(obj) {
return obj.productSpec === ps;
});
console.log(checkarray)
if (checkarray.length <= 0) {
this.data.productObjs.push(pobj)
}
}
getItems() {
return this.data.productObjs
}
clearCart() {
this.data.productObjs = []
}
clearProduct(objspec) {
var newarray = this.data.productObjs.filter(function(obj) {
return obj.productSpec !== objspec;
});
this.data.productObjs = newarray;
}
changeInventory() {
//update pricing from inputs
}
checkout() {
this.http.post('http://localhost:4201/api', this.data).subscribe((res) => {
console.log(res)
var json = res
if (json['bool'] === "false") {
//cant check out
// this checks inventory also.
//pop up error problem with pricing.
}
if (json['bool'] === "true") {
//can check out
//neeeds to request paypal to send productinfo and once payment response is succeded send valid, and delete from database.
}
})
}
ngOnInit() {
}
ngOnDestroy() {
this.sub.unsubscribe();
console.log(this.sub)
console.log(this.datap)
}
}
Check this article for how to initialize global data:
https://www.cidean.com/blog/2019/initialize-data-before-angular-app-starts/
BTW, you should never call business logic in a constructor like:
this.echo()
Instead you should call it in the component it need the data, maybe ngOnInit in that component when it is needed.
It is usually recommended to use constructor only for dependency injection mainly. For other initialization consider using Angular life cycle hooks (Difference between constructor and ngOnInit).
Since you want to run your function echo() which gets called from child component you can call it from ngOnInit() in child component.
Then if you have anything that needs to be called from parent to child. You can call child from parent components ngAfterViewInit() method. (In your case types() function)

Force the use of a factory

I have a class where I want a simple factory method:
class GTree{
public static createNode(){
return new GNode();
}
}
This means that I don't want to allow the consumer to immediately instantiate the GNode.
How do I properly implement this?
Obviously I can't do:
class GNode{
constructor(){
throw TypeError("This is nonsense");
}
}
Because then I can't create nodes anymore at all.
How do I force using the factory?
Here's a simpler scheme than my earlier comments. Just define the GNode class in a private (but shared) scope and thus that's the only place the constructor can be called from and also reset the .constructor property so it doesn't leak out:
const GTree = (function() {
class GNode {
constructor() {
}
someOtherMethod() {
console.log("someOtherMethod");
}
}
// reset public .constructor
GNode.prototype.constructor = function() {
throw new Error("Can't call GNode constructor directly");
};
class GTree {
constructor() {
this.nodes = [];
}
createNode() {
let node = new GNode();
this.nodes.push(node);
return node;
}
get length() {
return this.nodes.length;
}
}
return GTree;
})();
let tree = new GTree();
let node1 = tree.createNode();
let node2 = tree.createNode();
node1.someOtherMethod();
console.log(tree.length + " nodes");
You can't really do that in javascript, but you can do this:
export class GTree {
public static createNode(name: string): GNode {
return new GNodeImpl(name);
}
}
export interface GNode {
name: string;
}
class GNodeImpl implements GNode {
constructor(public name: string) {}
}
(code in playground)
Only GTree and the GNode interface are exported, meaning that it's not possible to instantiate GNodeImpl from outside the module.
I added the name property just for the example.

Using Mediator Pattern with webpack and ES6 Modules import export

I have multiple widgets written and need to communicated between them. I am trying to use the mediator pattern to do that. So I have something like below. Problem I am having is mediator is 2 different instances instead of just 1. So widget_2 is not actually subscribing to correct event/message.
I am using WebPack/Es6
How can I overcome that?
//mediator.js
//ref: https://github.com/HenriqueLimas/mediator-pattern-es6/blob/master/src/mediator.js
//app.js
import Widget_1 from './widget_1.js';
import Widget_2 from './widget_2.js';
new widget_1 = new Widget_1();
new widget_2 = new Widget_2();
widget_1.run();
widget_2.run();
//widget_1.js
import Mediator from './mediator.js';
const mediator = new Mediator();
export default class Widget_1 {
constructor() {
}
run() {
mediator.publish('widget1', 'hello there I am widget 1');
}
}
//widget_2.js
import Mediator from './mediator.js';
const mediator = new Mediator();
export default class Widget_2 {
constructor() {
}
run() {
mediator.subscribe('widget1', function(message) {
console.log('widget 1 says:' + message);
});
}
}
If you make your mediator a singleton - the same object will by definition be shared anywhere you use it. This modification could look smth like this.
'use strict';
class Mediator {
constructor() {
this.channels = {};
}
subscribe(channel, fn) {
if (!this.channels[channel]) this.channels[channel] = [];
this.channels[channel].push({
context: this,
callback: fn
});
}
publish(channel) {
if (!this.channels[channel]) return false;
var args = Array.prototype.slice.call(arguments, 1);
this.channels[channel].forEach(function(subscription) {
subscription.callback.apply(subscription.context, args);
});
return this;
}
installTo(obj) {
obj.channels = {};
obj.publish = this.publish;
obj.subscribe = this.subscribe;
}
}
var mediator = new Mediator();
export mediator;
But then you don't really need a es6 class here, as you will be using it only once to create a new object.

ES6 / Meteor Singleton pattern with inheritance

I have programmed a singleton using this blog:
http://amanvirk.me/singleton-classes-in-es6/
But I need a singleton on both client and server side.
That is, I have three programs: common.js
export class AppsManagerCommon {
constructor(options) {
// general options
this.options = options;
// list of registered apps
this.apps = [];
}
registerApp(app) {
a = this.apps.find(function (a) {return a.name === app.name;});
console.log(a);
this.apps.push(app);
}
}
client.js
import { AppsManagerCommon } from 'common.js';
let instanceAppsManager = null; // singleton pattern
export class AppsManager extends AppsManagerCommon {
constructor(options) {
if (!instanceAppsManager) { // not yet instantiated
super(options);
instanceAppsManager = this;
}
return instanceAppsManager;
}
}
server.js (identical to client.js)
import { AppsManagerCommon } from 'common.js';
let instanceAppsManager = null; // singleton pattern
export class AppsManager extends AppsManagerCommon {
constructor(options) {
if (!instanceAppsManager) { // not yet instantiated
super(options);
instanceAppsManager = this;
}
return instanceAppsManager;
}
}
The singletoc can be used sucessfully with:
a = new AppsManager();
a.registerApp({name:'app1'});
but as soon as I do
b = new AppsManager(); // should be the same instance
I get an error:
ReferenceError: this hasn't been initialised - super() hasn't been called
at BabelRuntime.possibleConstructorReturn (packages/babel-runtime.js:206:13)
I can more or less understand what the error means, but I have no clue how I could resolve the issue.
EDIT 1
NB The existence check in registerApp does not work, but is not a problem for now
This is a working solution:
common.js
let instanceAppsManager = null; // singleton pattern
export class AppsManagerCommon {
constructor(options) {
if (!instanceAppsManager) { // not yet instantiated
instanceAppsManager = this;
// general options
this.options = options;
// list of registered apps
this.apps = [];
}
return instanceAppsManager;
}
registerApp(app) {
this.apps.push(app);
}
}
server.js (identical to client.js)
import { AppCommon, AppsManagerCommon } from 'common.js';
export class AppsManager extends AppsManagerCommon {
constructor(options) {
super(options);
}
}

Categories