Angular "Невозможно прочитать свойство" подписаться "undefined"

Прежде чем начать свой вопрос, я хотел бы сообщить вам, что я уже провел большое исследование, и я не могу найти решение (объяснение), почему я получаю эту ошибку.

Также обратите внимание, что я совершенно новый на Angular, и я только начал изучать, как это работает.

Итак, проблема у меня есть то, что я ввел в заголовок этого вопроса.

Я пытаюсь создать систему входа в систему с использованием Firebase, основываясь на курсе, который я покупаю на Udemy.

Код, который я использую, следующий:

auth.service.ts

import {Injectable} from '@angular/core';
import * as firebase from 'firebase';

@Injectable ()
export class AuthService {
    token: string;

    // ...

    singInUser ( email: string, password: string ) {
        // login process here ...
    }

    // Responsible to retrieve the authenticated user token
    getToken () {   
        return firebase
            .auth ()
            .currentUser
            .getIdToken ();
    }
}

Данные-storage.service.ts

// ... Dependencies here
@Injectable ()
export class DataStorageService {
    private recipeEndPoint: string = 'https://my-unique-id.firebaseio.com/recipes.json';
    private recipeSubscription: Observable<any> = new Observable();

    constructor ( private http: Http,
                  private recipes: RecipeService,
                  private authService: AuthService ) {}

    // other functionality ...

    getRecipes () {
        const token = this.authService.getToken ();

        token.then (
            ( token: string ) => {
                this.recipeSubscription = this.http.get ( this.recipeEndPoint + '?auth=' + token ).map (
                    ( data: Response ) => {
                        return data.json ();
                    }
                );

                // THIS PARTICULAR CODE WORKS AS EXPECTED
                // WITH NO ISSUES
                this.recipeSubscription.subscribe (
                    ( data: Response ) => {
                        console.log ( 'Data response: ', data );
                    },
                    ( error ) => {
                        console.log ( 'Error: ' + error );
                    }
                )
            }
        );

        // This is supposed to return an Observable to the caller
        return this.recipeSubscription;
    }
}

header.component.ts

// Dependencies here ...

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  constructor(private dataStorage: DataStorageService, private recipeService: RecipeService) { }

  // Other Code Here ...

  onFetchData() {
    let recipeSubscription = this.dataStorage.getRecipes();

    // THIS RETURNS TRUE
    console.log(recipeSubscription instanceof Observable);

    // THIS LINE THEN RETURNS THE MESSAGE:
    // ERROR TypeError: Cannot read property 'subscribe' of undefined
    recipeSubscription.subscribe();

    // IF I COMMENT OUT THE PREVIOUS LINE
    setTimeout(
      () => {
        // THIS RETURNS TRUE
        console.log(recipeSubscription instanceof Observable);
      },
      500
    );

    setTimeout(
      () => {
        // AS WELL THIS ONE RETURNS TRUE
        console.log(recipeSubscription instanceof Observable);
      },
      1000
    );

    setTimeout(
      () => {
        // AS WELL THIS ONE RETURNS TRUE
        console.log(recipeSubscription instanceof Observable);
      },
      1500
    );
  }
}

Итак, к сожалению, я не вижу, что может быть неправильным с этим кодом. Может ли кто-нибудь заметить что-то, что я сделал неправильно?

Примечание. Я удалил части своего кода только для того, чтобы сделать фрагменты более удобочитаемыми. Если вам нужна другая часть, пожалуйста, не стесняйтесь спрашивать меня, и я предоставлю ее здесь.

ОБНОВЛЕНИЕ # 1

Вот как выглядит header.component.html

<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="navbar-header">Logo Here</div>

        <div class="navbar-default">
            <ul class="nav navbar-nav">
                <!-- Left Navigation Options -->
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <!-- Right Navigation Options -->
                <li class="dropdown" appDropdown>
                    <a routerLink="/" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li>
                            <a style="cursor: pointer;" (click)="onSaveData()">Save Data</a>
                        </li>
                        <li>
                            <!-- Here is where I call the onFetchData method -->
                            <a style="cursor: pointer;" (click)="onFetchData()">Fetch Data</a>
                        </li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>

Ответ 1

Кажется, что проблема заключается в порядке выполнения вашего кода, а точнее в методе getRecipes():

// Numbers indicate the execution order

getRecipes () {
    const token = this.authService.getToken ();

    // 1. You call a promise, which will take a while to execute...
    token.then (
        ( token: string ) => {
            // 3. Finally, this bit gets executed, but only when the promise resolves.
            this.recipeSubscription = ...
        }
    );

    // 2. Then, you return a variable that hasn't been assigned yet,
    // due to the async nature of the promise.
    return this.recipeSubscription;
}

Решение этого заключается в том, что ваш метод getRecipes () НЕ ДОЛЖЕН ПОДПИСАТЬСЯ. Он должен вернуть либо Promise, либо Observable.

Что-то вроде этого:

getRecipes() {
    // Convert the initial promise into an observable
    // so can you use operators like map(), mergeMap()... to transform it.
    const tokenObs = Observable.fromPromise(this.authService.getToken());

    // Merge the token observable into an HTTP observable
    // and return the JSON data from the response.
    return tokenObs
      .mergeMap(token => this.http.get('XXX?auth=' + token))
      .map(resp => resp.json());
}

Затем вызывающий код в HeaderComponent становится:

const recipeObs = this.dataStorage.getRecipes();
recipesObs.subcribe(jsonData => {
  // Use the JSON data from the HTTP response
});

Несколько замечаний:

  • Вам нужно явно импортировать операторы RxJS, используемые в вашем коде. Если вы следуете моему примеру, вам нужно добавить следующие импорты в начале:
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
  • НИКОГДА не следует подписываться в методе, который создает наблюдаемый. В вашем случае не подписывайтесь на getRecipes(). ВСЕГДА подписаться в последнюю минуту. Вы можете подписаться несколько раз на одно и то же наблюдаемое, но имейте в виду, что каждая подписка повторно выполняет наблюдаемое (в случае HTTP-запроса это означает, что вы выполняете запрос несколько раз, а не идеал...).
  • Не рекомендуется вызывать переменную recipeSubscription, поскольку она содержит Observable, а не Subscription. Под подпиской возвращается subscribe(). Другими словами: const subscription = observable.subscribe().
  • Я вижу, что вы используете Firebase SDK напрямую. Знаете ли вы, что библиотека AngularFire?

Ответ 2

Я получил ту же ошибку при использовании унитализированного EventEmitter:

@Output() change: EventEmitter<any>;

вместо:

@Output() change: EventEmitter<any> = new EventEmitter<any>();

Произошла ошибка в компоненте более высокого уровня, который попытался подписаться на событие изменения.

Ответ 3

проблема

Я наткнулся на ту же ошибку, и причина была в том, что я инициализировал свой @Output событий @Output внутри ngOnInit().

export class MyClass implements OnInit {

    @Output()
    onChange : EventEmitter<void>;

    ngOnInit() {
        // DO NOT initialize @Output event here
        this.onChange = new EventEmitter<void>();    
    }
}

Решение

Когда я изменил инициализацию на то же место объявления, это работало.

export class MyClass implements OnInit {

    @Output()
    onChange : EventEmitter<void> = new EventEmitter<void>();

    ngOnInit() {
    }
}

Я думаю, что это происходит потому, что родительский компонент пытается подписаться на событие слишком рано (до того, как ngOnInit() будет запущен).

Ответ 4

Проблема в том, что вы возвращаете наблюдаемое и повторно назначаете его в ответ Token().

Попробуйте сделать объект наблюдения, который у вас есть сейчас, я считаю, что это проще в использовании.

public recipeSubscription: Subject<any> = new Subject();

Измените назначение из

this.recipeSubscription = this.http.get....

Для

let response = this.http.get....

Подпишитесь на то, что в функции, которая вызвана:

response.subscribe((res) => {this.recipeSubscription.next(res)})

Теперь вы можете подписаться непосредственно на свойство

this.dataStorage.recipeSubscription.subscribe((res) => {
    // Do stuff.
});

this.dataStorage.getRecipes();

Надеюсь, этого достаточно, чтобы помочь вам:)

Ответ 5

Ионная 4.

У меня был метод, который подписывается на наблюдаемую, вызывая этот метод в ngOnInit, вызвал эту ошибку. Перемещение вызова метода в конструктор решило мою проблему.

Ответ 6

Как уже упоминалось в других ответах, вы должны вернуть наблюдаемое, чтобы подписаться на него. В моем случае это была ошибка, несмотря на это. Я только что перезапустил свое угловое приложение с помощью

ng serve 

а потом все заработало