Say I have a .js file where the class is exported and the constructor is built like this:
constructor(info) {
this.info = info;
}
And on another .js file, I want to call that variable and change it, and I want the changes to be reflected in the original .js file the variable comes from, so I import the class well and inject it:
#inject(ContactGateway, Router, TestElement)
export class LoginNormal {
constructor(contactGateway, router, testelement) {
this.contactGateway = contactGateway;
this.router = router;
this.testelement = testelement;
}
And then on this same .js file, inside a function, I change the original variable:
TestInfo() {
let testinfo = this.testelement;
testinfo.info= true;
}
Upon further testing, I see that the original variable isn't being changed at all, what am I doing wrong while trying to change the original variable's boolean through a function in another file?
Thanks in advance.
You're probably just getting a different instance of TestElement injected. Custom Elements are usually scoped to a specific child container or view (depending on the context).
If you want to ensure you get the same instance everywhere, and you're sure you'll only ever need one instance, you could manually register that custom element's class as a singleton on the root container.
It's better though to simply have a separate service / state class where you keep that info property. Register that state class as a singleton / instance on the root container, and inject it in both your TestElement and LoginNormal classes. That's the recommended way to pass information around for reading/modifying between different html resources.
Something like this should typically do the trick (split/move/rename accordingly):
#inject(ApplicationState)
export class TestElement {
constructor(state) {
this.state = state;
}
}
#inject(ContactGateway, Router, ApplicationState)
export class LoginNormal {
constructor(contactGateway, router, state) {
this.contactGateway = contactGateway;
this.router = router;
this.state = state;
}
TestInfo() {
this.state.info = true; // will be changed in your TestElement too
}
}
// the hard way
export class ApplicationState {
constructor(info) {
this.info = info;
}
}
export function configure(config) {
config.instance(ApplicationState, new ApplicationState(false))
}
// or the easy way (no configure required, but needs parameterless constructor)
#singleton()
export class ApplicationState {
constructor() {
this.info = false;
}
}
Related
I am new to typescript, and I am developing a WebGL viewer and facing an issue with a global variable for the app.
The issue is like this, WebGL viewer will be the Vue component so that multiple components can be inserted on the same page. I want to store the canvas context and some other data as global data so that I can be accessed in all my ".ts" file by just importing it. Since I tried to use two components on same page, the global state meshes up with each other.
Sample code here
class AppState {
static CanvasID: string;
}
class viewer
{
constructor(canvasID: string)
{
AppState.CanvasID = canvasID;
}
getCanvasID()
{
return AppState.CanvasID;
}
}
class app
{
public viewer: viewer;
constructor(canvasID: string)
{
this.viewer = new viewer(canvasID)
}
getCanvasID()
{
return this.viewer.getCanvasID();
}
}
function clientCode() {
var app1 = new app("id1");
alert(app1.getCanvasID());
var app2 = new app("id2");
alert(app2.getCanvasID());
alert(app1.getCanvasID());
}
clientCode();
In the above code, you can see that the value of "AppState.CanvasID" gets changed when I create new app instance(app2).
I want "AppState" to be local to the each instance and i want "AppState" to be imported in multiple .ts files and use like global.
I am using rollup to build as esm module.
Please provide your suggestion on this.
This is because you made AppState.CanvasId static. So no matter how many instances of the class they are all pointing to the same reference (you don’t even create new AppStates so in retro in should be more obvious it’s the same reference.). Change that to not be static and you should be fine. Note that you will need to create a new instance of AppState in each viewer similar to how you created a viewer in the app class.
But if you really wanted it static, you could use Map as in this example. Note I changed "CanvasID" to "CanvasIDs"
class AppState {
static CanvasIDs: Map<viewer, string> = new Map<viewer,string>();
}
class viewer
{
constructor(canvasID: string)
{
AppState.CanvasIDs.set(this, canvasID);
}
getCanvasID()
{
return AppState.CanvasIDs.get(this);
}
}
class app
{
public viewer: viewer;
constructor(canvasID: string)
{
this.viewer = new viewer(canvasID)
}
getCanvasID()
{
return this.viewer.getCanvasID();
}
}
function clientCode() {
console.log("test")
var app1 = new app("id1");
alert(app1.getCanvasID());
var app2 = new app("id2");
alert(app2.getCanvasID());
alert(app1.getCanvasID());
}
clientCode();
I'm trying to create an instance of a class, this class only sets up listeners but there is no need to call any methods manually in the class.
Below is the module I load in my html file, I create a simple MVC-Pattern but I get an eslint error on line 4.
I need the constructor of the class GameController to run in order to setup the listeners but for that I need an instance of it.
I've tried making a dummy function in the GameController class and calling that. Gets rid of the error but obviously that's not the way to go, I'm looking for a clean alternative.
function init() {
let data = new GameData(),
view = new GameView(data),
controller = new GameController(data, view);
}
init();
class GameController {
constructor(data, view) {
this.data = data;
this.view = view;
this.letterGenerator = LetterGenerator();
this.view.addEventListener(Config.EVENT.CONSONANT, this.onConsonantClicked
.bind(this));
this.view.addEventListener(Config.EVENT.VOWEL, this.onVowelClicked.bind(
this));
this.view.addEventListener(Config.EVENT.STOP, this.onGameEnd.bind(this));
}
onVowelClicked() {
...
}
onConsonantClicked() {
...
}
onGameEnd(event) {
...
}
}
What I want is instantiate a GameController but not use it, eslint basically forces me to make a dummy function in the GameController class that I need to call to get rid of the error.
I am creating a PDF like this inside a react Component.
export class Test extends React.PureComponent {
savePDF() {
const source = document.getElementById('printContainer');
/* eslint new-cap: ["error", { "newIsCap": false }]*/
let pdf = new jspdf('p', 'pt', 'letter');
let margins = { top: 50,
left: 60,
width: 612
};
pdf.fromHTML(
source,
margins.left,
margins.top,
{
width: margins.width
},
() => {
pdf.save('worksheet.pdf');
}
);
}
and I am getting warning Expected 'this' to be used by class method 'savePDF' class-me
this is being called an click like this onClick={this.savePDF} see below
render() {
<Link
name="save-to-pdf"
onClick={this.savePDF}
button="secondary">
Save to PDF</Link>
<div id="printContainer" className="cf-app-segment--alt cf-hearings-worksheet">...
There are two different answers to this question, depending on how you want to handle it.
First, the reason you get this error is because of the ESLint rule https://eslint.org/docs/rules/class-methods-use-this. Specifically, this is because if something is a class method, e.g. if you are calling this.foo() to call a function, the whole reason to make it a method is because there are properties on this that you need to use.
While in many languages with class, most functions are methods, that is not the case in JS. If you have a class like
class Example {
constructor(){
this.data = 42;
}
someMethod() {
this.someHelper(this.data);
}
someHelper(value){
console.log(value);
}
}
the someHelper function would trigger the same error you are getting, because it never uses this, so you can just as easily do
class Example {
constructor(){
this.data = 42;
}
someMethod() {
someHelper(this.data);
}
}
function someHelper(value){
console.log(value);
}
In your case, you can do this. Your whole savePDF function could be moved outside of the class object.
That said, it is important to ask yourself why something like this isn't using this. In most cases, you'd expect any function that works with HTML to absolutely use this, because how else, in React, is it supposed to access the element's that React has created.
So the real answer to your question would be to drop the
const source = document.getElementById('printContainer');
line. If you need access to the HTML element being created by React, you should be using React's APIs to do so. That would be done with something like
class SavePDFButton extends React.Component {
constructor(props) {
super(props);
this.printContainer = null;
this.savePDF = this.savePDF.bind(this);
this.handlePrintContainerRef = this.handlePrintContainerRef.bind(this);
}
render() {
return (
<div>
<Link
name="save-to-pdf"
onClick={this.savePDF}
button="secondary"
>
Save to PDF
</Link>
<div
id="printContainer"
className="cf-app-segment--alt cf-hearings-worksheet"
ref={this.handlePrintContainerRef}
/>
</div>
);
}
handlePrintContainerRef(el) {
// When React renders the div, the "ref={this.handlePrintContainerRef}" will
// make it call this function, which will store a reference.
this.printContainer = el;
}
savePDF() {
// OLD: const source = document.getElementById('printContainer');
const source = this.printContainer;
// ...
}
}
I believe that's caused by the class-methods-use-this ESLint rule.
It's just letting you know that your function doesn't use this, so you can probably make it a static function.
turn it into static function
static savePDF() { ... }
Its happening because this function isnt using this meaning it dosnt need to be dynamic
I'm creating custom UI components using ES6 classes doing something like this:
class Dropdown {
constructor(dropdown) {
this.dropdown = dropdown;
this._init();
}
_init() {
//init component
}
setValue(val) {
//"public" method I want to use from another class
}
}
And when the page load I initiate the components like this:
let dropdown = document.querySelectorAll(".dropdown");
if (dropdown) {
Array.prototype.forEach.call(dropdown, (element) => {
let DropDownEl = new Dropdown(element);
});
}
But now I need to acces a method of one of these classes from another one. In this case, I need to access a method to set the value of the dropdown
based on a URL parameter, so I would like to do something like:
class SearchPage {
//SearchPage is a class and a DOM element with different components (like the dropdown) that I use as filters. This class will listen to the dispached events
//from these filters to make the Ajax requests.
constructor() {
this._page = document.querySelector(".search-page")
let dropdown = this._page.querySelector(".dropdown);
//Previously I import the class into the file
this.dropdown = new Dropdown(dropdown);
}
setValues(val) {
this.dropdown.setValue(val);
//Set other components' values...
}
}
But when I create this instance, another dropdown is added to the page, which I don't want.
I think an alternative is to create the components this way, inside other ones, and not like in the first piece of code. Is this a valid way? Should I create another Dropdown class that inherits from the original one?
A simple solution is to store the Dropdown instance on the element to avoid re-creating it:
class Dropdown {
constructor(element) {
if (element.dropdown instanceof Dropdown)
return element.dropdown;
this.element = element;
element.dropdown = this;
//init component
}
…
}
I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}
In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}