0

My question is quite similar to these, but i did not solve my problem;

cannot access to this of component in subscribe function

'this' scope in typescript callback function

Cannot Access Component Variables in Subscribe when using chrome inspector

unable to get component variables inside a RxJS subscribe() function

Here is my typescript component class:

export class Test{
     x: string;
}

export class TestComponent implements OnInit{
     test: Test;

     constructor(private testService: TestService){}

     ngOnInit(){
          this.testService.getTest()
               .subscribe((res) => { this.test = res.test;}, ...);
          console.log(this.text.x) //it becomes undefined
     }
}

I am using gulp-typescript, and outputs are like these;

//when targeting es6

let testComponent = class TestComponent ... {
     constructor(testService){
          this.testService = testService;
     }

     ngOnInit(){
          this.testService.getTest()
               .subscribe((res) => {this.test = res.test;}, ...);
          console.log(this.text.x)
     }
}

//when targeting es5

var TestComponent = (function(){
     function TestComponent(testService){
         this.testService = testService;
     }

     TestComponent.prototype.ngOnInit = function(){
          var _this = this;
          this.testService.getTest()
               .subscribe(function (res) { _this.test = res.test }, ...);
          console.log(this.text.x)
     }
}())

When I want to try to reach 'this.test.x' I get the following error from browser with both outputs;

EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'x' of undefined

When I log the this.test, it is undefined. My TestService is injected properly, request come to my api and res.test includes what i need but i cannot use with this.test because it is always undefined. I do not know where I am doing wrong. Is there anyone else who can help me? Finally, I want to ask that, which one should I target when considering browser compatibility etc., es5 or es6?

Community
  • 1
  • 1
ulubeyn
  • 2,761
  • 1
  • 18
  • 29
  • What is `'whateverItIs'`. I don't see that in your code you have posted? Expand the constructor and put a `console.log(testService)` to be sure the service is being passed/injected into the constructor. – Martin Aug 24 '16 at 09:10
  • It is just any property of my Test class, I wrote 'whateverItIs' insted of property name. TestService is injected properly, my api gets the request. – ulubeyn Aug 24 '16 at 09:18
  • Can you please include some of the code from your test class. Thanks. I just want to see what the real property name is so I can see which object is `undefined`. – Martin Aug 24 '16 at 09:24
  • I updated my question @Martin – ulubeyn Aug 24 '16 at 10:48
  • Great. Where in the code are you trying to access `this.test.x`? I am not seeing that. – Martin Aug 24 '16 at 12:28
  • Let's say in ngOnInit, I am calling `console.log(this.test.x)`, and I am getting error @Martin – ulubeyn Aug 24 '16 at 13:06
  • 1
    Ok. That would make sense to me. I would expect an error is you called console.log(this.test.x) on ngOnInit. From the code you provided this.test will not be populated until at least the next "turn" of the VM. Though you would be able to call it within the subscribe callback of you are receiving the expected data. This would be the expected behavior. The console.log() is being called BEFORE your subscribe callback. You need to move to the callback. – Martin Aug 24 '16 at 13:16
  • I was going to turn this comment into an answer, but it looks like Katana314 beat me too it. Please mark that one as the correct answer. This is a gotcha when moving from synchronous to async programming. Now that it has bitten you, once you understand it you will be immune. – Martin Aug 24 '16 at 13:30
  • 1
    One final thing, for browser target ES5. ES6 isn't supported on most mobile browsers yet (as of now) and only up to date desktop browsers will support ES6. Finnaly, the ES6 implementations in these browsers have not been optimized as well as the ES5 implementations. – Martin Aug 24 '16 at 13:40

3 Answers3

3

Move the console.log statement inside of the arrow function.

ngOnInit(){
          this.testService.getTest()
               .subscribe((res) => {
                 this.test = res.test;
                 console.log(this.text.x)
          }, ...);

     }

If you had other things you wanted to do with this.test, then you're going to have to move those inside too (or, add another function and call that from inside the callback). Basically, if you have a service that returns an Observable, that means the callback will not run in the same "frame" that you called the function. In your original code this was the order of operations:

  1. getTest is called, an AJAX operation is started
  2. console.log called, prints undefined.
  3. User gets a second of UI time with not much happening as the request communicates with the server
  4. Request finishes, subscribe(() => callback is called. this.test is set.
Katana314
  • 8,429
  • 2
  • 28
  • 36
  • Here is a link to a deeper explanation of Asynchronous programming. This isn't specific to Angular2 or TypeScript http://code.tutsplus.com/tutorials/event-based-programming-what-async-has-over-sync--net-30027 – Martin Aug 24 '16 at 13:48
  • What if I want to use `this.test.x` inside a `[formGroup]`? When I call it inside subscribe, I get error, or should I use template driven form instead of ReactiveForms? @Katana314 @Martin – ulubeyn Aug 24 '16 at 15:01
  • You may want to simply wrap your form with an `*ngIf="test"`. This way the form and the reference to `test.x` within the form wont be called until your data has returned. Another option is to use the `async` pipe. Or a combination of the two. – Martin Aug 24 '16 at 15:06
2

"_this.componentProperty" works for me instead of "this.componentProperty" when inside a "subscribe()" rxjs call.

0

You can just replace your function TestComponent.prototype.ngOnInit for an Arrow function, so "this" would be accesible from your subscribe call.

Juan Sánchez
  • 1,014
  • 2
  • 15
  • 29