如何在不重新渲染组件的情况下重新应用 属性 绑定?

How to re-apply a property binding without re-render the component?

上下文

一个业务组件 A 依赖于一个业务服务,它说:

A 通过单向 属性 绑定到多个技术子组件 B 传递此知识(计算,而不是存储值) ,定义了可折叠组件的模板(其内容是具体业务组件的投影)。

AB 首次呈现时,绑定被解析并且 B 接收知识:可折叠组件知道它们是否应该显示或隐藏它们的内容。尽管如此,如果用户愿意,他们仍然提供一个按钮来显示或隐藏内容。

注意:可折叠组件不会注入服务本身来直接访问知识,因为它是业务服务并且是技术组件。

问题

A 中的一个动作之后,我想 "re-resolve" 属性 与 B 绑定,所以我希望 A 再次将服务知识传递给 B,从而覆盖用户的任何操作(例如:如果他打开了一个可折叠的组件默认关闭,我想让它恢复到默认状态,所以关闭)。

最简单的解决方案是重新渲染组件 (destroy/create),但我不想那样,因为 :

1) 我不希望用户看到由组件的 destruction/rendering 引起的闪烁。

2) 除了这个问题之外没有充分的理由重新渲染组件。

代码

@Component({
    selector: 'business-parent',
    template: '
    <generic-collapsable-component [opened]="businessHelper.conditionA">
        // A business-child component
    </generic-collapsable-component> 
    // More generic-collapsable-component
    '
})
export class BusinessParentComponent {

    constructor(private businessHelper: BusinessHelper) {
    }

    onBusinessAction() {
        // Here I do some business stuff...
        // And I want to force the binding [opened] to re-execute its default value, so I want GenericCollapsableComponent.opened = businessHelper.conditionA, and not what I currently have, which is the value of the last GenericCollapsableComponent.switch() I called.
    }
}

@Component({
    selector: 'generic-collapsable-component',
    template: '
    <button (click)="switch()">Switch to show or hide content</button>
    <div [ngClass]="{'is-hidden': !isOpen()}"
        <ng-content></ng-content>
    </div>
    '
})
export class GenericCollapsableComponent {

    @Input() opened: boolean; // Is intialized by the parent component but can be modified by the inner switch() method called in the template.

    constructor() {
    }

    switch(): void {
        this.opened = !this.opened;
    }

    isOpen(): boolean {
        return this.opened;
    };
}

解决方案

我想你要的是两个-way binding(又名"BANANA IN A BOX")。这将允许您的父组件传递初始状态。然后子组件可以在子组件发生变化时传回该值。然后,您还可以根据您拥有的任何业务逻辑更改父组件中的 "opened" 状态,让您的通用可折叠组件在 opened/closed.

时忽略任何业务规则规定的状态

可折叠组件:

export class GenericCollapsableComponent {

    @Input() opened: boolean;
    @Output() openedChange = new EventEmittter<boolean>(); //must have the variable name followed by "Change"

    constructor() {
    }

    switch(): void {
        this.opened = !this.opened;
        this.openedChange.emit(this.opened); //alert the parent that the "opened" state has changed
    }

    isOpen(): boolean {
        return this.opened;
    };
}

折叠组件的用法:

<generic-collapsible [(opened)]="isOpened">
   <div>Some content!</div>
</generic-collapsible>

和代码隐藏:

export class ParentComponent {
    isOpened: boolean = true;
}

这里 stackblitz 展示了双向绑定和多种更改可折叠状态的不同方法。

我没有看到一种方法可以强制数据绑定在该值未更改时重新应用该值。由于不想在child组件中注入业务服务,我建议让两个组件通过技术服务进行通信,而不是使用数据绑定。

技术服务可以定义为:

import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";

@Injectable({
  providedIn: "root",
})
export class TechnicalService {

  private conditionChanged = new BehaviorSubject<boolean>(false);
  public conditionChanged$ = this.conditionChanged.asObservable();

  public notifyConditionChanged(value: boolean) {
    this.conditionChanged.next(value);
  }
}

假设当业务服务发出计算值时通知 parent 组件,它可以通过技术服务将信息中继到 child 组件:

export class AppComponent {
  constructor(
    private businessService: BusinessService,
    private technicalService: TechnicalService) {

    this.businessService.conditionChanged$.subscribe((value) => {
      this.technicalService.notifyConditionChanged(value);
    })
  }
  ...
}

child 将在技术服务通知时更新其 属性,替换用户手动更改的值:

export class ChildComponent {
  public opened: boolean;
  private subscription: Subscription;

  constructor(private technicalService: TechnicalService) {
    this.subscription = this.technicalService.conditionChanged$.subscribe(value => {
      this.opened = value;
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

  toggleOpened() {
    this.opened = !this.opened;
  }
}

有关演示,请参阅 this stackblitz