Angular"无法阅读财产'订阅'未定义"

时间:2017-10-24 08:49:30

标签: angular typescript

在开始提问之前,我想告诉您我已经做了大量研究,但我找不到解决方法(解释)为什么会出现此错误。

请注意,我是Angular的新手,我刚开始学习它的工作原理。

所以,我遇到的问题就是我在这个问题的标题中输入的内容。

我尝试做的是根据我在Udemy上购买的课程,使用Firebase构建登录系统。

我使用的代码如下:

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>

7 个答案:

答案 0 :(得分:23)

我使用统一的EventEmitter遇到了相同的错误:

@Output() change: EventEmitter<any>;

代替:

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

尝试订阅更改事件的更高级别组件中发生了错误。

答案 1 :(得分:15)

问题似乎是代码执行的顺序,更具体地说是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';
  • 你永远不应该订阅创建observable的方法。在您的情况下,请勿在{{1​​}}中订阅。总是在最后一分钟订阅。您可以多次订阅同一个observable,但要注意每个订阅都会重新执行observable(如果是http请求,则表示您多次运行请求;不理想......)。
  • 调用变量getRecipes()不是一个好主意,因为它包含recipeSubscription,而不是Observable。订阅是Subscription返回的内容。换句话说:subscribe()
  • 我发现您直接使用了Firebase SDK。您知道AngularFire library吗?

答案 2 :(得分:4)

问题

我偶然发现了相同的错误,原因是我正在ngOnInit()内部初始化@Output事件发射器。

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()之前)。

答案 3 :(得分:3)

问题是,你正在返回一个observable并在Token()的响应中重新分配它。

尝试制作你现在拥有的Observable的主题,我发现这些更容易使用。

<div id="content">
  Confetti World
  <br /> I  confetti!
  <br />
  <div class="buttonContainer">
    <button id="stopButton">Stop Confetti</button>
    <button id="startButton">Drop Confetti</button>
  </div>
</div>
<canvas id="canvas"></canvas>

更改作业
public recipeSubscription: Subject<any> = new Subject();

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

在函数中订阅此函数:

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

现在您可以直接在物业上订阅

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

我希望这足以帮助你:)。

答案 4 :(得分:0)

离子4。

我有一个订阅可观察对象的方法,在ngOnInit中调用该方法,引发了此错误。将方法调用移到构造函数上解决了我的问题。

答案 5 :(得分:0)

正如已经提到的其他答案一样,您应该返回observable以便进行订阅。 就我而言,尽管如此,它仍会引发错误。我刚刚用

重新启动了我的角度应用程序
ng serve 

然后正常工作。

答案 6 :(得分:0)

我正在使用angular8 , 面临类似的挑战。 我应该打一个端点,将一些有效的凭据附加到我的get / post上,从而在检查凭据之前调用方法会导致问题。

在执行任何操作之前,请确保已通过身份验证,以解决问题:

  ngOnInit() {
    this.authService.isAuthenticated().then(status => {
      if (status === true) {
      this.getClients();
      this.getCities();
     } else {
    this.routerCtrl.navigate(["login"]);
   }
 });
}