Angular 中依赖注入问题造成 Observable 订阅不更新

这是园子博客后台从 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)打开上下文菜单是通过调用 CategoryContextMenuServiceopen 方法动态创建 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)在上下文菜单中点击编辑按钮,则调用 BlogCategoryEditorModalServiceopen 方法通过 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 订阅的 BlogCategoryStoreBlogCategoryEditorModalService 更新的 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 的代码。

posted @   dudu  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
历史上的今天:
2023-01-19 将错就错:DNS 错乱解析造成错误请求,借助 YARP 转发给正确的应用
2017-01-19 被Entity Framework Core的细节改进震撼了一下
2016-01-19 C#调用阿里云CDN API刷新缓存
2015-01-19 Mac中体验ASP.NET 5 beta2的K gen代码生成
2011-01-19 抛弃WebService,在.NET4中用 jQuery 调用 WCF
2005-01-19 [公告]新增三款Skin
2005-01-19 2005年1月16日 IT Pro 俱乐部活动纪实
点击右上角即可分享
微信分享提示