Angular Behavior Subject в обслуживании сбивается при инициализации двух одинаковых компонентов
Я создал компонент, который называется боковой панелью. Этот компонент имеет, скажем, 2 входа, sideBarMode и sideBarSide. В app.component я добавляю этот компонент вот так
<sidebar [sideBarMode]="'sliding'" [sideBarSide]="'right'"></sidebar>
.
Когда я использую вышеуказанный тег как один, он работает как положено. Однако когда я делаю что-то вроде
<div>
<sidebar [sideBarMode]="'sliding'" [sideBarSide]="'right'"></sidebar>
</div>
<div>
<sidebar [sideBarMode]="'docked'" [sideBarSide]="'left'"></sidebar>
</div>
Они отображаются, но с одними и теми же классами, хотя, если я консоль регистрирую передаваемые значения в ngOnInit, они отображаются правильно. Например, один div должен показывать скольжение как класс, а другой как закрепленный. Вместо этого они отображаются как пристыкованные.
Это шаблон HTML.
<div class="container-fluid">
<div [ngClass]="barSliding ? 'sliding' : 'sidebar-docked'">
<div class="row k-sidebar {{theme}}"
[ngClass]="[sidebarLeft ? 'position-left' : 'position-right', barSliding ? 'sliding-content-bar' : 'sidebar-docked', isDesktopWidth.value ? 'largeView' : 'mobileView']"
[@slideAnimation]= 'animationState'
[@dockMobileSidebar]= 'animationStateMobile'
[ngStyle]="{'width.px': isDesktopWidth.value ? sideBarWidth : '' }"
>
<div class="col-md-12">
<ng-container *ngIf="sidebarTemplate">
<ng-container *ngTemplateOutlet="sidebarTemplate">
</ng-container>
</ng-container>
<ng-container *ngIf="!sidebarTemplate">
<p>This is a paragraph, no template has been passed</p>
</ng-container>
</div>
</div>
</div>
Вот как я проверяю режим, закреплен он или нет в файле TS, и вызываю этот метод из ngOnInit.
private checkSidebarMode(mode) {
console.log('checkSidebarMode ', mode);
if (mode === 'sliding') {
this.sidebarService.sideBarSliding.next(true);
console.log('checkSidebarMode ', this.sidebarService.sideBarSliding.getValue());
this.sideBarDock.next(false);
} else if (mode === 'docked' && this.sidebarService.innerWidth.getValue() <= 767) {
this.sideBarDock.next(false);
this.sidebarService.sideBarSliding.next(true);
} else if (mode === 'docked' && this.sidebarService.innerWidth.getValue() > 767) {
this.sideBarDock.next(true);
this.sidebarService.sideBarSliding.next(false);
this.sidebarService.animationState.next('docked');
console.log('checkSidebarMode ', this.sidebarService.sideBarSliding.getValue());
}
}
Как видите, если он скользит, я устанавливаю для наблюдаемого значение true, а затем в ngOninit я присваиваю это значение barSliding, которое используется в шаблоне HTML для определения имени класса.
Пристыкованный правильный, но скользящий получает свойства пристыковки... почему?
1 ответ
Как ваш BehaviorSubject
находится в sidebarService
, это наверное providedIn: 'root'
что делает его одноэлементным. Таким образом, вашBehaviorSubject
то же самое для двух экземпляров вашего компонента.
У вас есть несколько решений:
- объявить
BehaviorSubject
непосредственно в вашем компоненте вместо использования общего BehaviorSubject в службе. Я бы обновил его, позвонивnext()
вngOnChanges
метод, как толькоmode
входные изменения.
@Component({...})
export class MyComponent implements OnInit {
@Input() mode: 'sliding' | 'docked' = 'sliding';
private subject = new BehaviorSubject<string>(null);
ngOnChanges() {
// here you should check that 'mode' changed before calling next()
this.subject.next(this.mode);
}
}
- используйте фабричный метод в своем сервисе, который возвращает новый
BehaviorSubject
экземпляр, который будет использоваться вашим компонентом. Может быть, нет реальной выгоды от первого решения, если это простойBehaviorSubject
без какой-либо логики.
// service
@Injectable()
export class MyService {
public getNewBehaviorSubject() {
return new BehaviorSubject<String>(null);
}
}
// component
@Component({...})
export class MyComponent implements OnInit {
@Input() mode: 'sliding' | 'docked' = 'sliding';
private subject = this.myService.getNewBehaviorSubject();
ngOnChanges() {
// here you should check that 'mode' changed before calling next()
this.subject.next(this.mode);
}
}
Но это действительно имеет смысл, только если BehaviorSubject
при возврате из службы вы должны использовать другую информацию из службы (другие данные, статические переменные, такие как размер экрана и т. д.).
- сделайте вашу службу не одноэлементной, предоставив ее на уровне компонента, а не на уровне модуля или корня. Но вы можете предпочесть другие решения, поскольку использование не-одиночных сервисов может быть немного более запутанным и не совсем то, что вам нужно. Дополнительная информация об этом. Имейте в виду, что сервисы Angular по умолчанию спроектированы как одиночные. Это наиболее распространенный вариант использования.
Один вопрос, который вы должны задать себе, заключается в следующем: должно ли это поведение быть общим для компонентов или быть известным другим компонентам? В этом случае вы можете делегировать его глобальной одноэлементной службе. Пример: каждому компоненту может потребоваться в какой-то момент узнать, открыто ли главное скользящее меню приложения. Или это поведение специфично для экземпляра компонента? В этом случае состояние должно храниться в самом экземпляре компонента. Пример: у меня есть два складных элемента, каждый должен отслеживать, свернут он или нет, но ни один другой компонент не должен знать об этом состоянии.
Вы можете принять множество стратегий, и нет ответа, подходящего для всех ситуаций.