繁体   English   中英

使 angular 服务返回 class 字段作为 observable

[英]Make angular service return class field as a observable

这是我想要实现的目标:有一个组件中有一个 UserService 。 登录后,我想调用该服务以从后端检索用户数据并将其存储在服务字段中。 然后在我重新加载主页后,我希望该服务不再调用后端,只返回以前存储的数据。 此外,我希望在加载数据之前不加载一些 div。 我怎样才能做到这一点? 我应该让getAllUSerInfoNormal()方法返回 observable,如果可能的话,我应该怎么做? 还是有其他方法?

这是我现在得到的:

服务 class:

@Injectable()
export class AccountService {

    userAccount : UserAccount;
    constructor(private _httpClient: HttpClient) {
    }


     getAllUSerInfoNormal(): UserAccount {
    if (!this.userAccount) {
        this._httpClient.get<UserAccount>(Constants.apiRoot + 'account/accountInfo').subscribe(user => {
            this.userAccount = user;
            return this.userAccount
        })
    } else {
        return this.userAccount;
    }
}



组件 class:

@Component({
  selector: 'app-main-view',
  templateUrl: './main-view.component.html',
  styleUrls: ['./main-view.component.scss']
})
export class MainViewComponent implements OnInit {

  private account: UserAccount;
  private isDataLoaded = false;

  constructor(private _router: Router,
              private _accountService: AccountService) {
    setInterval(() => {
      this.updateResources();
    }, 1);
  }

  ngOnInit() {
    this._accountService.getAllUSerInfoNormal();
  }

  updateResources(){

  }
}



组件模板:

<div class="ingame-menu">
    <div *ngIf="isDataLoaded">
        <div class="resources">
            <div class="resource"><img src="../../../assets/images/resources/gold.png" title="Gold"/>
                <br/> {{account.gold}}</div>
            <div class="resource"><img src="../../../assets/images/resources/wood.png" title="Wood"/>
                <br/> {{account.wood}}</div>
            <div class="resource"><img src="../../../assets/images/resources/stone.png" title="Stone"/>
                <br/> {{account.stone}}</div>
            <div class="resource"><img src="../../../assets/images/resources/meat.png" title="Meat"/>
                <br/> {{account.people}} </div>
        </div>
    </div>
    <div class="ingame-nav">
        <ul>
            <li><a routerLink="overview">Overview</a></li>
            <li><a routerLink="population">Population</a></li>
            <li><a routerLink="resources">Resources</a></li>
            <li><a routerLink="research">Research</a></li>
            <li><a routerLink="buildings">Buildings</a></li>
            <li><a routerLink="barracks">Barracks</a></li>
            <li><a routerLink="tavern">Tavern</a></li>
            <li><a routerLink="defence">Defence</a></li>
        </ul>
    </div>
    <div id="content">
        <router-outlet></router-outlet>
    </div>
</div>

您可以在第一次加载后将加载的用户保存在 lcoalStorage 中,然后在以下访问中读取它。

这是一个简单的学习解决方案。 但在实际应用场景中,您不应该有这样的“无限时间”缓存。 并且有时必须有办法更新缓存。

服务 class更改为:

@Injectable()
export class AccountService {

    get userAccount() {
      return JSON.parse(localStorage.getItem('user'));
    };

    constructor(private _httpClient: HttpClient) {
    }


    getAllUSerInfoNormal(forceBanckendCall: boolean): Observable<UserAccount> {
        if (this.userAccount && !forceBackendCall) {
           return of(this.userAccount);
        }

        return this._httpClient.get<UserAccount>(Constants.apiRoot + 'account/accountInfo')
           .pipe(
             tap(user => localStorage.setItem('user', JSON.stringify(user)))
           );

    }

然后组件 class到:

@Component({
  selector: 'app-main-view',
  templateUrl: './main-view.component.html',
  styleUrls: ['./main-view.component.scss']
})
export class MainViewComponent implements OnInit {

  private account: UserAccount;
  private isDataLoaded = false;

  constructor(private _router: Router,
              private _accountService: AccountService) {
    setInterval(() => {
      this.updateResources();
    }, 1);
  }

  ngOnInit() {
    this._accountService.getAllUSerInfoNormal().subscribe(account => this.userAccount = account);
  }

  updateResources(){

  }
}

然后在您的组件模板中放置一个*ngIf="userAccount"在您想要在用户出现后立即加载的元素中。

  1. 您无法像尝试那样同步地从订阅中返回值。 来自 observable 的变量是异步发出的。 有关异步数据的更多信息,请参阅此处 简而言之,该值仅在订阅内可用。

  2. 有多种方法可以缓存来自 observable 的通知。 最快的方法之一是在服务中使用多播 observable(如Subject )来保存数据。 我将说明使用带有缓冲区 1 的ReplaySubject ,因为它可以保持当前值。

服务

import { ReplaySubject } from 'rxjs';

@Injectable()
export class AccountService {
  userAccountSource: ReplaySubject<UserAccount> = new ReplaySubject<UserAccount>(1);
  userAccount$ = this.userAccountSource.asObservable();
  
  constructor(private _httpClient: HttpClient) {
    this.getAllUSerInfoNormal().subscribe();  // <-- push to cache on app start
  }

  getAllUSerInfoNormal(): Observable<UserAccount> {
    return this._httpClient.get<UserAccount>(Constants.apiRoot + 'account/accountInfo').pipe(
      tap({
        next: user => this.userAccountSource.next(user),
        error: error => { }  // <-- handle error
      })
    );
  }
}
  1. 现在,如果组件 controller 中不需要发射,您可以直接跳过其中的订阅,并在模板中使用 Angular async pipe。 除了保持代码整洁之外,它还将处理开放订阅的潜在泄漏问题。

Controller

@Component({...})
export class MainViewComponent implements OnInit {
  private isDataLoaded = false;

  constructor(
    private _router: Router,
    private _accountService: AccountService
  ) {
    setInterval(() => {
      this.updateResources();
    }, 1);
  }

  ngOnInit() {
  }

  updateResources() {
  }
}

模板

<div class="ingame-menu">
  <div *ngIf="(_accountService.userAccount$ | async) as account">
    <div class="resources">
      <div class="resource"><img src="../../../assets/images/resources/gold.png" title="Gold" />
        <br /> {{account.gold}}</div>
      <div class="resource"><img src="../../../assets/images/resources/wood.png" title="Wood" />
        <br /> {{account.wood}}</div>
      <div class="resource"><img src="../../../assets/images/resources/stone.png" title="Stone" />
        <br /> {{account.stone}}</div>
      <div class="resource"><img src="../../../assets/images/resources/meat.png" title="Meat" />
        <br /> {{account.people}} </div>
    </div>
  </div>
  <div class="ingame-nav">
    <ul>
      <li><a routerLink="overview">Overview</a></li>
      <li><a routerLink="population">Population</a></li>
      <li><a routerLink="resources">Resources</a></li>
      <li><a routerLink="research">Research</a></li>
      <li><a routerLink="buildings">Buildings</a></li>
      <li><a routerLink="barracks">Barracks</a></li>
      <li><a routerLink="tavern">Tavern</a></li>
      <li><a routerLink="defence">Defence</a></li>
    </ul>
  </div>
  <div id="content">
    <router-outlet></router-outlet>
  </div>
</div>
  1. 如前所述,还有多种其他方法可以缓存来自 observable 的数据。 你可以从这里这里开始。

  2. 与问题无关,我发现setInterval()与参数 1 的用法非常不雅。 您是否希望定期轮询端点以保持数据更新? 如果是这样,您可以查看 RxJS timerinterval function。 它们允许您使用单个订阅进行轮询,而不是每 n 秒打开一个新订阅。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM