本文介绍了ngIf不会评估异步代码Angular和Firebase的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

当我正确登录时,我将isLogIn设置为true,另一方面当我单击注销时,财产虚假。


$ b

  firebase.auth()。onAuthStateChanged((user)在我的AppComponent中有这个逻辑(第一个组件被加载) => {
if(user){
//用户登录。
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 {
//没有用户登录
console.error('no user is login');
this.authService.isLogIn.next(false);
}
});

在我的HeaderComponent中:

  export class HeaderComponent implements OnInit {
isActivated = false;
构造函数(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-2aria-hidden =true>< / i> Ingresar
< / a>
< / ng-template>
< ng-template [ngIf] =isActivated>
< div class =btn-grouprole =group* ngIf =authService.user>
{{authService.user.displayName}}
< / button>
< div class =dropdown-menu dropdown-menu-rightaria-labelledby =btnGroupDrop1>
< a class =dropdown-itemhref =#>下拉式连结< / a>
< a class =dropdown-itemhref =#>下拉式连结< / 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-2aria-hidden =true>< / i>仪表板
< / a>
< / ng-template>

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



现在我让你把整个文件:
AppRouting模块:
$ b $ pre $ 从'@ angular / core'导入{NgModule};
从'@ angular / router'导入{Routes,RouterModule};
从'./shared/home/home.component'导入{HomeComponent};
从'./public/login/login.component'导入{LoginComponent};
从./auth/dashboard/dashboard.component导入{DashboardComponent};
$ b $ const路由:路径= [
{path:'',component:HomeComponent},
{path:'login',component:LoginComponent},
{路径:'仪表板',组件:DashboardComponent}
];
@NgModule({
imports:[RouterModule.forRoot(routes)],
exports:[RouterModule]
})
export class AppRoutingModule {}

AppComponent.ts

 从'@ angular / core'导入{Component,OnInit}; 
从'firebase'导入*作为firebase;
从'./public/services/auth.service'中导入{AuthService};
从./auth/models/user.model导入{User};
$ b @Component({
selector:'app-root',
templateUrl:'./app.component.html',
styleUrls:['./ app.component.css']
})
导出类AppComponent实现OnInit {
isLoading = true;
构造函数(private authService:AuthService){}
$ b $ ngOnInit(){

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

firebase.auth onIdTokenChanged(
(data)=> {
console.log('TOKEN HAS CHANGED',data);
if(数据){
data.getIdToken()。then(
(tk)=> {
this.authService.token = tk;
}
);
} else {
console.log('您还没有令牌,请登录...');
// TODO这意味着用户是新的或有注销。
}
}
);
$ b $ firebase.auth()。onAuthStateChanged((user)=> {
if(user){
//用户登录
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 {
//没有用户登录
console.error('没有用户登录');
this.authService.isLogIn.next(false);
}
});

//检查localstorage,所以我们可以看到是否有记录用户
if(this.authService.getLocalUserInfo()){
this.authService.isLogIn.next真正);
} else {
this.authService.isLogIn.next(false);


$ b code
$ b

AppComponent Template:

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


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

AppHeader.ts

 从'@ angular / core'导入{Component,OnInit}; 
从'../../public/services/auth.service'中导入{AuthService};
$ b @Component({
选择器:'app-header',
templateUrl:'./header.component.html',
styleUrls:['./ header.component.css']
})
export class HeaderComponent implements OnInit {
isActivated = false;
构造函数(private authService:AuthService){}
ngOnInit(){
this.authService.isLogIn.asObservable()。subscribe(
(data:boolean)=> {
this.isActivated = data;
}
);
// this.isActivated = this.authService.isLogin();


$ b $ / code $ / $ p

AppHeader模板$ / $>

 < nav class =navbar navbar-expand-lg navbar-dark bg-custom-nav px-5> 
< a class =navbar-brandrouterLink =/> MovieApp< / a>
< span class =navbar-toggler-icon>< / span>
< / button>
< div class =container-fluid>
< div class =collapse navbar-collapse d-lg-flex justify-content-lg-rows rowid =navbarNavAltMarkup>
< div class =navbar-nav px-sm-4 px-lg-0>

还有一件事,我使用了异步管道,但模板不反映chages。在github项目中,您会发现我在模板中使用异步但仍然是相同的错误。我希望有人可以帮助我。

解决方案

我会尽力帮助/回答,介意你有很多代码筛选,所以我可能错过了一些明显的东西。无论如何,希望你的代码整洁,并指出你正确的方向,你可能想看看AsyncPipe。



我想先修改您的AuthService与内联通过首先更改 isLogIn = new Subject< boolean>(); to private _isLogIn = new Subject< boolean>(); 。我们不想将其他组件分享给其他将会修改它的组件,所以在我们的AuthService中的任何地方,你只需要添加下划线...最后导出我们的主题被使用,我们可以添加一个像这样的get函数:

  get isLogIn():Observable< boolean> {
返回this._isLogIn.asObservable();



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



另外,我们希望将您的用户状态更改从AppComponent您的AuthService。然后,您可以在AppModule的构造函数中实例化服务,以便您的服务在您的应用程序启动后立即开始监视auth状态中的更改。



也会遇到auth状态的解析可能不在区域内的问题,所以虽然这可能不是必须的,但你可以/从core导入 NgZone 并传递它到您的AuthService 构造函数(私有路由器:Router,private _zone:NgZone){} 的构造函数中,所以当您更新auth状态的值时,可以将其包装在此函数中:

  this._zone.run(()=> {
//您的代码在这里
}

现在,如果我要编写 header.component.ts 我会做这样的事情:

  export class HeaderComponent implements OnInit {
constructor(private authService:AuthService){}

ngOnInit(){}

isActivated():Obser vable<布尔> {
返回this.authService.isLogIn;




$ b $ p
$ b $ p所以在这里我只是直接将观察值传给我们带有获取功能的模板。所以在模板中我会像这样实现AsyncPipe:

 < div * ngIf =!(isActivated | async)else用户> 
< a class =nav-item nav-link text-white anchor-hoverrouterLink =/ login
routerLinkActive =active font-weight-bold>
< i class =fa fa-sign-in pr-2aria-hidden =true>< / i> Ingresar
< / a>
< / div>
< ng-template#user>
< div class =btn-grouprole =group* ngIf =authService.user>
{{authService.user.displayName}}
< / button>
< div class =dropdown-menu dropdown-menu-rightaria-labelledby =btnGroupDrop1>
< a class =dropdown-itemhref =#>下拉式连结< / a>
< a class =dropdown-itemhref =#>下拉式连结< / 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-2aria-hidden =true>< / i>仪表板
< / a>
< / ng-template>

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



现在我给出一些概述/基本更改,以确保代码的行为符合预期。我邀请你试试这个,看看你的 * ngIf 是否更巧妙地工作。但是,你也可能想查找其他角度... =>


I have a property in my AuthService wich tell you if you are log in or not.

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

When I login correctly I set the isLogIn to true in the other hand when I click logout I set the property to false. I have this logic in my AppComponent(the first component been loaded).

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);
      }
    });

In my HeaderComponent I have:

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();
  }

}

the template:

<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>

What I'm approching here is that when the user is log in I show him a dashboard button and hide the login button but this is only seen if you refresh the page or chage the current route. I don't want to change the route or refresh the page to see differents UI components depending if you are log in or not.

Now I let you the entire files:AppRouting Module:

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 Template:

<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 Template

<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;
  }
}

You can find the full project at: https://github.com/lucasdavidferrero/movieAppOne more thing, I've used the async pipe but the template doesn't reflect the chages. In the github project you'll find that I'm using async in the template but still the same bug. I hope someone could help me.

解决方案

I'm going to try to help/answer, mind you there is a lot of code to sift through, so I may have missed something obvious. Anyway to hopefully neaten up your code and point you to the right direction you may want to look into the AsyncPipe.

I want to first modify your AuthService to get inline with some better practices, by first changing isLogIn = new Subject<boolean>(); to private _isLogIn = new Subject<boolean>();. We don't want to share the Subject to other components that will modify it, so everywhere in our AuthService you'll just need to add the underscore... Finally to export our Subject to be used we can add a get function like this:

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

And now we don't need to chain the asObservable() to where ever we use it.

Also we want to move where you watch for changes in your user state from the AppComponent to your AuthService. Then you can instantiate the service in the constructor of your AppModule so your service will start to watch for changes in the auth state as soon as your app starts up.

You may also run into an issue where the resolve of the auth state may be falling out of zone, so while this may not be necessary you can/should import NgZone from core and pass it into your constructor of your AuthService constructor(private router: Router, private _zone: NgZone) { } so when you update the value of the auth state you can wrap it in this function:

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

Now if I was to write your header.component.ts I would have done something like this:

export class HeaderComponent implements OnInit {
  constructor(private authService: AuthService) { }

  ngOnInit() {}

  get isActivated(): Observable<boolean> {
    return this.authService.isLogIn;
  }
}

So here I am just passing the observable straight to our template with a get function. So in the template I would implement the AsyncPipe like so:

<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>

The AsyncPipe will automatically subscribe and unsubscribe to your Observable and handle the refresh/zone updates.

Now I gave some overview/basic changes I would do to make sure the code behaves as expected. I invite you to try this and see if your *ngIfs work more "neatly." However you may also want to look up if else in Angular... => https://stackoverflow.com/a/43006589/5076023

这篇关于ngIf不会评估异步代码Angular和Firebase的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 03:02