tschoessi tschoessi - 2 months ago 8
TypeScript Question

Typescript: Setting members of object via callback fails strangely

I have a very strange issue for which I did not find the cause yet. I try to show a textbox component in Angular 2, which you can give a message, a label for the button and a callback that is invoked, when the button is clicked.

Here is my component:

@Component({
selector: 'text-box',
templateUrl: './textbox.component.html',
styleUrls: ['./textbox.component.styl']
})
export default class TextBoxComponent implements AfterViewInit {
content: String;
btnCaption: String;
callback: () => void;

constructor(@Inject(TextBoxService) private service: TextBoxService) {
}

ngAfterViewInit(): void {
this.service.init(this.show);
}

public show(message: String, btnCaption: String, callback: () => void) {
this.content = message;
this.btnCaption = btnCaption;
this.callback = callback;
// (1)
// set opacity 1
}

public btnOnClick() {
// (2)
this.callback();
this.dismiss();
}

public dismiss() {
// set opacity 0
}
}


Components are Singletons and cannot be injected, so you cannot simply invoke
show()
on the component from the outside. Therefore I added a service and put a reference to the method into it (see the component's
ngAfterViewInit()
method):

@Injectable()
export default class TextBoxService {
private showCallback: (m: String, b: String, c: () => void) => void;

public init(showCallback: (m: String, b: String, c: () => void) => void) {
this.showCallback = showCallback;
}

public show(message: String, btnCaption: String, callback: () => void) {
this.showCallback(message, btnCaption, callback);
}
}


It is invoked by another service like this:

this.textBoxService.show(
'Wollen Sie einen Kredit aufnehmen?',
'Ja', () => {
ziel.activate();
this.activatedEvents.set(event, ziel);
this.inputService.kreditInput.summe = ziel.getPrice() - ziel.getLiquidFunds();
this.inputService.kreditInput.startdatum = date;
});


However, the text does not update when the button above is called, neither is the listener attached to the button (showing
this.callback() is not a function
). I debugged it (put
console.log()
s on (1) and (2) in the component) and found out, that both methods are correctly called. On (1) the members
content
,
btnCaption
and
callback
are correctly set - but on (2), these members are undefined!

I tried replacing the fat-arrow-syntax with
function()
-syntax but with no success. I also tried hard-coding the string inside
show()
but when accessing it via buttonClick, it's still undefined.

It seems like on (1) and (2) there are two different objects accessed. I have no idea what the reason for this behaviour could be. Any ideas?

Answer

Class prototype methods that are supposed to be passed as callbacks by design (as in this.service.init(this.show)) should be defined as arrow class properties:

public show = (message: String, btnCaption: String, callback: () => void) => { ... };

Or be bound on class construction:

constructor(@Inject(TextBoxService) private service: TextBoxService) {
  this.show = this.show.bind(this);
}

Alternatively, decorators may be used for neater syntax, e.g. @autobind from core-decorators.