You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Angular中首次点击按钮时EventEmitter.emit未显示名称问题

问题分析与解决方案

我来帮你排查这个点击按钮首次不显示名称的问题,核心原因有两个关键点:

1. 服务里误用了EventEmitter

Angular的EventEmitter是专门为组件的@Output()装饰器设计的,绝对不应该在服务中使用。服务里的跨组件事件通信,应该用RxJS的SubjectBehaviorSubject来实现,这才是RxJS中用于这类场景的标准工具。

2. *ngIf导致的订阅时机差问题

你的<hello>组件是通过*ngIf="name"控制渲染的:

  • 第一次点击按钮时,nameService.show.emit('World')触发,AppComponent订阅到值后设置this.name = 'World',这时候<hello>组件才会被初始化。
  • <hello>组件的ngOnInit里才去订阅nameService.show,这时候第一次的emit事件已经结束了,所以它根本接收不到这个值。
  • 第二次点击时,<hello>已经存在,订阅已经生效,所以能正常收到emit的值。

修复步骤

第一步:修改服务,用BehaviorSubject替代EventEmitter

BehaviorSubject会保存最新的发射值,新订阅的观察者会立即拿到这个最新值,完美解决我们的时机差问题。同时别忘了给服务加上@Injectable,确保它是单例:

// name.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class NameService {
  // 私有Subject,内部管理值的发射
  private showSubject = new BehaviorSubject<string | null>(null);
  // 对外暴露只读的Observable,避免外部直接修改Subject
  show$ = this.showSubject.asObservable();

  emitName(name: string) {
    this.showSubject.next(name);
  }
}

第二步:修改各个组件的调用逻辑

ShowComponent(按钮组件):

调用服务新增的emitName方法来发射值:

// show.component.ts
import { Component } from '@angular/core';
import { NameService } from './name.service';

@Component({
  selector: 'show',
  template: `<button (click)="show()">Click to emit</button>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class ShowComponent {
  constructor(private nameService: NameService) { }

  show() {
    this.nameService.emitName('World');
  }
}

AppComponent:

订阅服务的show$流,这里可以保留*ngIf="name"(也可以去掉,因为BehaviorSubject会自动把最新值推给新创建的组件):

// app.component.ts
import { Component } from '@angular/core';
import { NameService } from './name.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
})
export class AppComponent {
  name: string | null = null;

  constructor(private nameService: NameService) { }

  ngOnInit() {
    this.nameService.show$.subscribe(name => {
      this.name = name;
    });
  }
}

HelloComponent:

同样订阅服务的show$流,现在即使组件是后来创建的,也能立即拿到最新的name值:

// hello.component.ts
import { Component } from '@angular/core';
import { NameService } from './name.service';

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
  name: string | null = null;

  constructor(private nameService: NameService) { }

  ngOnInit() {
    this.nameService.show$.subscribe(name => {
      this.name = name;
    });
  }
}

额外优化:取消订阅防止内存泄漏

在组件销毁时记得取消订阅,推荐用takeUntil操作符,以HelloComponent为例:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { NameService } from './name.service';
import { Subject, takeUntil } from 'rxjs';

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit, OnDestroy {
  name: string | null = null;
  private destroy$ = new Subject<void>();

  constructor(private nameService: NameService) { }

  ngOnInit() {
    this.nameService.show$
      .pipe(takeUntil(this.destroy$))
      .subscribe(name => {
        this.name = name;
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

这样修改后,第一次点击按钮就能正常显示Hello World!了!

内容的提问来源于stack exchange,提问作者wilcus

火山引擎 最新活动