Angular 2+一次性绑定
Angular 2+ one-time binding
在 angular 1 中,我们可以这样进行一次绑定:{{ ::myFunction() }}
.
在 angular 2 这是投掷:
EXCEPTION: Template parse errors:
Parser Error: Unexpected token : at column 2 in [{{ ::consent(false, undefined, box) }}] in CookieConsent@5:29 ("ull-right" href="" (click)="consent(true, $event, box)">De acuerdo</a>
<span class="hidden">[ERROR ->]{{ ::consent(false, undefined, box) }}</span>
我们如何在 angular2 中进行一次绑定?
目前,您无法使用 Angular 2 进行一次性绑定。但是,您可以知道何时绑定更改并重置您的输入。
Angular 2 为其提供了 OnChanges 生命周期钩子。您需要实现 OnChanges 接口以获取更改。
请参阅下面的代码示例,其中我在调用 OnInit 时将数据绑定 属性 存储在私有变量中。
export class Footer implements OnInit, OnChanges {
@Input() public name: string;
private privname: string;
constructor() { }
ngOnInit() {
this.privname = this.name;
}
ngOnChanges(changes: { [key: string]: SimpleChange }): void {
if (!changes["name"].isFirstChange()) {
this.name = this.privname;
}
}
}
稍后当发生其他更改时,我会在后续更改时将该值设置为其旧值。
此机制的工作方式类似于一次性绑定。
替代解决方案:
您还可以使用 setter 函数来捕获更改。
ChangeDetectionStrategy.CheckOnce
是这个问题的解决方案。
这里有一些信息:
ChangeDetectionStrategy.CheckOnce is the solution for this problem.
这已更新为 OnPush
另请参阅代码中的注释:
export declare enum ChangeDetectionStrategy {
/**
* `OnPush` means that the change detector's mode will be set to `CheckOnce` during hydration.
*/
OnPush = 0,
/**
* `Default` means that the change detector's mode will be set to `CheckAlways` during hydration.
*/
Default = 1,
}
我在 angular 2 中找到了一次性绑定的解决方案:
https://github.com/angular/angular/issues/14033
我创建了这个指令:
import { Directive, TemplateRef, ViewContainerRef, NgZone } from "@angular/core";
@Directive({
selector: '[oneTime]',
})
export class OneTimeDirective {
constructor(template: TemplateRef<any>, container: ViewContainerRef, zone: NgZone) {
zone.runOutsideAngular(() => {
const view = container.createEmbeddedView(template);
setTimeout(() => view.detach());
})
}
}
并使用它:
<some-selector *oneTime [somePropertyToOneTimeBinding]="someValueToOneTimeBinding"></some-selector>
例如:
<iframe *oneTime [src]="myUrl"></iframe>
因为有一次 read/bind 在 angular 中默认是不可能的,我认为写一个 public getter 函数可以解决这个问题。
例如
public getValue():number {
return mynumber ? mynumber : 25; // if mynumber is not undefined the mynumber else return 25
}
//In html template
<input type="range" min="getValue()" max="100">
如果 getter 函数能够在模板呈现发生之前回复,这将完美地工作。因此,如果在 ngOnInit() 函数中完成 mynumber 的初始化会很棒
使用ngOnInit()
在 Angular 2+ 中,我们有 ngOnInit()
,通常只会 运行 一次,恰好在组件初始化时。这是解决一次性绑定问题的最简单且通常最好的解决方案。
绑定到一个函数可能会导致对该函数进行数十次不必要的调用并降低您的应用程序速度。
而不是 {{ ::myFunction() }}
,在组件上创建一个 属性 并在 ngOnInit()
中设置它的值:
export class MyComponent implements OnInit {
myValue: any;
constructor() { }
ngOnInit() {
this.myValue = /* CALL FUNCTIONS OR CALCULATE VALUE HERE */
}
}
然后在模板中,只需使用:
{{ myValue }}
您的计算将 运行 仅一次。
Angular 不提供任何语法糖来控制输入的绑定频率。
但是,您可以控制输入更新时的响应时间。有两种方法,可以使用其中一种或两种方法来获得所需的行为:
- 对输入使用setter并有条件地转发更新
到组件主体或组件模板。
- 正在切换变化检测
当输入为 on 或 off 时使用
ChangeDetectorRef
满意。
注意关闭变化检测并不意味着输入赢了'被更新。输入将始终更新,因此无论更改检测是 off 还是 on,都会调用 ngOnChanges
。但是,如果更改检测关闭,模板将不会根据更新的输入进行更新。请参阅 codesandbox 以了解此效果。
示例 1
为了解释上面的第 1 点,请考虑以下几点:
_input1: number;
@Input() set input1(input: number) {
if (this._input1 === undefined || this._input1 === null) {
this._input1 = input;
}
}
正在使用模板
<div>Bound Input1: {{ _input1 }}</div>
_input1
是组件的本地副本,仅在需要时更新,即在上述特定情况下,本地副本仅在之前为 null 或 未定义。 input1
是输入 属性。这里我们假设 undefined 或 null 值永远不会被认为是有效值,除非 input1
被传递 non-null non-undefined 值,我们将认为“绑定一次”没有发生。
示例 2
ChangeDetectorRef
文档摘录:
Base class that provides change detection functionality. A change-detection tree collects all views that are to be checked for changes. Use the methods to add and remove views from the tree, initiate change-detection ...
因此 ChangeDetectorRef 的方法 detach
可用于从变更检测树中分离组件。为了演示上面的第 2 点,以下示例等待所有输入都满足,然后关闭变化检测:
import {
ChangeDetectorRef,
Component,
Input,
OnChanges,
SimpleChanges
} from "@angular/core";
@Component({
selector: "bind-until-all",
template: `
<div>Input1: {{ input1 }}</div>
<div>Input2: {{ input2 }}</div>
<div>Input3: {{ input3 }}</div>
`
})
export class BindUntillAllComponent implements OnChanges {
/**
* We assume that a null or undefined value is not an empty.
* And until a non-empty value is passed to an input, it'll be considered as not-assigned.
*
* Based on the use case, define what is a suitable "empty" value
* and assign that value as the default value to the inputs.
*/
@Input() input1: number;
@Input() input2: number;
@Input() input3: number;
private isDetached: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
ngOnChanges(changes: SimpleChanges) {
// Check whether all inputs are satisfied i.e. they have non-empty values assigned
// For our case, type === "number" satisfies that the value is non-empty
const areAllInputsSatisfied = [this.input1, this.input2, this.input3].every(
(n) => typeof n === "number"
);
// Stop change detection after triggering the manual change detection once
if (areAllInputsSatisfied && !this.isDetached) {
this.cdr.detectChanges();
this.cdr.detach();
this.isDetached = true;
}
}
}
上面提到的实例codesandbox。
在 angular 1 中,我们可以这样进行一次绑定:{{ ::myFunction() }}
.
在 angular 2 这是投掷:
EXCEPTION: Template parse errors:
Parser Error: Unexpected token : at column 2 in [{{ ::consent(false, undefined, box) }}] in CookieConsent@5:29 ("ull-right" href="" (click)="consent(true, $event, box)">De acuerdo</a>
<span class="hidden">[ERROR ->]{{ ::consent(false, undefined, box) }}</span>
我们如何在 angular2 中进行一次绑定?
目前,您无法使用 Angular 2 进行一次性绑定。但是,您可以知道何时绑定更改并重置您的输入。
Angular 2 为其提供了 OnChanges 生命周期钩子。您需要实现 OnChanges 接口以获取更改。
请参阅下面的代码示例,其中我在调用 OnInit 时将数据绑定 属性 存储在私有变量中。
export class Footer implements OnInit, OnChanges {
@Input() public name: string;
private privname: string;
constructor() { }
ngOnInit() {
this.privname = this.name;
}
ngOnChanges(changes: { [key: string]: SimpleChange }): void {
if (!changes["name"].isFirstChange()) {
this.name = this.privname;
}
}
}
稍后当发生其他更改时,我会在后续更改时将该值设置为其旧值。
此机制的工作方式类似于一次性绑定。
替代解决方案: 您还可以使用 setter 函数来捕获更改。
ChangeDetectionStrategy.CheckOnce
是这个问题的解决方案。
这里有一些信息:
ChangeDetectionStrategy.CheckOnce is the solution for this problem.
这已更新为 OnPush
另请参阅代码中的注释:
export declare enum ChangeDetectionStrategy {
/**
* `OnPush` means that the change detector's mode will be set to `CheckOnce` during hydration.
*/
OnPush = 0,
/**
* `Default` means that the change detector's mode will be set to `CheckAlways` during hydration.
*/
Default = 1,
}
我在 angular 2 中找到了一次性绑定的解决方案: https://github.com/angular/angular/issues/14033
我创建了这个指令:
import { Directive, TemplateRef, ViewContainerRef, NgZone } from "@angular/core";
@Directive({
selector: '[oneTime]',
})
export class OneTimeDirective {
constructor(template: TemplateRef<any>, container: ViewContainerRef, zone: NgZone) {
zone.runOutsideAngular(() => {
const view = container.createEmbeddedView(template);
setTimeout(() => view.detach());
})
}
}
并使用它:
<some-selector *oneTime [somePropertyToOneTimeBinding]="someValueToOneTimeBinding"></some-selector>
例如:
<iframe *oneTime [src]="myUrl"></iframe>
因为有一次 read/bind 在 angular 中默认是不可能的,我认为写一个 public getter 函数可以解决这个问题。
例如
public getValue():number {
return mynumber ? mynumber : 25; // if mynumber is not undefined the mynumber else return 25
}
//In html template
<input type="range" min="getValue()" max="100">
如果 getter 函数能够在模板呈现发生之前回复,这将完美地工作。因此,如果在 ngOnInit() 函数中完成 mynumber 的初始化会很棒
使用ngOnInit()
在 Angular 2+ 中,我们有 ngOnInit()
,通常只会 运行 一次,恰好在组件初始化时。这是解决一次性绑定问题的最简单且通常最好的解决方案。
绑定到一个函数可能会导致对该函数进行数十次不必要的调用并降低您的应用程序速度。
而不是 {{ ::myFunction() }}
,在组件上创建一个 属性 并在 ngOnInit()
中设置它的值:
export class MyComponent implements OnInit {
myValue: any;
constructor() { }
ngOnInit() {
this.myValue = /* CALL FUNCTIONS OR CALCULATE VALUE HERE */
}
}
然后在模板中,只需使用:
{{ myValue }}
您的计算将 运行 仅一次。
Angular 不提供任何语法糖来控制输入的绑定频率。
但是,您可以控制输入更新时的响应时间。有两种方法,可以使用其中一种或两种方法来获得所需的行为:
- 对输入使用setter并有条件地转发更新 到组件主体或组件模板。
- 正在切换变化检测
当输入为 on 或 off 时使用
ChangeDetectorRef
满意。
注意关闭变化检测并不意味着输入赢了'被更新。输入将始终更新,因此无论更改检测是 off 还是 on,都会调用 ngOnChanges
。但是,如果更改检测关闭,模板将不会根据更新的输入进行更新。请参阅 codesandbox 以了解此效果。
示例 1
为了解释上面的第 1 点,请考虑以下几点:
_input1: number;
@Input() set input1(input: number) {
if (this._input1 === undefined || this._input1 === null) {
this._input1 = input;
}
}
正在使用模板
<div>Bound Input1: {{ _input1 }}</div>
_input1
是组件的本地副本,仅在需要时更新,即在上述特定情况下,本地副本仅在之前为 null 或 未定义。 input1
是输入 属性。这里我们假设 undefined 或 null 值永远不会被认为是有效值,除非 input1
被传递 non-null non-undefined 值,我们将认为“绑定一次”没有发生。
示例 2
ChangeDetectorRef
文档摘录:
Base class that provides change detection functionality. A change-detection tree collects all views that are to be checked for changes. Use the methods to add and remove views from the tree, initiate change-detection ...
因此 ChangeDetectorRef 的方法 detach
可用于从变更检测树中分离组件。为了演示上面的第 2 点,以下示例等待所有输入都满足,然后关闭变化检测:
import {
ChangeDetectorRef,
Component,
Input,
OnChanges,
SimpleChanges
} from "@angular/core";
@Component({
selector: "bind-until-all",
template: `
<div>Input1: {{ input1 }}</div>
<div>Input2: {{ input2 }}</div>
<div>Input3: {{ input3 }}</div>
`
})
export class BindUntillAllComponent implements OnChanges {
/**
* We assume that a null or undefined value is not an empty.
* And until a non-empty value is passed to an input, it'll be considered as not-assigned.
*
* Based on the use case, define what is a suitable "empty" value
* and assign that value as the default value to the inputs.
*/
@Input() input1: number;
@Input() input2: number;
@Input() input3: number;
private isDetached: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
ngOnChanges(changes: SimpleChanges) {
// Check whether all inputs are satisfied i.e. they have non-empty values assigned
// For our case, type === "number" satisfies that the value is non-empty
const areAllInputsSatisfied = [this.input1, this.input2, this.input3].every(
(n) => typeof n === "number"
);
// Stop change detection after triggering the manual change detection once
if (areAllInputsSatisfied && !this.isDetached) {
this.cdr.detectChanges();
this.cdr.detach();
this.isDetached = true;
}
}
}
上面提到的实例codesandbox。