Angular2 component's "this" is undefined when executing callback function - javascript

I have a component which calls a service to fetch data from a RESTful endpoint. This service needs to be given a callback function to execute after fetching said data.
The issue is when I try use the callback function to append the data to the existing data in a component's variable, I get a EXCEPTION: TypeError: Cannot read property 'messages' of undefined. Why is this undefined?
TypeScript version: Version 1.8.10
Controller code:
import {Component} from '#angular/core'
import {ApiService} from '...'
#Component({
...
})
export class MainComponent {
private messages: Array<any>;
constructor(private apiService: ApiService){}
getMessages(){
this.apiService.getMessages(gotMessages);
}
gotMessages(messagesFromApi){
messagesFromApi.forEach((m) => {
this.messages.push(m) // EXCEPTION: TypeError: Cannot read property 'messages' of undefined
})
}
}

Use the Function.prototype.bind function:
getMessages() {
this.apiService.getMessages(this.gotMessages.bind(this));
}
What happens here is that you pass the gotMessages as a callback, when that is being executed the scope is different and so the this is not what you expected.
The bind function returns a new function that is bound to the this you defined.
You can, of course, use an arrow function there as well:
getMessages() {
this.apiService.getMessages(messages => this.gotMessages(messages));
}
I prefer the bind syntax, but it's up to you.
A third option so to bind the method to begin with:
export class MainComponent {
getMessages = () => {
...
}
}
Or
export class MainComponent {
...
constructor(private apiService: ApiService) {
this.getMessages = this.getMessages.bind(this);
}
getMessages(){
this.apiService.getMessages(gotMessages);
}
}

Or you can do it like this
gotMessages(messagesFromApi){
let that = this // somebody uses self
messagesFromApi.forEach((m) => {
that.messages.push(m) // or self.messages.push(m) - if you used self
})
}

Because you're just passing the function reference in getMessages you don't have the right this context.
You can easily fix that by using a lambda which automatically binds the right this context for the use inside that anonymous function:
getMessages(){
this.apiService.getMessages((data) => this.gotMessages(data));
}

I have same issue, resolved by using () => { } instead function()

Please define function
gotMessages = (messagesFromApi) => {
messagesFromApi.forEach((m) => {
this.messages.push(m)
})
}

Related

Angular 2+ wait for subscribe to finish to update/access variable

I am having an issue with my variables being undefined. I am certain this is because the observable hasn't finished. Here is the part of my code in my .ts file that is causing the issue. (I'm placing the minimum code required to understand the issue. Also myFunction gets called from a click event in the HTML).
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
});
console.log(myVariable) --> undefined
}
}
So this piece of code calls a function in my service that returns some data from an API. The issue is that when I try to access the variable myVariable right outside of the subscribe function it returns undefined. I'm sure this is because the subscribe hasn't finished before I try to access myVariable
Is there a way to wait for the subscribe to finish before I try to access myVariable?
why not create a separate function and call it inside the subscription.
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
this.update()
});
this.update()
}
update(){
console.log(this.myVariable);
}
}
As you know subscriptions are executed when server return data but the out side of subscription code executed synchronously. That is why console.log outside of it executed. The above answer can do your job but you can also use .map and return observable as shown below.
let say you are calling it from s service
export class myClass {
myVariable: any;
// calling and subscribing the method.
callingFunction() {
// the console log will be executed when there are data back from server
this.myClass.MyFunction().subscribe(data => {
console.log(data);
});
}
}
export class myClass {
myVariable: any;
// this will return an observable as we did not subscribe rather used .map method
myFunction() {
// use .pipe in case of rxjs 6
return this.myService.getApi().map(data => {
this.myVariable = data;
this.update()
});
}
}

testing a service call in jasmine

I am trying to write a unit-test for a function that calls a service. But am running into the error: TypeError: undefined is not a constructor
What I am trying to test is a service call that, on success, sets the value of the variable 'cards'.
I've created the appropriate mock for the service (CardService), which you can see in the spec file below
test.component.spec.ts
class MockCardService extends CardService {
constructor() {
super(null); // null is the http in service's constructor
}
getCardDetails(): any {
return Observable.of([{ 0: 'card1' }, { 1: 'card2' }]);
}
}
describe('MyComponent', () => {
let component: MyComponent;
let mockCardService: MockCardService;
beforeEach(() => {
mockCardService = new MockCardService();
component = new MyComponent(
mockCardService // add the mock service that runs before each test
);
});
// The failing test :(
it('should set the card variable to the value returned by the service', () => {
spyOn(mockCardService, 'getCardDetails').and.callThrough();
// Act
component.ngOnInit();
component.updateCards(); // call the function I am testing
// Assert
expect(component.cards).toConTainText('Card1');
});
And the component file with the function I'm testing:
export class MyComponent implements OnInit {
public cards: CardModel[] = [];
constructor(
private cardService: CardService,
) {
}
ngOnInit() {
this.updateCards(); // call the update card service
}
updateCards(): void {
this.cardService.getCardDetails().subscribe(
(cardsDetails) => {
this.cards = cardsDetails;
},
(err) => {
// todo: error handling
console.log(err);
}
);
}
}
Whenever this test runs I recieve the error:
TypeError: undefined is not a constructor (evaluating 'Observable_1.Observable.of([{ 0: 'card1' }, { 1: 'card2' }])') (line 22)
getCardDetails
updateCards
ngOnInit
And I can't figure out what I'm doing wrong, why 'getCardDetals.subscribe' is undefined. The MockCardService class I provided doesn't appear to be working for some reason.
(note that this.cardService.getCardDetails() is defined, if I log it out in the component itself )
Any help would be very much appreciated.
Author here:
I'm still not sure what is going wrong. But I was able to fix this by changing class MockCardService extends CardService { to just let MockCardService, and using that variable throughout. Good luck to anyone who runs into this!
MockCardService.getCardDetails() should return an Observable, so you can run subscribe in the component.

Is there any way to call typescript methods inside Jquery event handler inside ngAfterViewInit method

I am using Angular 4 . Want to call methods of typescript file from ngAfterViewInit method like
declare var $;
#Component({
selector: 'app-details',
templateUrl: './details.component.html',
styleUrls: ['./details.component.css']
})
export class DetailsComponent implements OnInit ,AfterViewInit{
ngAfterViewInit(): void {
$(document).on("hide.bs.modal", function () {
//this.setValueInsideDetailForm(); This is methods inside typescript file , which one i want to call
});
}
setValueInsideDetailForm(){
// Some code here.
}
}
But it throws error like setValueInsideDetailForm is undefined.
Try:
ngAfterViewInit(): void {
$(document).on("hide.bs.modal", () => {
this.setValueInsideDetailForm();
});
}
You need to use an arrow function to be able to access a method outside of the current scope.
myFirstMethod() {
console.log('my first method');
}
myMethod() {
this.myFirstMethod() // Works
function test() {
this.myFirstMethod() // Does not work because it is restricted to what
is inside of the test() method
}
const test = () => {
this.myFirstMethod() // Works since is not restricted to test method
scope
}
}
Your function is taking this context from your component. Before the $ type const self = this; and use self instead.
Try logging this inside your callback function and see what it logs.

Can I use Angular2 service in DOM addEventListener?

Issue: When I try to call service within addEventListener, service seems to be empty.
Html:
<div id="_file0">
Service:
#Injectable()
export class FilesService {
constructor(private http : Http) { }
}
Component:
export class NoteComponent implements OnInit {
constructor(private filesService : FilesService) { }
ngOnInit() {
this.addEvent(document.getElementById("_file0"));
}
addEvent(div){
console.log("tag1", this.filesService);
div.addEventListener("click", function(){
console.log("tag2", this.filesService);
});
}
}
Console output:
tag1 FilesService {http: Http}
tag2 undefined
Try using this code to maintain this context
div.addEventListener("click", () => {
console.log("tag2", this.filesService);
});
Additional info: this is called Arrow function or lamda function. What it actually does is when Typescript compiles your code into javascript, it creates another _this variable to store the real this and whenever you call this it gets replaces by _this.
That's why your original code gets compile, this is undefined because it was the context of click event not your Angular component.

Angular2 'this' is undefined

I have a code that looks like this:
export class CRListComponent extends ListComponent<CR> implements OnInit {
constructor(
private router: Router,
private crService: CRService) {
super();
}
ngOnInit():any {
this.getCount(new Object(), this.crService.getCount);
}
The ListComponent code is this
#Component({})
export abstract class ListComponent<T extends Listable> {
protected getCount(event: any, countFunction: Function){
let filters = this.parseFilters(event.filters);
countFunction(filters)
.subscribe(
count => {
this.totalItems = count;
},
error => console.log(error)
);
}
And the appropriate service code fragment from CRService is this:
getCount(filters) {
var queryParams = JSON.stringify(
{
c : 'true',
q : filters
}
);
return this.createQuery(queryParams)
.map(res => res.json())
.catch(this.handleError);
}
Now when my ngOnInit() runs, I get an error:
angular2.dev.js:23925 EXCEPTION: TypeError: Cannot read property
'createQuery' of undefined in [null]
ORIGINAL EXCEPTION:
TypeError: Cannot read property 'createQuery' of undefined
So basically, the this in the return this.createQuery(queryParams) statement will be null. Does anybody have an idea how is this possible?
The problem is located here:
gOnInit():any {
this.getCount(new Object(), this.crService.getCount); // <----
}
Since you reference a function outside an object. You could use the bind method on it:
this.getCount(new Object(), this.crService.getCount.bind(this.crService));
or wrap it into an arrow function:
this.getCount(new Object(), (filters) => {
return this.crService.getCount(filters));
});
The second approach would be the preferred one since it allows to keep types. See this page for more details:
https://basarat.gitbooks.io/typescript/content/docs/tips/bind.html
To fix this error I yanked all the innards out of my function causing the error and threw it in another function then the error went away.
example:
I had this function with some code in it
this.globalListenFunc = renderer.listenGlobal('document', 'click', (event) => {
// bunch of code to evaluate click event
// this is the code that had the "this" undefined error
});
I pulled the code out and put it in an external public function, here's the finished code:
this.globalListenFunc = renderer.listenGlobal('document', 'click', (event) => {
this.evaluateClick(event);
});
evaluateClick(evt: MouseEvent){
// all the code I yanked out from above
}

Categories

Resources