15

I am able to use this.variable to access variables in any part of the component, except inside RxJS functions like subscribe() or catch().

In the example below, I want to print a message after running a process:

import {Component, View} from 'angular2/core';

@Component({
    selector: 'navigator'
})
@View({
    template: './app.component.html',
    styles: ['./app.component.css']
})
export class AppComponent {
    message: string;

    constructor() {
        this.message = 'success';
    }

    doSomething() {
        runTheProcess()
        .subscribe(function(location) {
            console.log(this.message);
        });
    }
}

When I run doSomething(), I get undefined. This scenario can be solved using a local variable:

import {Component, View} from 'angular2/core';

@Component({
    selector: 'navigator'
})
@View({
    template: './app.component.html',
    styles: ['./app.component.css']
})
export class AppComponent {
    message: string;

    constructor() {
        this.message = 'success';
    }

    doSomething() {

        // assign it to a local variable
        let message = this.message;

        runTheProcess()
        .subscribe(function(location) {
            console.log(message);
        });
    }
}

I suppose this is related to the this, however, why I can't access the this.message inside the subscribe()?

Stphane
  • 3,368
  • 5
  • 32
  • 47
Haoliang Yu
  • 2,987
  • 7
  • 22
  • 28

2 Answers2

40

This has nothing to do with rx or angular, and everything to do with Javascript and Typescript.

I assume you're familiar with the semantics of this in the context of function invocations in Javascript (if not, there are no shortage of explanations online) - those semantics apply in the first snippet, of course, and that's the only reason this.message is undefined inside subscribe()there. That's just Javascript.

Since we're talking about Typescript: Arrow functions are a Typescript construct intended (in part) to sidestep the awkwardness of these semantics by lexically capturing the meaning of this, meaning that this inside an arrow function === this from the outer context.

So, if you replace:

.subscribe(function(location) {
        //this != this from outer context 
        console.log(this.message); //prints 'undefined'
    });

by:

.subscribe((location) => {
     //this == this from the outer context 
        console.log(this.message); //prints 'success'
    });

You'll get your expected result.

drew moore
  • 31,565
  • 17
  • 75
  • 112
  • I know this is an old question - but when I follow your answer and run my application in google chrome I get a page crash - memory error.. any idea? –  Oct 24 '16 at 08:00
  • Is there any quick solution to reference the outer 'this' in the fat arrow function? – etlds Dec 01 '17 at 16:11
  • @etlds, if you are talking about referencing "this" while debugging, then you can watch for _this Otherwise, internally, the "this" refers to the outside context (when used in arrow function) – Mahesh Feb 22 '18 at 20:07
  • 1
    Thanks, This is very useful! – Vipul Mar 06 '19 at 09:26
2

As an alternative to @drewmoore's answer, if you wish to have external functions you can do:

 .subscribe((location) => dataHandler(location), (error) => errorHandler(error));

 ....

 const dataHandler = (location) => {
     ...
 }

By externalising the errorHandler function, it can be used in multiple places (ie. subscriptions). By having as (fat) arrow functions your code will capture the 'this' context (as discussed in @Drewmoore's answer).

What is lacking is the ability to write the following and have handled like an arrow function. The following works and passes the argument implicitly. Unfortunately AFAIK you cannot capture the this context (perhaps use bind to achieve this, though that makes the code more verbose overall).

 .subscribe(dataHandler, errorHandler);

This is soooo succinct! But alas won't work if the context is required.

HankCa
  • 9,129
  • 8
  • 62
  • 83