You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中从组件调用TodosService的addTodo方法不生效问题

Angular Todo应用组件调用addTodo后视图不更新的原因及解决方法

问题描述

我开发了一个采用Subject as a Service模式进行状态管理的Todo应用。TodosService中的addTodo方法在从组件调用时,虽然能执行(可打印新数组),但新增的Todo无法在视图中显示;而在服务内部构造函数中调用该方法时,视图能正常更新。

TodosService代码

import { Injectable } from '@angular/core';
import { Todo } from './todo.model';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject, map, pipe } from 'rxjs';

@Injectable()
export class TodosService {
  private todos$ = new BehaviorSubject<Todo[]>([]);
  constructor(private http: HttpClient) {
    this.http
      .get<Todo[]>('https://jsonplaceholder.typicode.com/users/1/todos')
      .subscribe((todos) => {
        this.todos$.next(todos);
      });
  }

  public getTodos(): Observable<Todo[]> {
    return this.todos$;
  }

  public getCompletedTodos(): Observable<Todo[]> {
    return this.todos$.pipe(
      map((todos) => todos.filter((todo) => todo.completed))
    );
  }

  public addTodo(event: string) {
    const newArray: Todo[] = [
      ...this.todos$.value,
      {
        userId: 1,
        id: this.todos$.value.length,
        title: event,
        completed: false,
      },
    ];
    this.todos$.next(newArray);
  }
}

AddTodo组件代码

import 'zone.js/dist/zone';
import { Component } from '@angular/core';
import { TodosService } from './todo.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-add-todo',
  standalone: true,
  imports: [CommonModule, FormsModule],
  providers: [TodosService],
  template: `
    <label>Add Todo</label>
    <input type="text" [(ngModel)]="todo" />
    <button (click)="addNewTodo()">Submit</button>
  `,
})
export class AddTodo {
  todo: string = '';
  constructor(private todoService: TodosService) {}

  addNewTodo() {
    this.todoService.addTodo(this.todo);
    this.todo = '';
  }

  ngOnInit() {}
}

服务内部调用正常的情况

当在服务构造函数中通过setTimeout调用addTodo时,视图更新正常:

constructor(private http: HttpClient) {
  this.http
    .get<Todo[]>('https://jsonplaceholder.typicode.com/users/1/todos')
    .subscribe((todos) => {
      this.todos$.next(todos);
    });

    setTimeout(()=> {
      this.addTodo('Todo from inside setTimeout')
    }, 3000)
}

原因分析

核心问题是服务实例不共享:

  • 在AddTodo组件的@Component装饰器的providers数组中声明了TodosService,这会让Angular为该组件及其子组件创建一个独立的服务实例。
  • 显示Todo列表的组件使用的是另一个TodosService实例(比如根注入器提供的实例),所以AddTodo组件调用自己实例的addTodo时,更新的是该实例内部的BehaviorSubject,列表组件订阅的是另一个实例的数据流,自然看不到更新。
  • 服务内部构造函数调用addTodo时,操作的是列表组件订阅的那个实例的BehaviorSubject,因此视图能正常更新。

解决方法

步骤1:确保服务全局单例

修改TodosService的@Injectable装饰器,添加providedIn: 'root',让Angular在根注入器中提供该服务,实现全局单例:

@Injectable({ providedIn: 'root' })
export class TodosService {
  // ... 原有代码不变
}

步骤2:移除组件内的服务声明

删除AddTodo组件@Component装饰器中的providers数组,确保组件使用根注入器的服务实例:

@Component({
  selector: 'app-add-todo',
  standalone: true,
  imports: [CommonModule, FormsModule],
  // 移除providers: [TodosService]
  template: `
    <label>Add Todo</label>
    <input type="text" [(ngModel)]="todo" />
    <button (click)="addNewTodo()">Submit</button>
  `,
})
export class AddTodo {
  // ... 原有代码不变
}

这样修改后,AddTodo组件和列表组件会使用同一个TodosService实例,调用addTodo时更新的数据流会被列表组件订阅到,视图就能正常更新了。


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:05:39