Angular服务中字符串、数组、对象的变更检测差异原因问询
这个问题的核心其实和JavaScript的原始类型与引用类型的赋值机制直接相关,咱们结合你给出的代码一步步拆解:
一、数组/对象能被检测到变更的原因
在你的ngOnInit里,这两行代码做的是引用赋值:
this.list = this.nameService.list; this.obj = this.nameService.obj;
数组和对象属于JavaScript的引用类型,当你把服务里的list和obj赋值给组件的同名属性时,组件的变量和服务的变量指向的是内存中的同一个对象/数组。
当3秒后服务里执行this.list.push('a', 'b')或者this.obj.a = 'obj.a changed'时,你并没有改变list或obj本身的引用,只是修改了它们内部的内容。Angular的变更检测机制在触发时(比如setTimeout的宏任务执行完后会自动触发变更检测),会重新计算模板里的表达式:
{{list | json}}会遍历数组内容,发现和之前不一样,更新视图{{obj | json}}会读取对象的属性值,发现a的内容变了,更新视图
二、字符串无法被检测到变更的原因
而字符串属于JavaScript的原始类型,在ngOnInit里的这行代码是值复制:
this.name = this.nameService.name;
原始类型的赋值是把当前值复制一份给新变量,所以组件的name和服务的name是完全独立的两个变量,只是初始值相同。
当3秒后服务里执行this.name = 'Name changed'时,只是把服务里的name变量指向了一个新的字符串(因为字符串是不可变的,修改其实是创建新值),但组件的name还是原来的空字符串,两者已经没有任何关联了。所以Angular的变更检测不会发现组件name的变化,视图自然不会更新。
三、解决方法
针对这个问题,有几种常见的解决方案:
1. 使用RxJS的可观察对象(推荐)
把服务里的字符串改成BehaviorSubject,让组件订阅变更:
// 服务代码修改 import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable() export class NameService { private nameSubject = new BehaviorSubject(''); name$ = this.nameSubject.asObservable(); list = []; obj = { a: 'something' }; constructor() { setTimeout(() => { this.nameSubject.next('Name changed'); this.list.push('a', 'b'); this.obj.a = 'obj.a changed'; }, 3000); } } // 组件代码修改 ngOnInit() { this.nameService.name$.subscribe(name => this.name = name); this.list = this.nameService.list; this.obj = this.nameService.obj; }
2. 组件直接绑定服务的变量
模板里直接使用服务的变量,而不是复制到组件属性:
<!-- 组件模板修改 --> <div>{{nameService.name}}</div> <div>{{list | json }}</div> <div>{{obj | json }}</div>
这样每次变更检测时,都会直接读取服务里name的最新值,自然能更新视图。
3. 手动触发变更检测(不推荐)
如果必须保留组件属性的赋值,可以在服务里添加一个变更通知的方法,组件订阅后手动触发变更检测:
// 服务里添加一个Subject import { Subject } from 'rxjs'; @Injectable() export class NameService { name = ''; list = []; obj = { a: 'something' }; private changeSubject = new Subject<void>(); change$ = this.changeSubject.asObservable(); constructor() { setTimeout(() => { this.name = 'Name changed'; this.changeSubject.next(); this.list.push('a', 'b'); this.obj.a = 'obj.a changed'; }, 3000); } } // 组件里订阅并触发变更检测 import { ChangeDetectorRef } from '@angular/core'; constructor(private nameService: NameService, private cdr: ChangeDetectorRef) { } ngOnInit() { this.name = this.nameService.name; this.list = this.nameService.list; this.obj = this.nameService.obj; this.nameService.change$.subscribe(() => { this.name = this.nameService.name; this.cdr.detectChanges(); }); }
内容的提问来源于stack exchange,提问作者Doua Beri

