【问题标题】:Angular BehaviorSubject usage in service and component, possible race conditionAngular BehaviorSubject 在服务和组件中的使用,可能的竞争条件
【发布时间】:2018-01-24 04:33:14
【问题描述】:

我确定这是一个简单的问题,我只是不知道在哪里找到它。 我有一个为用户数据创建 BehaviorSubject 的 UserProfile 服务。 当我登录时,我使用此服务来广播用户数据。 登录后,我会在我的 Dashbord 组件中订阅此用户数据。但是当我第一次创建 BehaviorSubject 时,userdata observable 只返回第一个空对象。就好像使用 .next() 推送新数据的登录功能仅在仪表板订阅了 BehaviourSubject 之后才运行。我尝试将仪表板的订阅放在 ngAfterContentInit() 中,以便 .next() 在订阅之前运行,但我仍然得到一个空白对象。如果我通过仪表板中的 set_new_data() 函数运行 .next() 以检查可观察的工作是否有效,我将获得 .next() 传递的数据;但是我没有通过我的服务中的 set_userdata() 函数获取我的登录组件设置的数据。

我们不能使用服务共享这样的数据吗?离开登录组件后服务是否被破坏或发生了什么?

我的用户档案服务:

@Injectable()
export class UserProfileService {
    public isLoggedIn: boolean = false;
    public userdata: BehaviorSubject<any> = new BehaviorSubject<any>({});

    constructor() {

    }

    public set_userdata(data) {
        // this.userdata = data;
        console.log(this.userdata);
        if (this.userdata === undefined) {
            console.log(data);
            //this.userdata = new BehaviorSubject<any[]>(data);
            console.log(this.userdata);
        } else {
            console.log(this.userdata);
            this.userdata.next(data);
        }

    }
}

我的仪表板组件:

@Component({
    selector: 'dashboard',
    styleUrls: ['./dashboard.scss'],
    templateUrl: './dashboard.html'
})
export class Dashboard {
    public isLoggedIn: boolean;
    public curr_user: object;

    constructor(private _appGlobals: AppGlobals, private _user: UserProfileService) {
        this._appGlobals.isUserLoggedIn.subscribe(value => this.isLoggedIn = value);
        // this.curr_user = _user.userdata;
    }

    ngAfterContentInit() {
        console.log("this executes first");
        this._user.userdata.subscribe(value => {
            this.curr_user = value;
            console.log(value);
        });
    }

    set_new_data() {
        let obj = {
            name: "test",
            age: 45
        };
        this._user.userdata.next(obj);
    }
}

我的控制台输出,(显示空对象:{},需要用户数据)

【问题讨论】:

  • 你们在哪里提供UserProfileService?为什么在代码//this.userdata = new BehaviorSubject&lt;any[]&gt;(data); 中注释掉这一行。完全删除它是安全的。不需要为其分配新实例。这将使所有订阅无效。
  • 我在 LoginModule 和 DashbordModule 中提供了它。那条注释行是因为我在创建 BSubject 时试图让初始实例拥有正确的数据
  • 这些模块之一是延迟加载的吗?要初始化 BehaviorSubject,只需调用 this._userdata.next(someInitialValue);
  • 我认为它是延迟加载的,我正在开发github.com/akveo/ng2-admin 框架。对于登录路径,我有这个声明:loadChildren: 'app/pages/login/login.module#LoginModule'
  • 延迟加载模块创建自己的 DI 根范围。您的 UserProfileService 周围可能有 2 个实例。尝试AppModule 中提供服务,如果您获得所需的行为,请重试。您可以实现forRoot() 并使用forRoot() 导入延迟加载的模块,以确保仅创建一个服务实例。 angular.io/guide/ngmodule-faq#what-is-the-forroot-method

标签: angular rxjs


【解决方案1】:

确保您只有一个服务实例,否则订阅者可能订阅的BehaviorSubject 不同于您用来发出事件的BehaviorSubject

在组件上提供服务可能会产生与创建该组件的实例一样多的服务实例。

另一个可能导致多个实例的陷阱是延迟加载模块,它们拥有自己的 DI 根范围。
来自非延迟加载模块的提供者被提升到应用程序根范围,即使多个非延迟加载模块提供相同的服务,也只会有一个实例。

由于 DI 作用域在初始化后无法更新,因此无法将提供程序从延迟加载的模块提升到应用程序根作用域,因此创建了一个新的“根”作用域(作为应用程序根作用域的子作用域)或另一个延迟加载的“根”作用域)用于一起加载的每组延迟加载模块。

使用forRoot 允许将提供程序从延迟加载的模块添加到应用程序根范围。另见https://angular.io/guide/ngmodule-faq#what-is-the-forroot-method,
或者只是在AppModule 或任何其他已知不能直接延迟加载的模块中提供服务。

【讨论】:

    【解决方案2】:

    如果您从 set_userdata(data) 返回 userdata 作为 Observable,然后订阅该函数而不是 Subject 本身,您的代码应该可以按预期工作。虽然我不确定最佳实践;

    //UserProfileService
    public set_userdata(data) { //--> Maybe make this get_userdata()
        // this.userdata = data;
        console.log(this.userdata);
        if (this.userdata === undefined) {
            console.log(data);
            console.log(this.userdata);
        } else {
            console.log(this.userdata);
            this.userdata.next(data);
        }
        return this.userdata.asObservable();
    }
    
    //Dashboard
    ngAfterContentInit() { //--> this could be in ngOnInit
        console.log("this executes first");
        this._user.set_userdata().subscribe(value => {
            this.curr_user = value;
            console.log(value);
        });
    }
    

    【讨论】:

    • 感谢您的回复,我在我的 userProfile 服务中添加了:public get_userdata() { return this.userdata.asObservable(); },仍然使用 set_userdata 进行登录以更新数据。但我的仪表板上仍然出现空对象:this._user.get_userdata().subscribe(value =&gt; { this.curr_user = value; console.log(value); });
    猜你喜欢
    • 1970-01-01
    • 2019-09-09
    • 1970-01-01
    • 2013-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-17
    • 2021-12-06
    相关资源
    最近更新 更多