Подписка на созданный наблюдаемый вызов снова Api
У меня есть 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, я должен передать его как можно более
getRoles(): void {
this.roleDepartments = this.userService
.getRoles();
}
<select2 class="form-control" [data]="roleDepartments | async" (valueChanged)="changed($event)" [width]="300"></select2>
Это работает, как и ожидалось, и выпадающий список заполняется данными при загрузке. Теперь я хочу иметь возможность зафиксировать значение, которое выбирается при изменении и поддерживается. Проблема в том, что мне нужно перебрать значения "roleDepartments" и получить весь объект для выбранного значения. Поскольку объект является наблюдаемым, я не могу перебрать его. Я пытался подписаться на него, присваивая результат в переменную и повторяя его. Когда я пытаюсь это сделать, снова вызывается Api, который заполняет Observable. Я собираюсь сделать это лучшим способом? Если нет, что мне делать?
1 ответ
Прежде всего, причина, по которой мы снова подписываемся на наблюдаемые, состоит в том, что это то, что известно как "наблюдаемые холода". Это означает, что каждый новый подписчик получит новую копию наблюдаемой и начнет любую работу, выполненную для создания этой наблюдаемой. В случае Http
, он сделает новый запрос.
Поэтому нам нужен способ превратить его из "холодной" наблюдаемой в такую, которая не будет повторно отправлять запрос при подписке, а вместо этого сразу же возвращает самый последний результат. Мы можем сделать это, добавив на .publishReplay(1)
оператор сопровождается .refCount()
оператор так, что первый подписчик запускает запрос, а последний отписывается отписывается от рассматриваемой наблюдаемой.
Таким образом, в change()
Метод, вы можете сделать:
this.roleDepartments.take(1).subscribe(roles => {
let option: Select2OptionData;
roles.forEach(role => {
if (role.id === selectedId) {
option = role;
}
});
});
Это получит только воспроизведенное значение (самое последнее значение, испущенное исходной наблюдаемой), а затем отписаться благодаря .take(1)
,
Я полагаю, что такой подход менее подвержен утечкам, поскольку нет шансов оставить висящую подписку на заметку, от которой вы забудете отказаться. Это потому, что с помощью async
труба безопасна - Angular позаботится о правильной подписке и отписке для вас, а другая подписка в change()
Метод происходит только после первоначальной загрузки данных, а затем сразу же автоматически отписывается.
Более простой подход
Более простое решение (которое легче испортить и вызвать утечки) состоит в том, чтобы ваш компонент подписывался на наблюдаемое один раз в ngOnInit()
подключить, сохранить массив ролей, а также объект подписки, а затем в 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>