【问题标题】:How to re-apply a property binding without re-render the component?如何在不重新渲染组件的情况下重新应用属性绑定?
【发布时间】:2020-04-17 12:31:41
【问题描述】:

上下文

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

  • true:所有可折叠组件都应默认打开。

  • false:默认关闭所有可折叠组件。

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

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

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

问题

A中的一个动作之后,我想“重新解析”与B的属性绑定,所以我希望A通过再次向 B 提供服务知识,从而覆盖用户的任何操作(例如:如果他打开了一个默认关闭的可折叠组件,我希望它恢复到默认状态,所以关闭) .

最简单的解决方案是重新渲染组件(销毁/创建),但我不希望这样做,因为:

1)我不希望用户看到由于组件的破坏/渲染而导致的闪烁。

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;
    };
}

解决方案

  • 重新渲染组件:否。
  • 绑定一个函数 () => boolean 来设置初始值并使用另一个私有布尔值来响应用户操作:这是我所做的并且它有效,但它不是理想。

【问题讨论】:

  • 你能创建一个堆栈闪电战来显示这个问题吗?帮助进行现场演示会容易得多。
  • 有时间我会试试的。

标签: angular typescript components property-binding


【解决方案1】:

我认为您要的是两个-way binding(又名“盒子里的香蕉”)。这将允许您的父组件以初始状态传递。然后,子组件可以在子组件中更改值时将其传回。然后,您还可以根据您拥有的任何业务逻辑更改父组件中的“打开”状态,让您的通用可折叠组件不知道打开/关闭某物时所规定的任何业务规则。

可折叠组件:

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,展示了双向绑定以及改变折叠状态的多种不同方式。

【讨论】:

  • 感谢您的建议,但不幸的是布尔值来自计算,而不是存储值(在您的示例中为“isOpened”),因此无处存储发出的值:它只能是单程。此外,如果它可以是两种方式,我不希望这样,因为值来自业务服务,并且作为技术的组合器不应该能够修改此服务,这会在发出值时发生。
  • 我认为将可折叠的状态存储在某处是不可避免的。我添加了一个 stackblitz,它展示了如何使用双向绑定从三种方式(服务、父组件和子组件)更新折叠状态。这有帮助吗?
  • 我认为你的两种方式绑定的解决方案最适合我的情况,谢谢(但我让可折叠的投影组件存储了“'isOpened”的默认值,而不是父组件,因为每个可折叠的可能有不同的默认值)
【解决方案2】:

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

技术服务可以定义为:

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);
  }
}

假设当业务服务发出计算值时通知父组件,它可以通过技术服务将信息传递给子组件:

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

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

当技术服务通知时,孩子会更新其属性,替换用户手动更改的值:

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 以获取演示。

【讨论】:

  • 您应该确保取消订阅您的服务。
猜你喜欢
  • 1970-01-01
  • 2020-08-18
  • 1970-01-01
  • 2018-06-10
  • 1970-01-01
  • 2017-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多