1

当设置订阅的指令被销毁时,我在取消订阅主题时遇到问题。考虑以下 html:

<ng-container *ngFor="let item of items; let id = index">
    <div [toggleCollapsible]="'target'+id">
        {{ item.label }}
    </div>
    <div *toggleCollapsibleTarget="'target'+id">
        <h1>Some nice content up in here</h1>
    </div>
</ng-container>

toggleCollapsible指令接收并@Input()带有一个唯一 ID,该 ID 将用于识别哪些内容应该折叠/取消折叠,这是由*toggleCollapsibleContent结构指令完成的。这 2 个指令之间的通信由名为 的服务处理toggleCollapsibleService

toggleCollapsible这是该指令的一些代码。为了便于阅读,我省略了一些内容:

@Directive({
    selector: "[toggleCollapsible]",
    host: {
        "(click)": "_onClick($event)",
    }
})
export class toggleCollapsibleDirective {
    @Input('toggleCollapsible') target: string;
    isOpen: boolean;
    constructor(private _toggle: toggleCollapsibleService) {}
    _onClick(e) {
        this._toggle.toggleContent(this.target, this.isOpen);
        this.isOpen = !this.isOpen;
    }
}

基本上,当单击宿主元素时,调用接收 2 个参数的服务方法,目标名称和可折叠当前是否打开。现在,我的toggleCollapsibleService

@Injectable()
export class toggleCollapsibleService {
    targetName: string;
    private togglerState$: Subject<boolean> = new Subject();
    toggleContent(target: string, currentState: boolean) {
        this.targetName = target;
        this.togglerState$.next(!currentState);
    }
}

所以,基本上这只是保存将要打开/关闭的可折叠的 ID 并传递相应的值(同样,它应该打开还是关闭)。让我们看看*toggleCollapsibleContent事情变得棘手的地方:

@Directive({
    selector: "[toggleCollapsibleContent]"
})
export class toggleCollapsibleContentDirective {
    private _name: string;

    @Input("toggleCollapsibleContent")
    set name(name: string) {
        this._name = name;
        this._toggle.togglerState$.subscribe(status => {
            if (this._name == this._toggle.targetName && status) {
                this.renderTarget();
            } else if (this._name == this._toggle.targetName) {
                this.unmountTarget();
            }
        });
    }

    constructor(
        private _view: ViewContainerRef,
        private _template: TemplateRef<any>,
        private _toggle: toggleCollapsibleService
    ) {}    

    renderTarget() {
        this._view.createEmbeddedView(this._template);
    }

    unmountTarget() {
        if (this._view) this._view.clear();
    }
}

结构指令工作正常,因此实施的那一侧没有问题。所以问题是,假设我有 HTML 片段,HomeComponent并且items集合的长度为 2。这意味着我正在创建*toggleCollapsibleContent结构指令的 2 个实例,每个实例都订阅togglerState$主题。如果通过对象进行检查console.logtogglerState$我会发现我的对象有 2 个观察者,这是预期的行为,每个*toggleCollapsibleContent.

但是,如果我转到另一条路线并渲染另一个组件等等,togglerState$主题仍然存在,当我回到加载了我的/home路线时HomeComponent,toggerState$ 会增加 2 个观察者,并且由于原始观察者仍然存在,现在我有 4 个观察者,每个*toggleCollapsibleContent指令实例 2 个,因此我的内容被复制了。

有谁知道为什么会这样?

4

1 回答 1

0

您需要明确取消订阅:

    this.subscription = this._toggle.togglerState$.subscribe(status => { ...

...

ngOnDestroy() {
  this.subscription.unsubscribe();
}
于 2017-07-20T11:29:56.550 回答