I have a simple class that is written in ES6
class MyClass {
constructor() {
this.firstVar = 'a';
this.secondVar = 'b';
}
get first() { return this.firstVar; }
set first(val) { this.firstVar = val; }
get second() { return this.secondVar; }
set second(val) { this.secondVar = val; }
allValues() {
return this.firstVar + this.secondVar;
}
}
export { MyClass };
I compile that code to ES5 via babel MyClass.es6 > MyClass.js and then try to use it in my existing ES5 codebase:
var t = require('./MyClass');
console.log(t.allValues());
But I get an error stating that t has no method allValues. Is what I'm trying to do possible?
Classes have to be instantiated. You are never creating an instance of MyClass. You are also importing it incorrectly. You are exporting MyClass as a named export and have to import it as such.
In its current form, you would have to do
var MyClass = require('./MyClass').MyClass;
var t = new MyClass();
console.log(t.allValues());
Or you can export it as default export:
export default MyClass;
In which case you can do
var t = require('./MyClass');
(at least with Babel)
Related
I went through this issue while working on the ScheduleJS framework. At some point I am provided with a HTMLCanvasElement which I want to replace with a dynamically generated component programatically. To do so, and to keep the code as clean as possible, I'd like to create my own Angular components at runtime and use the HTMLCanvasElement.replaceWith(component) method from the provided HTMLCanvasElement replacing the canvas with the dynamically created component.
Here is the Angular service I came up with, which does the job the way I expected:
import {ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable, Injector, Type} from "#angular/core";
import {ReplacementComponent} from "xxx"; // This is a higher order type of Component
#Injectable({providedIn: "root"})
export class DynamicComponentGenerator {
// Attributes
private _components: Map<string, ComponentRef<ReplacementComponent>> = new Map();
private _currentKey: number = 0;
// Constructor
constructor(private _appRef: ApplicationRef,
private _resolver: ComponentFactoryResolver,
private _injector: Injector) { }
// Methods
create(componentType: Type<ReplacementComponent>): ComponentRef<ReplacementComponent> {
const componentRef = componentType instanceof ComponentRef
? componentType
: this._resolver.resolveComponentFactory(componentType).create(this._injector);
this._appRef.attachView(componentRef.hostView);
this._components.set(`${this._currentKey}`, componentRef);
componentRef.instance.key = `${this._currentKey}`;
this._currentKey += 1;
return componentRef;
}
remove(componentKey: string): void {
const componentRef = this._components.get(componentKey);
if (componentRef) {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(componentKey);
}
}
clear(): void {
this._components.forEach((componentRef, key) => {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(key);
});
this._currentKey = 0;
}
}
So basically this service lets me create a component with .create(ComponentClass) remove it by providing the component key .remove(key) and clear() to remove all the components.
My issues are the following:
The ComponentFactoryResolver class is deprecated, should I use it anyways?
Could not manage to use the newer API to create unattached components (not able to have access to an Angular hostView)
Is there a better way to do this?
Thank you for reading me.
You could try using new createComponent function:
import { createComponent, ... } from "#angular/core";
const componentRef =
createComponent(componentType, { environmentInjector: this._appRef.injector});
this._appRef.attachView(componentRef.hostView);
In ES5, I can write it like this:
MyClass.prototype.value = (function() {
var privateVariable = 0;
return function() {
return ++privateVariable;
};
})();
But in ES6, how can I do it ?
class MyClass {
get value() {
// return ??
}
}
A direct counterpart, which is not idiomatic to ES6 classes:
class MyClass {}
MyClass.prototype.value = (() => {
let privateVariable = 0;
return function() {
return ++privateVariable;
};
})();
There are no practical reasons to make privateVariable completely unavailable from the outer scope, especially since it doesn't play well with ES6 classes. This cannot be considered a proper encapsulation because privateVariable isn't available for reflection; it cannot act as protected member as well.
There can be private member that stores a value:
class MyClass {
constructor() {
this._value = 0;
}
value() {
return ++this._value;
}
}
It could be a symbol but this doesn't make class design more secure, just introduces additional complications when a class is inherited in another module:
// should be carried around everywhere to make the class extendable
export const VALUE = Symbol('value');
class MyClass {
constructor() {
this[VALUE] = 0;
}
value() {
return ++this[VALUE];
}
}
Note that get value() {} is not same thing as value() {}.
One of TypeScript (which is a superset of ECMAScript) benefits is the encapsulation that is enforced at compilation time.
The equivalent to the ES5
function MyClass() {}
var privateVariable = 0;
MyClass.prototype = {
get value() {
return ++privateVariable;
}
};
in ES6 class syntax would be
let privateVariable = 0;
class MyClass {
get value() {
return ++privateVariable;
}
}
Whether you put all that in an IEFE or module or whatnot for local scope doesn't matter, it's the same.
The example below is simplified. I have a getter method:
class MyClass {
constructor() {}
get myMethod() {
return true;
}
}
which is processed by babel. And I want to mock it like this:
var sinon = require('sinon');
var MyClass = require('./MyClass');
var cls = new MyClass();
var stub = sinon.stub(cls, 'myMethod');
stub.returns(function() {
return false;
});
But I get the following error:
TypeError: Attempted to wrap undefined property myMethod as function
And this happens on both version 1 and 2 of sinon library.
Its an issue with how you defined your method myMethod. When you use get to define a method, it actually is treated like a property and not a method. This means you can access cls.myMethod but cls.myMethod() will throw an error as it is not a function
Problem
class MyClass {
constructor() {}
get myMethod() {
return true;
}
}
var cls = new MyClass();
console.log(cls.myMethod())
Solution
You have to update your class definition to treat myMethod as a function like below
class MyClass {
constructor() {}
myMethod() {
return true;
}
}
var cls = new MyClass();
console.log(cls.myMethod())
With this change now your sinon.stub should work fine
Trying to figure out why I can call this function from a instantiated version of this class.
The error I get is this:
Error: Call to a possibly undefined method getRegionNameForCountries through a reference with static type com.framework.model:CountryModel.
The error comes from this code:
public static function territoriesFunction( item:Object, column:DataGridColumn ):String
{
return RemoteModelLocator.getInstance().appModel.countryModel.getRegionNameForCountries( item.countriesAvailable ) + ' ('+ item.countriesAvailable.length.toString() + ')';
}
The Class I'm trying to call the function from is here:
package com.framework.model
{
import com.adobe.cairngorm.vo.IValueObject;
import com.adobe.crypto.MD5;
import com.vo.RegionVO;
import flash.utils.ByteArray;
import mx.utils.ObjectUtil;
public class CountryModel implements IValueObject
{
public static function getCountriesForRegion( regionName:String ):Array
{
try
{
var result:Array = _dataModel[regionName];
}
catch(e:Error){}
result = ( result )? result: _dataModel[CountryModel.WORLDWIDE];
return ObjectUtil.copy( result ) as Array;
}
public static function getRegionNameForCountries( countries:Array ):String
{
if( !countries || !countries.length )
{
return CountryModel.WORLDWIDE;
}
countries.sortOn("name");
var buffer:ByteArray = new ByteArray();
buffer.writeObject(countries);
buffer.position = 0;
var hash:String = MD5.hashBytes( buffer );
try
{
var regionName:String = _dataModel[hash];
return ( regionName && regionName.length )? regionName : CountryModel.CUSTOM;
}
catch( e:Error )
{
}
return CountryModel.CUSTOM;
}
}
}
You can only access static vars/method from the Class object itself (eg. MyClass.method()), or from within the class declaration (static or instantiated).
Here is a simplified example:
MyClass.staticFunction(); //allowed
var myInstance = new MyClass();
myInstance.staticFunction(); //NOT allowed
//inside the MyClass.as
this.staticFunctionInSameClass(); //allowed
What you are trying to do is access a static method from a reference to an instantiated object of that class.
To keep the same structure as you are currently doing, you either need to create a non static helper method in the class:
//CountryModel class
public function getRegionNameForCountriesHelper(countries:Array):String
{
return getRegionNameForCountries(countries); //calls the static method
}
OR just access it on the class itself.
return CountryModel.getRegionNameForCountries(item.countriesAvailable, ....);
If the Class is not known ahead of time, you can do it by casting the instance as Object, then accessing the constructor property which returns a reference to the Class.
return (Object(RemoteModelLocator.getInstance().appModel.countryModel).constructor).getRegionNameForCountries(item.countriesAvailable, ...);
That way is very messy though, without compile time checking.
I would recommending either making the class static only (don't allow instantiation), or don't use static methods in it. Without knowing what all those parts of your application are (eg. RemoveModelLocator, appModel) it's difficult to say what would be best for your circumstance.
The question is a bit silly. I am trying to implement a skill updating system. So to explain.
There is a class
class AppInfo
{
public static var power:int = 10;
public static var speed:int = 20;
}
and class SmartButton which should take a reference to one of the static variables e.g. power in a constructor and increment it on the given value.
e.g.
class SmartButton
{
public function onClick(skillReference:int = <AppInfo.power>, incrementVAlue:int = 10)
{
skillReference += incrementVAlue
}
}
I want this code to update the value of the power in AppInfo class. But this doesn't happen... I assume because the skill was passed as value not as reference...
Can you suggest a way of solving the task?
Thanks
Your assumption is correct, ints are passed by value rather than reference. One direct approach would be to encapsulate power into a reference type (a class) rather than a value type:
class Skill {
public var value:int;
public function Skill(val:int) {
this.value = val;
}
}
class AppInfo
{
public static var power:Skill = new Skill(10);
public static var speed:Skill = new Skill(20);
}
Then passing power should pass it as a reference to the instance. Though you would have to change your implemenation a bit to use skillReference.value instead.
Aside from that, I think there are a couple of ways to abstract what you want out. One way would be use an interface and leverage some dependency injection.
interface ISkills
{
function get power():int;
function set power(val:int):void;
}
class AppInfo implements ISkills
{
private static _power:int = 0;
public function get power():int { return _power; }
public function set power(val:int):void { _power = val; }
}
class SmartButton
{
public function onClick(skills:int = ISkills, skill:String = "power", incrementVAlue:int = 10)
{
skills[skill] += incrementVAlue
}
}
The idea here that you want to decouple your usage from your implementation. In this case SmartButton doesn't need to know how Skills work just how to operate on them. It loses its reference to the static class AppInfo in favor of an injectable instance. There are some advantages to this approach, it makes it easier to test and easier to swap implementations later if you decide that a static class isn't the best implementation idea without having to update a bunch of classes/code. Also, rather than injecting ISkills into the method, you could inject it into the constructor of SmartButton, and keep a private reference to the skill container.
Another approach would be to use a functional approach.
class SmartButton
{
public var defaultWorker:Function = function(val:int):void {
AppInfo.power += val;
}
public function onClick(worker:Function = undefined, incrementValue:int = 10):void
{
if(worker == undefined) worker = defaultWorker;
worker.call(this, incrementValue);
}
}
Again, in this case, rather than tightly coupling your implementation to use the AppInfo class directly, you inject a "worker" for it do the work for you (if the worker is undefined then use the default worker. You can then swap out which property gets changed by changing the closure that gets passed in. For instance if you wanted to change speed instead then you would call:
var smartButton:SmartButton;
smartButton.onClick(function(val:int):void { AppInfo.speed += val});
Not quite as succinct as it could be, but it gets the job done.
The obligatory "elegantly sophisticated" approach using the command pattern:
Interface Command {
function execute():void;
}
Class UpdatePower implements Command {
private var appInfo:AppInfo;
private var delta:int;
public function UpdatePower(appInfo:AppInfo, delta:int) {
this.appInfo = appInfo;
this.delta = delta;
}
public function execute():void {
appInfo.delta += delta;
}
}
Class SmartButton {
var command:Command;
public function SmartButton(command:Command) {
this.command = command;
}
public function onClick(event:Event):void {
command.execute();
}
}
I would probably implement this in a slightly different way.
Maybe something like;
class Properties {
private var _properties:Dictionary = new Dictionary();
public function setValue(key:String, value:int) {
_properties[key] = value;
}
public function getValue(key:String):int {
if( !_properties[key] ) return 0;
else return _properties[key];
}
public function modifyValue(key:String, value:int) {
setValue(key, getValue(key) + value);
}
}
class SmartButton
{
public function onClick(target:Properties, key:String, incrementValue:int = 10) {
target.modifyValue(key, incrementValue);
}
}
Or something along those lines.