订阅Created Observable再次呼叫Api

时间:2017-01-05 22:17:45

标签: angular angular2-observables

我有一个Api,它返回一个对象数组并将其转换为ng2-select2的对象,如此

    getRoles(): Observable<Array<Select2OptionData>> {
    return this.authHttp.get(`${this.usersUrl}/GetRoles`)
        .map(function (response) {
            var result = response.json();

            var temp = [];
            result.forEach(function (dept) {
                var d = {
                    'id': '0',
                    'text': dept.name,
                    'children': []
                };

                dept.roles.forEach(function (role) {
                    var r = {
                        'id': role.id.toString(),
                        'text': role.name
                    };
                    d.children.push(r);
                }, this);
                temp.push(d);
            }, this);
            return temp;
        })
        .catch(this.handleError);
}

返回的是一个包含子项的Select2OptionData数组

export interface Select2OptionData {
id: string;
text: string;
children?: Array<Select2OptionData>;
additional?: any;
}

为了加载Dropdown,我必须像它那样传递observable

getRoles(): void {
  this.roleDepartments =  this.userService
        .getRoles();
}

<select2 class="form-control" [data]="roleDepartments | async"  (valueChanged)="changed($event)" [width]="300"></select2>

这可以按预期工作,并在下载时使用数据填充下拉列表。我现在希望能够捕获在更改时选择的值以及支持的值。问题是我需要迭代&#34; roleDepartments&#34;并获取所选值的整个对象。由于对象是Observable,我不能迭代它。我已经尝试订阅它,将结果分配给变量并迭代它。当我尝试这个时,再次调用填充Observable的Api。我是不是最好的方式呢?如果没有,我该怎么办?

1 个答案:

答案 0 :(得分:1)

首先,再次订阅observable的原因是它被称为“冷可观察”。这意味着每个新订阅者都将获得observable的新副本,并启动任何创建该observable的工作。在Http的情况下,它会发出新请求。

因此,我们需要一种方法将其从“冷”可观察对象转换为在我们订阅时不会重新提交请求的方法,而是立即返回最近的结果。我们可以通过添加.publishReplay(1)运算符后跟.refCount()运算符来执行此操作,以便第一个订阅者启动请求,并且最后一个取消订阅者取消订阅相关的observable。

这样,在change()方法中,您可以执行以下操作:

this.roleDepartments.take(1).subscribe(roles => {
    let option: Select2OptionData;
    roles.forEach(role => {
        if (role.id === selectedId) {
             option = role;
        }
    });
});

这将只获得重放值(原始observable发出的最新值),然后通过.take(1)取消订阅。

我认为,这种方法不易发生泄漏,因为没有机会让您忘记取消订阅的悬挂订阅。这是因为使用async管道是安全的 - Angular负责正确订阅和取消订阅,change()方法中的其他订阅仅在初始加载数据后发生,然后立即自动取消订阅

更简单的方法

一个更简单的解决方案(更容易搞砸并导致泄漏)是让你的组件在ngOnInit()钩子中订阅一次observable,存储角色数组以及订阅对象,以及然后在ngOnDestroy()钩子中取消订阅。最后,在模板中,您根本不需要使用async管道。

更容易导致泄漏,因为如果您(或未来的开发人员)忘记取消订阅,您可能会通过阻止清理控制器实例来引入内存泄漏。

但如果您愿意承担这种风险,那就是它的样子:

 @Component({
      // ...
 })
 export class MyComponent implements OnInit, OnDestroy {
      private rolesSubscription: Subscription;
      private roleDepartments: Select2OptionData[];

      constructor(private userService: UserService) { }

      ngOnInit(): void {
          this.rolesSubscription = this.userService.getRoles()
                                       .subscribe(roles => this.roleDepartments = roles);
      }

      ngOnDestroy(): void {
          this.rolesSubscription.unsubscribe();
      }

      change(selectedId: string) {
          // find correct role from this.roles and do something with it
      }
}

然后在您的模板中,将其稍微更改为:

<select2 class="form-control" [data]="roleDepartments" 
         (valueChanged)="changed($event)" [width]="300">
</select2>