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 по умолчанию спроектированы как одиночные. Это наиболее распространенный вариант использования.

Один вопрос, который вы должны задать себе, заключается в следующем: должно ли это поведение быть общим для компонентов или быть известным другим компонентам? В этом случае вы можете делегировать его глобальной одноэлементной службе. Пример: каждому компоненту может потребоваться в какой-то момент узнать, открыто ли главное скользящее меню приложения. Или это поведение специфично для экземпляра компонента? В этом случае состояние должно храниться в самом экземпляре компонента. Пример: у меня есть два складных элемента, каждый должен отслеживать, свернут он или нет, но ни один другой компонент не должен знать об этом состоянии.

Вы можете принять множество стратегий, и нет ответа, подходящего для всех ситуаций.

Другие вопросы по тегам