I'm having the following, very simple angular2 Service:
#Injectable()
export class DrawingService {
private _draw:Draw;
constructor(private mapSvc:MapService) {}
initialize(geometry: GeometryType):void {
this._draw = new Draw(this.mapSvc.getMap());
this._draw.on("draw-end", this.addGraphic);
this._draw.activate(geometry);
}
addGraphic(evt):void {
this._draw.deactivate();
}
}
In initialize, I'm setting the method addGraphic as the callback. Now the problem is, that within the addGraphic method execution, this._draw is undefined.
What's the problem here?
If you pass a method reference like
this._draw.on("draw-end", this.addGraphic);
the reference to this points to the caller function.
If you use instead
this._draw.on("draw-end", this.addGraphic.bind(this));
it should work as expected.
Alternatively you can also use arrow functions but this requires to repeat the parameters (if there need any to be passed).
this._draw.on("draw-end", (param) => this.addGraphic(param));
Move the code to ngOnInit() instead of the constructor. You'll have to implement OnInit.
Related
The code is as follows
class ComposerForm extends BaseForm {
constructor(formsObject, options) {
super({
...options,
setup: {},
});
this.formsObject = { ...formsObject };
}
..
}
Now i have a new form
class PreferencesForm extends ComposerForm {
constructor(company, options = {}) {
super(
{
upids: new UpidsForm(company).initialize(),
featureSettings: new FeatureSettingsForm(company)
},
options
);
}
}
When initialising the FeatureSettingsForm, i need to pass the Preference form along with the company object
Something like
{
featureSettings: new FeatureSettingsForm(company, {prefForm: this})
},
so that i can access the preference form inside featureSettings form.
But this cannot be done since this cannot be accessed inside the super method.
Any idea on how to achieve this?
If I understand you right,
You need to pass a FeatureSettingsForm instance in the object you're passing to super (ComposerForm) in the PreferencesForm constructor, and
You need this in order to create the FeatureSettingsForm instance
So you have a circular situation there, to do X you need Y but to do Y you need X.
If that summary is correct, you'll have to¹ change the ComposerForm constructor so that it allows calling it without the FeatureSettingsForm instance, and add a way to provide the FeatureSettingsForm instance later, (by assigning to a property or calling a method) once the constructor has finished, so you can access this.
¹ "...you'll have to..." Okay, technically there's a way around it where you could get this before calling the ComposerForm constructor, by falling back to ES5-level ways of creating "classes" rather than using class syntax. But it general, it's not best practice (FeatureSettingsForm may expect the instance to be fully ready) and there are downsides to have semi-initialized instances (that's why class syntax disallows this), so if you can do the refactoring above instead, that would be better. (If you want to do the ES5 thing anyway, my answer here shows an example of class compared to the near-equivalent ES5 syntax.)
I'm new to Angular and I tried some code where the property set is being triggered before the ngOnInit().
export class App implements OnInit{
isTriggered = false;
constructor(){
...
}
ngOnInit(){
this.isTriggered = true;
}
}
I'm not sure how this works but isTriggered = false; is getting triggered first before the debugger moves to this.isTriggered = true;
Can someone explain me why this is happening and whats the approach to trigger this.isTriggered = true; from ngOnInit()
It's pretty obvious actually. To invoke ngOnInit you need an instance of App class. When you create an instance all declared fields are initialized first.
The issue is that ngOnInit is an Angular lifecycle method, whereas the isTriggered = false is a class property, native to Javascript, same as if you had placed it inside the constructor.
In the old way of doing things before Javascript Classes even came around, it might've been more obvious.
export function App() {
this.isTriggered = false;
this.ngOnInit = function() { };
}
Seen this way, it's pretty obvious that isTriggered = false will be invoked immediately upon creating a new App() vs. ngOnInit which will only be invoked by something calling ngOnInit after the new object is already created.
Angular lifecycle methods will happen on Angular's framework timing, meaning it's going to happen sometime after that class is initialized. During the initialization of that class, the class property will be set, hence why you see the debugger go to that line first.
When you declare isTriggered = false, that is the same as doing it as initializing it as if it was a part of the constructor. ngOnInit happens afterwards, so that's why you're getting it set to false and then to true.
You can declare isTriggered without assigning it a value simply by removing = false; and then only in ngOnInit assign it to true if that's what you're looking to do.
Trying to access an object's data in a constructor returns an "undefined" object. It works on the ngOnInit() function, but the data (going to get reset) is needed every time the component starts.
import { Component, OnInit, Input } from '#angular/core';
#Input() data: any;
constructor(dataService: DataService)
{
console.log(this.data); // undefined
}
ngOnInit()
{
console.log(this.data) // works here
}
Have you read this documentation?
It said:
After creating a component/directive by calling its constructor,
Angular calls the lifecycle hook methods in the following sequence at
specific moments
And then it lists all methods according to the order of excecution.
You can read the full documentation of lifecycle hook, it is good to know this when we start developing application using Angular.
You can't access the value of a component input inside its constructor, except for the initial value you assign to it.
Why?
Well, easy: Angular must create the component first (by invoking its constructor). Only then can it set the inputs. If you try to use an input property in the constructor, obviously it will be undefined.
In your case, you should use the value in ngOnInit, or, if you really need it in the constructor, retrieve it in another way (by using an injected service, a global shared object ...).
Can I write React lifecycle methods as class properties?
I've been using class properties for a while as I like the fact that I no longer have to manually bind my methods, but I'd like to keep some consistency across my components and I'm wondering if there is any drawback on writing the React lifecycle methods as class properties
import React, { Component } from 'react';
class MyComponent extends Component {
render = () => {
return (
<div>Foo Bar</div>
);
}
}
export default MyComponent;
For example, is the context of this class property affected compared to the context in an equivalent method. Given that the render method in the above code is written as an arrow function, this concern seems relevant.
In a way, the true answer depends on your build pipeline and what the resulting Javascript output looks like. There are two primary possibilities:
Input Code
Let's start by saying you are writing the following before going through any sort of pipeline transformations (babel, typescript, etc):
class Test {
test = () => { console.log('test'); };
}
Output as class member variable.
In one possible world, your pipeline will also be outputting the test function as a member variable for the output class. In this case the output might look something like:
function Test() {
this.test = function() { console.log('test'); };
}
This means that whenever you write new Test() the test function is going to be recreated every single time.
Output as class prototype function
In the other major possibility, your pipeline could be recognizing this as a function property and escape it from the class instance to the prototype. In this case the output might look something like:
function Test() {
}
Test.prototype = {
test: function() { console.log('test'); }
}
This means that no matter how many times you call new Test() there will still be only one creation of the test function around in memory.
Desired behavior
Hopefully it's clear that you want your end result to have the function end up on the prototype object rather than being recreated on each class instance.
However, while you would want the function to not end up as a property, that doesn't necessarily mean you couldn't write it that way in your own code. As long as your build chain is making the correct transformations, you can write it any way you prefer.
Although, looking at the default babel settings (which your babeljs tag leads me to believe you are using) it does not make this transformation for you. You can see this in action here. On the left I've created one class with the function as a property and one class with the function as a class method. On the right hand side, where babel shows it's output, you can see that the class with the function as a property still has it being an instance-level property, meaning it will be recreated each time that class's constructor is called.
I did find this babel plugin, which seems like it might add this transformation in, but I've not used it for myself so I'm not positive.
In my experience, the most reason for writing a method as a class property is when the method will be passed as a callback, and you need it to always be bound to the instance. React lifecycle methods will always be called as a method, so there's no reason to bind them (and you incur a tiny memory penalty when you do). Where this makes a difference is when you're passing a function to a component as a callback (e.g. onClick or onChange).
Take this example:
class BrokenFoo extends React.Component {
handleClick() {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
The function represented by this.handleClick is not automatically bound to the component instance, so when the method tries to read the value of this.props it will throw a TypeError because this is not defined. Read this article if you're not familiar with this; the problem described in section 4.2 "Pitfall: extracting methods improperly" is essentially what's happening when you pass around a method without making sure it's bound correctly.
Here's the class, rewritten with the handler as a class property:
class HappyFoo extends React.Component {
handleClick = () => {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
Effectively, you can think of the handleClick definition in the second example as placing this code into the component's constructor (which is just about exactly the way Babel does it):
this.handleClick = () => {
alert(this.props.message);
}
This achieves the same thing as calling bind on the function (as described in the linked article) but does it a little differently. Because this function is defined in the constructor, the value of this in this.props.message is bound to the containing instance. What this means is that the function is now independent of the calling context; you can pass it around and it won't break.
The rule of thumb that I follow: by default, write methods as methods. This attaches the method to the prototype and will usually behave the way you'd expect. However, if the method is ever written without parentheses (i.e. you're passing the value and not calling it), then you likely want to make it a class property.
So I know I can set getters/setters per property.
export class Person {
private _name: string;
set name(value) {
this._name = value;
}
get name() {
return this._name;
}
}
But is there a way to have a "global instance-wide" setter that trigger whenever any property of a class instance gets modified?
Without manually adding a function call in every getter/setter calling that trigger function? Like, something built into TypeScript?
Thanks in advance.
There isn't a built-in way in TypeScript to do this, nor a JavaScript runtime feature for it.
The closest thing would be a Proxy, which can wrap an underlying object and intercept property assignments. But you'd have to make sure the class instances got wrapped before they were passed off to somewhere else.