【问题标题】:ngIf doesn't evaluate async code Angular and FirebasengIf 不评估异步代码 Angular 和 Firebase
【发布时间】:2017-09-28 02:19:40
【问题描述】:

我的 AuthService 中有一个属性可以告诉你是否登录。

export class AuthService {
  token: string;
  isLogIn = new Subject<boolean>();

当我正确登录时,我将 isLogIn 设置为 true,另一方面,当我单击注销时,我将属性设置为 false。我的 AppComponent 中有这个逻辑(第一个组件已加载)。

firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // User is signed in.
        this.authService.user = new User(user.displayName, user.email, user.photoURL, user.uid);

        console.log(user);
        this.isLoading = false;
        this.authService.isLogIn.next(true);
      } else {
        // No user is signed in.
        console.error('no user is login');
        this.authService.isLogIn.next(false);
      }
    });

在我的 HeaderComponent 我有:

export class HeaderComponent implements OnInit {
  isActivated = false;
  constructor(private authService: AuthService) { }
  ngOnInit() {
    this.authService.isLogIn.asObservable().subscribe(
      (data: boolean) => {
        this.isActivated = data;
      }
    );
    // this.isActivated = this.authService.isLogin();
  }

}

模板:

<ng-template [ngIf]="!isActivated">
      <a class="nav-item nav-link text-white anchor-hover"

         routerLink="/login"
         routerLinkActive="active font-weight-bold">
        <i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Ingresar
      </a>
    </ng-template>
    <ng-template [ngIf]="isActivated">
      <!--DROPDOWN PROFILE-->
      <div class="btn-group" role="group" *ngIf="authService.user">
        <button id="btnGroupDrop1" type="button" class="btn btn-light dropdown-toggle pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
          {{authService.user.displayName}}
        </button>
        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
          <a class="dropdown-item" href="#">Dropdown link</a>
          <a class="dropdown-item" href="#">Dropdown link</a>
        </div>
      </div>
      <a class="nav-item nav-link text-white anchor-hover"
         routerLink="/dashboard"
         routerLinkActive="active font-weight-bold">
        <i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Dashboard
      </a>
    </ng-template>

我在这里要注意的是,当用户登录时,我会向他显示一个仪表板按钮并隐藏登录按钮,但这只有在您刷新页面或更改当前路线时才能看到。我不想更改路由或刷新页面以查看不同的 UI 组件,具体取决于您是否登录。

现在我给你整个文件: 应用路由模块:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from './shared/home/home.component';
import {LoginComponent} from './public/login/login.component';
import {DashboardComponent} from './auth/dashboard/dashboard.component';

const routes: Routes = [
  {path: '', component: HomeComponent},
  {path: 'login', component: LoginComponent},
  {path: 'dashboard', component: DashboardComponent}
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule{}

AppComponent.ts

import {Component, OnInit} from '@angular/core';
import * as firebase from 'firebase';
import {AuthService} from './public/services/auth.service';
import {User} from "./auth/models/user.model";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  isLoading = true;
  constructor(private authService: AuthService) {}

  ngOnInit() {

    // this.authService.isLogIn.next(localStorage.length > 0);

    firebase.auth().onIdTokenChanged(
      (data) => {
        console.log('TOKEN HAS CHANGED', data);
        if(data) {
          data.getIdToken().then(
            (tk) => {
              this.authService.token = tk;
            }
          );
        } else {
          console.log('You don\'t have a token yet, please login...');
          // TODO this means that the user is new or have logout.
        }
      }
    );

    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // User is signed in.
        this.authService.user = new User(user.displayName, user.email, user.photoURL, user.uid);

        console.log(user);
        this.isLoading = false;
        this.authService.isLogIn.next(true);
      } else {
        // No user is signed in.
        console.error('no user is login');
        this.authService.isLogIn.next(false);
      }
    });

    // check localstorage, so we can see if there is a logged user
    if (this.authService.getLocalUserInfo()) {
      this.authService.isLogIn.next(true);
    } else {
      this.authService.isLogIn.next(false);
    }
  }
}

AppComponent 模板:

<div>
  <app-header></app-header>
</div>


<div>
  <router-outlet></router-outlet>
</div>

AppHeader.ts

import { Component, OnInit } from '@angular/core';
import {AuthService} from '../../public/services/auth.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  isActivated = false;
  constructor(private authService: AuthService) { }
  ngOnInit() {
    this.authService.isLogIn.asObservable().subscribe(
      (data: boolean) => {
        this.isActivated = data;
      }
    );
    // this.isActivated = this.authService.isLogin();
  }

}

AppHeader 模板

<nav class="navbar navbar-expand-lg navbar-dark bg-custom-nav px-5">
  <a class="navbar-brand" routerLink="/">MovieApp</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="container-fluid">
    <div class="collapse navbar-collapse d-lg-flex justify-content-lg-between row" id="navbarNavAltMarkup">
      <div class="navbar-nav px-sm-4 px-lg-0">
        <a class="nav-item nav-link text-white"
           routerLink="/"
           routerLinkActive="active font-weight-bold"
           [routerLinkActiveOptions]="{exact: true}">Inicio</a>
      </div>
      <div class="col-lg-8">
        <app-search-bar></app-search-bar>
      </div>
      <div class="row">
        <ng-template [ngIf]="!isActivated">
          <a class="nav-item nav-link text-white anchor-hover"

             routerLink="/login"
             routerLinkActive="active font-weight-bold">
            <i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Ingresar
          </a>
        </ng-template>
        <ng-template [ngIf]="isActivated">
          <!--DROPDOWN PROFILE-->
          <div class="btn-group" role="group" *ngIf="authService.user">
            <button id="btnGroupDrop1" type="button" class="btn btn-light dropdown-toggle pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
              {{authService.user.displayName}}
            </button>
            <div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
              <a class="dropdown-item" href="#">Dropdown link</a>
              <a class="dropdown-item" href="#">Dropdown link</a>
            </div>
          </div>
          <a class="nav-item nav-link text-white anchor-hover"
             routerLink="/dashboard"
             routerLinkActive="active font-weight-bold">
            <i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Dashboard
          </a>
        </ng-template>

      </div>
    </div>
  </div>
</nav>

AuthService.ts

import * as firebase from 'firebase';
import {Router} from '@angular/router';
import { Injectable } from "@angular/core";
import {Subject} from "rxjs/Subject";
import {User} from "../../auth/models/user.model";

@Injectable()
export class AuthService {
  token: string;
  isLogIn = new Subject<boolean>();
  user: User;


  constructor(private router: Router){}

  signinWithFacebook() {
    const provider = new firebase.auth.FacebookAuthProvider();
    provider.addScope('user_location');
    return firebase.auth().signInWithPopup(provider)
      .then(
        (res) => {
          console.log(res);
          this.getTokenId();
          localStorage.setItem('userInfo', JSON.stringify(firebase.auth().currentUser.providerData[0]));
          const userInfo = firebase.auth().currentUser.providerData[0];
          this.user = new User(userInfo.displayName, userInfo.email, userInfo.photoURL, userInfo.uid);

          this.isLogIn.next(true);
          this.router.navigate(['/dashboard']);
        }
      );
  }

  getTokenId() {
    firebase.auth().currentUser.getIdToken()
      .then(
        (tk) => {
          return this.token = tk;
        }
      );
  }

  logout() {
    return firebase.auth().signOut();
    // handle in component
  }

  getLocalUserInfo(): boolean {
    if(localStorage.getItem('userInfo')) {
      const transformStoredUser = JSON.parse(localStorage.getItem('userInfo'));
      this.user = new User(transformStoredUser.displayName, transformStoredUser.email, transformStoredUser.photoURL, transformStoredUser.uid);
      return true;
    } else {
      return false;
    }
  }
  isLogin():boolean {
    if (localStorage.getItem('userInfo')) {
      return true;
    }
    return false;
  }
}

您可以在以下位置找到完整的项目:https://github.com/lucasdavidferrero/movieApp 还有一件事,我使用了异步管道,但模板没有反映 chages。在 github 项目中,您会发现我在模板中使用了 async 但仍然是相同的错误。我希望有人可以帮助我。

【问题讨论】:

    标签: javascript angular firebase firebase-authentication


    【解决方案1】:

    我将尝试帮助/回答,请注意,有很多代码需要筛选,所以我可能遗漏了一些明显的东西。无论如何,希望能整理你的代码并指出你可能想要研究 AsyncPipe 的正确方向。

    我想首先修改您的 AuthService 以符合一些更好的做法,首先将 isLogIn = new Subject&lt;boolean&gt;(); 更改为 private _isLogIn = new Subject&lt;boolean&gt;();。我们不想将 Subject 共享给将修改它的其他组件,因此在我们的 AuthService 中的任何地方,您只需要添加下划线...最后导出我们的 Subject 以供使用,我们可以添加一个像这样的 get 函数:

    get isLogIn(): Observable<boolean> {
       return this._isLogIn.asObservable();
    }
    

    现在我们不需要将asObservable() 链接到我们使用它的任何地方。

    我们还希望将您监视用户状态变化的位置从 AppComponent 转移到您的 AuthService。然后,您可以在 AppModule 的构造函数中实例化该服务,这样您的服务就会在您的应用启动后立即开始监视 auth 状态的变化。

    您还可能遇到身份验证状态的解析可能超出区域的问题,因此虽然这可能不是必需的,但您可以/应该从核心导入 NgZone 并将其传递到您的 AuthService 构造函数constructor(private router: Router, private _zone: NgZone) { } 因此,当您更新身份验证状态的值时,您可以将其包装在此函数中:

    this._zone.run(() => { 
        // your code here
    }
    

    如果我要写你的header.component.ts,我会这样做:

    export class HeaderComponent implements OnInit {
      constructor(private authService: AuthService) { }
    
      ngOnInit() {}
    
      get isActivated(): Observable<boolean> {
        return this.authService.isLogIn;
      }
    }
    

    所以在这里我只是通过 get 函数将 observable 直接传递给我们的模板。所以在模板中我会像这样实现 AsyncPipe:

    <div *ngIf="!(isActivated | async) else user">
       <a class="nav-item nav-link text-white anchor-hover" routerLink="/login"
         routerLinkActive="active font-weight-bold">
        <i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Ingresar
      </a>
    </div>
    <ng-template #user>
      <!--DROPDOWN PROFILE-->
      <div class="btn-group" role="group" *ngIf="authService.user">
        <button id="btnGroupDrop1" type="button" class="btn btn-light dropdown-toggle pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
          {{authService.user.displayName}}
        </button>
        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
          <a class="dropdown-item" href="#">Dropdown link</a>
          <a class="dropdown-item" href="#">Dropdown link</a>
        </div>
      </div>
      <a class="nav-item nav-link text-white anchor-hover"
         routerLink="/dashboard"
         routerLinkActive="active font-weight-bold">
        <i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Dashboard
      </a>
    </ng-template>
    

    AsyncPipe 将自动订阅和取消订阅您的 Observable 并处理刷新/区域更新。

    现在我给出了一些概述/基本更改,以确保代码按预期运行。我邀请你试试这个,看看你的*ngIfs 是否更“整齐”地工作。但是,您可能还想在 Angular 中查找 else... => https://stackoverflow.com/a/43006589/5076023

    【讨论】:

    • 您也可以在您的AuthService 中处理firebase.auth().onAuthStateChanged,然后在您的AppModule 的构造函数中实例化该服务。我错过了你没有在 AuthService 中这样做
    • 我已按照您的步骤进行操作,但现在如何将值设置为 isLogIn?有套装吗?
    • 我不知道你有没有看到我的评论,但我错过了你没有在 authservice 中查看用户状态,所以我建议你把它移到那里。 (见上面的评论)
    • 我已将 firebase 逻辑更改为 AuthService 并从 AppModule 调用它,但仍在标题中我看不到与 ngIf 关联的元素。如果你愿意,我不能在 github 上创建一个 repo 并与你分享。
    • 我想你的意思是说你可以和我分享 repo,所以拜托,我会尽力帮助。
    猜你喜欢
    • 2018-03-09
    • 2016-10-15
    • 1970-01-01
    • 2017-01-04
    • 1970-01-01
    • 2015-09-22
    • 2018-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多