I am quite new to TypeScript and I experience a strange problem at the moment. I create an instance of my main class when the document is ready, using JQuery.
var main: MainApp;
$(document).ready(function () {
main = new MainApp();
});
The simplified MainApp Class:
class MainApp {
// Helper Objects
net: AppNetworking;
urlHelper: UrlHelper;
cat: Category;
// Construction
constructor() {
this.net = new AppNetworking();
this.urlHelper = new UrlHelper();
}
// Ajax Callback with Data needed to initialize the "cat" object
private AjaxCallback(categoryData){
this.cat = new Category(categoryData);
}
// Event Handler for an HTML-Element
// As it would be called anonymously in JS I decided to make it a static function
static onClickSendButton(): void{
// Using some members of the MainApp
var hostUrl: string = main.urlHelper.getQueryStringParam("HostUrl");
if (main.cat.isValidCategory()) {
main.sendCategory();
}
}
sendCategory(): boolean {
// Some logic to send data via AJAX
}
}
The function is being registered to the onClick Event of a Button on construction of the MainApp Class.
$("#btnSendCat").click(MainApp.onClickSendButton);
When the function onClickSendButton() gets called, it produces the error:
Uncaught TypeError: Cannot read property 'isValidCategory' of undefined
When debugging, the urlHelper Instance is defined, but the cat Instance is undefined. As I do not touch the instance cat anywhere in my application, I'm really confused how it is undefined. Also when checking the main variable all members are defined!
Am I doing anything illegal here? Could there be issues with that code?
Completely revised answer. I actually answered with the two most common scenarios for this error, but actually your problem is different.
The usual answers are
Make sure you are referencing .js files, not .ts files
Make sure you are loading scripts in the correct order
In your case, this is not the problem and your code is sufficient to recreate the issue.
I have put together the following test, filling in the blanks - and it works as expected.
app.ts
declare var main: MainApp;
class AppNetworking {
}
class UrlHelper {
getQueryStringParam(input: string) {
console.log('Got here getQueryStringParam');
return input;
}
}
class Category {
isValidCategory() {
console.log('Got here isValidCategory');
return true;
}
}
class MainApp {
// Helper Objects
net: AppNetworking;
urlHelper: UrlHelper;
cat: Category;
// Construction
constructor() {
this.net = new AppNetworking();
this.cat = new Category();
this.urlHelper = new UrlHelper();
}
// Event Handler for an HTML-Element
// As it would be called anonymously in JS I decided to make it a static function
static onClickSendButton(): void{
// Using some members of the MainApp
var hostUrl: string = main.urlHelper.getQueryStringParam("HostUrl");
if (main.cat.isValidCategory()) {
main.sendCategory();
}
}
sendCategory(): boolean {
// Some logic to send data via AJAX
return true;
}
}
index.html snip
<div id="btnSendCat">BTN SEND CAT</div>
<script src="app.js"></script>
<script src="Scripts/jquery-2.1.1.min.js"></script>
<script>
var main;
$(document).ready(function () {
main = new MainApp();
$("#btnSendCat").click(MainApp.onClickSendButton);
});
</script>
The result of running this test is the following output in the console window:
"Got here getQueryStringParam" app.js:10
"Got here isValidCategory" app.js:19
I left some important parts of my App out, I'm sorry. Later in the project I used to reinitialize that Category Object. This re initialization was done in an AJAX-Callback Function. This function runs outside of my Object and this wont be my MainApp Class but the Window. I think it's what you call an anonymous function in JavaScript.
I fixed that issue by taking use of my global main Variable
class MainApp {
// Called anonymous so it should be a static function
private AjaxCallback(categoryData){
// this.cat = new Category(categoryData); ! this will be the Window Instance and not a MainApp Instance
main.cat = new Category(categoryData); // Initialization using the "main" variable
}
}
The call in my onClickSendButton Method to this.cat succeeds now, as this.cat was reinitialized correctly.
This video helped me a lot in my researches: Understanding "this" in TypeScript
Related
I want to import an element and bind one of its properties. My import fails silently. I expect the value of userLocal to be an object. But, instead, userLocal is undefined.
my-app.html
<link rel="import" href="/src/app-state/state-user-local.html">
...
<state-user-local user-local="{{userLocal}}"></state-user-local>
...
<script>
/* #polymerElement */
class MyApp extends Polymer.Element {
static get is() { return 'my-app; }
static get properties() { return {
userLocal: {
type: Object,
notify: true,
},
...
}}
}
window.customElements.define(MyApp.is, MyApp);
</script>
with the following error message.
The element state-user-local is not defined
I know the import definition is correct because I am using VSCode and when I command+click the import it takes me to the correct file.
I know there is no problem with the <state-user-local> element itself because I successfully import it into other element/s in the app and obtain the expected value of userLocal there.
This problem sounds like what is described at the following links.
The element xxx is not defined (issue #54)
Recognize elements registered with MyElem.is (issue #540)
The first link discusses using "/* #polymerElement */ above the class" which is what I have tried (see above code) without success.
It seems to me that you didn't define the <state-user-local> element inside your file; you defined <my-app>. If you want to use the tag name <state-user-local> you need to define it as such.
<script>
class StateUserLocal extends Polymer.Element {
static get is() { return 'state-user-local'; }
static get properties() { return {
userLocal: {
type: Object,
notify: true,
},
...
}}
}
window.customElements.define(StateUserLocal.is, StateUserLocal);
</script>
I am currently writing a wrapper around socket.io. Comming from a very object-oriented background, I want to implement the concept of Models in my framework/wrapper.
If you happen to know socket.io you might know that you get the data that is associated with an event as a parameter, now I have implemented a custom routing system where the handler of the route gets the data in an express.js like request object.
The idea is to have model classes that look something like this:
class XRequestModel
#v.String({ message: 'The username must be a string!' })
public userName: string;
}
And the route event might look something like this:
#RouteConfig({ route: '/something', model: XRequestModel })
class XEvent extends Route {
public on(req: Request<XRequestModel>, res: Response) {
// Handle Event
}
}
And to complete the example here is how the request object might look like:
class Request<T> {
public data: T;
}
Now generics in typescript are very limited since the type information is removed after compilation, I can not use the generic Request parameter ( which is the type of the model ) to get metadata from the model - Metadata, in this case, is the validation decorator. To overcome this issue I give a reference of the Model class to the RouteConfig of the RouteEvent, which is internally used and would allow me to create instances of the model, get the properties and so on...
The idea here is to give the handler of a route, a request object with pre-validated, typesafe data.
The thing holding me back from this, is the fact that unused properties, get removed after compilation by typescript, So I cannot get the metadata of the model. Initializing the class-property would solve this:
class XRequestModel
#v.String({ message: 'The username must be a string!' })
public userName: string = '';
}
But I think this makes for some very verbose syntax, and I dont want to force the user of this wrapper to init all the model properties.
An implementation side-note:
The user of the framework has to register the classes to a 'main' class and from there I can get the Route-class via decorator reflection.
When I try to get the properties of the model without initialized properties - First model example.
// Here the route.config.model refers to the model from the RouteConfig
Object.getOwnPropertyNames(new route.config.model());
>>> []
Here is what I get with initialized properties:
Object.getOwnPropertyNames(new route.config.model());
>>> [ 'userName' ]
Here a link to the GitHub repository: https://github.com/FetzenRndy/SRocket
Note that models are not implemented in this repo yet.
Basically, my question is: How can I get the properties of a class that has uninitialized properties after compilation.
The problem is that if no initialization happens, no code is emitted for the fields, so at runtime the field does not exist on the object until a value is assigned to it.
The simplest solution would be to initialize all fields even if you do so with just null :
class XRequestModel {
public userName: string = null;
public name: string = null;
}
var keys = Object.getOwnPropertyNames(new XRequestModel())
console.log(keys); // [ 'userName', 'name' ]
If this is not a workable solution for you, you can create a decorator that adds to a static field on the class and the walk up the prototype chain to get all fields:
function Prop(): PropertyDecorator {
return (target: Object, propertyKey: string): void => {
let props: string[]
if (target.hasOwnProperty("__props__")) {
props = (target as any)["__props__"];
} else {
props = (target as any)["__props__"] = [];
}
props.push(propertyKey);
};
}
class XRequestModelBase {
#Prop()
public baseName: string;
}
class XRequestModel extends XRequestModelBase {
#Prop()
public userName: string;
#Prop()
public name: string;
}
function getAllProps(cls: new (...args: any[]) => any) : string[] {
let result: string[] = [];
let prototype = cls.prototype;
while(prototype != null) {
let props: string[] = prototype["__props__"];
if(props){
result.push(...props);
}
prototype = Object.getPrototypeOf(prototype);
}
return result;
}
var keys = getAllProps(XRequestModel);
console.log(keys);
I'm trying to write a React component which subscribes to data on the server by passing the collection name as a prop. (in the example the name is still hardcoded to keep things simple) Since the collection names can change dynamically, I'm calling a method to construct the publication and server collection variable. Now the theory is to construct the data publication, server and client mongo collection variable in a parent component and the subscribe to this publication in the child component. The parent component is set up like this:
export default class TilesSingle extends TrackerReact(Component) {
newDataCollection(){
Meteor.call("setupData","randomcollectionname");
if(!window["randomcollectionname"]){
window["randomcollectionname"] = new Meteor.Collection("randomcollectionname");
}
}
render(){
this.newDataCollection();
return(
<div>
<DataObject />
</div>
)
}
}
The setupData method looks like this:
Meteor.methods({
setupData(collectionname){
if(!global[collectionname]){
global[collectionname] = new Meteor.Collection(collectionname);
Meteor.publish("pub."+ collectionname,function() {
return global[collectionname].find();
});
}
}
})
And the child DataObject component looks like this:
export default class DataObject extends TrackerReact(Component) {
constructor(){
super();
this.state = {
subscription: {
tiledata: Meteor.subscribe("pub.randomcollectionname"),
}
}
}
componentWillUnmount(){
this.state.subscription.tiledata.stop();
}
findData(){
post = window['randomcollectionname'].findOne();
console.log(post)
return post;
}
render(){
obj = this.findData();
if(!obj){
obj = {};
obj["score"] = 0;
}
return(
<div>
<h4>{obj.score}</h4>
</div>
)
}
}
Notice that I'm logging post in the findData() function. When I refresh, the console logs:
undefined
undefined
> Object {..}
> Object {..}
undefined
undefined
It is almost as if the subscription works and then stops working. I'm thinking that it has to do with the order in which react renders these components, and that the method is not finished on the server by the time the client tries to render the child component. Any Ideas?
Today, I trying to make my own annotation to use it in Angular2 project.
This annotation must add body class on specific component.
So, I've search on Angular source code, but it's so difficult to see where and how annotation was created.
For the moment, I've tried this :
export function BodyClass(classes: any): ClassDecorator{
classes = classes || {};
if(typeof classes === 'string'){
classes = {classes};
}
return function changeBodyClass(){
console.log('ici');
}
}
And my component :
import {Component} from "angular2/core";
import {RouterOutlet} from "angular2/router";
import {BodyClass} from "../core/annotations/body_class";
#Component({
selector: 'my-component',
template: `
<router-outlet></router-outlet>
`,
})
#BodyClass('test')
export class MyComponent{
}
My console log in the annotation was correctly fired, but I want to use "DOM" class from angular2/src/platform/dom/dom_adapter to add my classes, but DOM in undefined when on console log it (not need to instanciate it).
However, the DOM class works well directly in my component.
I add classes in ngOnInit function, and remove them on ngOnDestroy.
But, I want this behavior on many component, and I think a new annotation is the best way.
Maybe you have a better idea for this ? Or to work with DOM class on a annotation ?
Thanks !
It's not so obvious since you want to work at the component instance level and not at the component class one. So you need to wrap the corresponding constructor function.
export function MyComponentDecorator(value: string) {
return function (target: Function) {
var original = target;
function construct(constructor, args) {
var c : any = function () {
// Call the target constructor
var ret = constructor.apply(this, args);
// Add your additional processing
return ret;
}
c.prototype = constructor.prototype;
return new c();
}
// The new constructor
// Don't forget to add a name at this function
// since Angular requires it for the template compilation
var f : any = function WrappedComponent(...args) {
return construct(original, args);
}
f.prototype = original.prototype;
return f;
}
}
At this point, you wrap the component instance but you lose the component metadata. You need to copy them by hand:
f.prototype = original.prototype;
var annotations = Reflect.getMetadata('annotations', original));
Reflect.defineMetadata('annotations', annotations, f);
var properties = Reflect.getMetadata('propMetadata', original));
Reflect.defineMetadata('propMetadata', properties, f);
return f;
To use this decorator simply add it before or after the #Component one:
#MyComponentDecorator()
#Component({
selector: 'sub',
template: `
<div (click)="showMessage()">Test</div>
`
})
export class SubComponent {
(...)
}
You can notice that this decorator only copies the metadata at the component level and not the other ones like properties (with #Input)...
See this plunkr: https://plnkr.co/edit/PrAvliIpWTNFAtH33bHA?p=preview.
I'm trying to bind current instance to the class method, please note ES6 syntax.
class SomeClass {
search() => { ... }
}
Which is 100% legit code, however, babelify doesn't want to compile it
SyntaxError: /Users/vladmiller/Projects/test/test/client/test/app/pages/Search.react.js: Unexpected token (50:26) while parsing file: /Users/vladmiller/Projects/test/test/client/test/app/pages/Search.react.js\
Instead, now I have to bind context in class constructor
class SomeClass {
constructor() {
this.search = this.search.bind(this)
}
search() { ... }
}
Which is quite annoying and boring.
UPD: It turns out that this is invalid ES6 syntax; therefore the question is follows. What is the best way to bind instance context to a class method?
UPD2: By default context should be attached, however, the issue with React http://jsbin.com/citafaradu/2/edit?js,console,output
This code is not valid ES2015. Prototype methods are defined like this:
class SomeClass {
search() { /* ... */ }
}