这是园子博客后台从 angular 15 升级到 angular 19 后遇到的一个问题。博客后台「随笔 」的侧边栏会显示随笔的分类列表 ,通过这个列表的上下文菜单可以修改分类名称,升级后测试时发现一个问题,修改分类名称后分类列表没有随之更新。
侧边栏随笔分类列表是 SidebarBlogCategoriesComponent
通过订阅 BlogCategoryStore
的 Observable 类型的 categories$
属性更新的
class SidebarBlogCategoriesComponent implements OnInit {ngOnInit(): void {this._store.categories$.pipe(this._rxHelper.autoUnsubscribe,distinctUntilChanged<BlogCategoryEditDto[]>(isEqual),map(categories => {//...})).subscribe(async nodes => {//...});}
}
对应的 BlogCategoryStore
部分实现代码
@Injectable({providedIn: 'any'
})
export class BlogCategoryStore {readonly categoryList$: Observable<BlogCategoryList>;protected readonly $categoryList = new BehaviorSubject<BlogCategoryList | undefined>(undefined);private _categories$?: Observable<BlogCategoryEditDto[]> | null;get categories$(): Observable<BlogCategoryEditDto[]> {return (this._categories$ ??= this.$categoryList.pipe(map(x => x?.categories ?? [])));}
}
页面加载时通过 SidebarBlogCategoriesComponent
的 parent component PostsSidebarComponent
调用 BlogCategoryStore.refresh
方法触发分类列表的订阅更新,这个地方正常
export class PostsSidebarComponent implements OnInit {ngOnInit() {this.categoryStore.refresh(BlogCategoryType.postCollection);}
}
修改分类名称通过上下文菜单打开编辑分类的对话框进行的:
1)打开上下文菜单是通过调用 CategoryContextMenuService
的 open
方法动态创建 CategoryContextMenuComponent
@Injectable({providedIn: 'any',
})
export class CategoryContextMenuService {constructor(private readonly contextMenuServ: NzContextMenuService,) { }async open<THost extends object>(categoryType: BlogCategoryType,ev: MouseEvent,containerRef: ViewContainerRef) {const { CategoryContextMenuComponent: _CategoryContextMenuComponent } = await import('./category-context-menu/category-context-menu.component');const compRef = containerRef.createComponent(_CategoryContextMenuComponent);//...if (comp.menu) this.contextMenuServ.create(ev, comp.menu);}
}
2)在上下文菜单中点击编辑按钮,则调用 BlogCategoryEditorModalService
的 open
方法通过 NzModalService
打开模态对话框
openEditCategoryModal() {if (this.category) {this._categoryModalServ.open(this.categoryType, {category: this.category,});}
}
BlogCategoryEditorModalService
中对应的部分实现代码
@Injectable({providedIn: 'any',
})
export class BlogCategoryEditorModalService {constructor(private _categoryServ: BlogCategoryService,private _categoryStore: BlogCategoryStore,private readonly nzModalService: NzModalService) { }open(categoryType: BlogCategoryType, { category, createdCallback }: CategoryEditModalOption = {}) {const isCreating = !category || category.categoryId <= 0;const categoryTypeName = BlogCategoryTypeNameMap[categoryType];const title = isCreating ? `新建${categoryTypeName}` : `编辑${categoryTypeName}`;const modalRef = this.nzModalService.create({nzTitle: title,nzContent: BlogCategoryEditComponent,nzData: { categoryType, category },nzOnOk: async comp => {//.... await Promise.all(Array.from(parentsToRefresh).map(p =>// 在编辑分类后刷新分类列表 this._categoryStore.refreshAsync(categoryType, p)));//....return true;},});}
}
3)模态对话框中运行的是进行分类编辑操作的 BlogCategoryEditComponent
export class BlogCategoryEditComponent implements OnInit {constructor(private readonly fb: NonNullableFormBuilder,private readonly systemInfoServ: SystemInfoService,@Inject(NZ_MODAL_DATA)private readonly nzModalData: {categoryType: BlogCategoryType,category: BlogCategoryEditDto | null},) {//...}//...
}
在模态对话框中完成分类编辑(修改分类名称)后,模态对话框会关闭,然后执行 BlogCategoryEditorModalService
中的 nzOnOk
回调方法,调用 this._categoryStore.refreshAsync
方法更新 BlogCategoryStore
中的 _categories$
,SidebarBlogCategoriesComponent
订阅了这个 Observable,从而触发侧边栏分类列表的更新。
现在的问题是虽然 this._categoryStore.refreshAsync
用修改后分类列表数据更新了 _categories$
,但 SidebarBlogCategoriesComponent
并没有响应这个 Observable 的更新。
一开始折腾了一段时间,没找到任何线索。
后来突然想到,出现这个问题唯一可以解释得通的原因就是 SidebarBlogCategoriesComponent
订阅的 BlogCategoryStore
与 BlogCategoryEditorModalService
更新的 BlogCategoryStore
不是同一个实例。
有了这个思路后,立马想到的是动手验证这个2个实例是否是同一个,参考这篇博文 Get object reference IDs in JavaScript/TypeScript,很快完成了验证,详见博问 https://q.cnblogs.com/q/151643
验证结果如下:
CategoryContextMenuService.categoryStore id: 2
SidebarBlogCategoriesComponent._store id: 2
CategoryContextMenuComponent.categoryStore id: 3
BlogCategoryEditorModalService._categoryStore id: 3
果然不是同一个实例!从 CategoryContextMenuComponent
开始就不是同一个实例,这个 Component 是通过下面的代码动态创建的
const compRef = containerRef.createComponent(_CategoryContextMenuComponent);
改为在 createComponent 时将 Injector 与 EnvironmentInjector 传递过去,问题就解决了。
const compRef = containerRef.createComponent(_CategoryContextMenuComponent,{injector: this.injector,environmentInjector: this.envInjector}
);
之所以升级后出现这个问题,是因为在升级过程中重构代码时删除了上面的 createComponent 时传递 Injector 的代码。