Angular2 ngModel:为什么它可以更改不可变字符串?
Angular2 ngModel: Why can it change an immutable string?
我在这里有点困惑。
使用 ngModel 我可以执行以下操作:
import {NgModel} from "angular2/common";
@Component({
selector: "test",
template: `<input tpye="text" [(ngModel)]="strInObj.str"> {{strInObj|json}}`,
directives: [NgModel]
})
class Test{
strInObj: {str: string} = {str: "this is a string"};
}
现在,当我输入内容时,strInObj.str
会更新。我对此感到困惑,因为据我所知,字符串是不可变的,并且无法找到引用的父级。
在这种情况下,我直接传递了 str
属性,这意味着 ngModel
获得了对字符串的引用。如果它 "changes" 该引用上的字符串会创建一个新字符串,因此不会更改 strInObj.str
指向的原始字符串。至少这是我的理解。
并且无法找到传递给 ngModel 的引用的父级(没有像 str.parent()
这样的概念,这会 return strInObj
)
那么他们是怎么做到的呢?我试图了解 ts 和 js 来源,但是......好吧,我在这里。
我试图构建一个类似的指令,但最终只能传递包装字符串的对象,我还没有找到在传递 str
属性 时修改原始对象的方法直接....(在示例中,我将 strInObj
传递给我的指令,然后它将与传递的对象的 str
属性 一起工作,效果很好)。
如果有人能帮我解开这个谜,我会很高兴:)
编辑
In this plunker 我有我的自定义指令 StrDirective
和一个带有 NgModel 指令的输入字段。两者具有相同的绑定 exampleStr
,它在一个简单的范围内输出。
现在,当我在输入中输入文本时,您可以看到 exampleStr
正在更新。这是预期的行为。我知道这行得通。
StrDirective
在单击时更新其绑定。您可以看到它更新了字符串的 "working copy",但是 exampleStr
没有更新。
我现在的问题是:他们是怎么做到的/我怎样才能让我的指令更新 ExampleStr
而不必将其包装在对象中?
在Javascript中,所有字符串都是不可变的。当有人在输入字段中键入内容时,它会更新字符串的 "working copy",以便工作副本指向新的引用。或者换句话说,每次字符串更改时,它都是一个新的字符串引用。
当按下更改模型的键时,ngModelChange
输出事件被触发,然后使用新引用更新父组件的模型。参考现在是同步的。当你说 "modify the string that was passed to it" 时,这是不可能的,因为字符串是不可变的。
当双向绑定模型时:
[(ngModel)]="str"
绑定等同于:
[ngModel]="str" (ngModelChange)="str=$event"
只要str
(模型绑定)发生变化,就会触发@Output
ngModelChange
事件。通过这种方式,引用的更改会向上传播到设置双向模型绑定的所有组件,以便每个模型指向相同的引用。
[编辑]
在更新问题的Plnkr中,它显示用户在输入框中键入一个键后正在恢复双向绑定。问题是如何以及为什么?
要了解发生了什么,让我们看一下这两种情况:
用户单击标签并触发事件处理程序,从而更改绑定的@Input 值
用户在输入框中键入一个键。两个标签都自动重新绑定到相同的引用,并且双向模型绑定适用于两个标签。
在用户点击标签之前,所有绑定都是同步的:
S1 (app component)
/\
(exampleStr binding) S1 S1 (str component)
点击事件后,绑定的@Input模型发生变化。然后,从根开始进行一轮更改检测,并按深度优先顺序工作到子组件。由于 @Input 绑定向下传播,因此其他绑定没有真正改变。
在第一种情况下,这是单击事件后绑定的状态:
S1 (app component)
/\
(exampleStr binding) S1 S2 (str component)
当用户开始在具有双向绑定设置的文本框中键入时,它会触发一个 ngModelChange
事件,该事件将 exampleStr
的值更改为 S3
。
S3 (app component)
/\
(exampleStr binding) S3 S2 (str component)
然后启动默认的更改检测策略,它从根开始,按深度优先顺序向下处理到子组件。
按下某个键后的绑定状态是:
S3 (app component)
/\
(exampleStr binding) S3 S3 (str component)
如您所见,所有绑定再次同步。默认的变更检测策略检查所有组件;对模型的更改通过组件的 @Input
绑定以可预测的单向流从父级传播到子级。
要了解变化检测的工作原理,请将其视为分阶段进行。这过于简单化了,但它可能有助于您的理解:
- 输入绑定从根传播到子。 Angular 跟踪哪些模型绑定到哪些输入属性。稍后需要进行更改检测。
- 一个事件触发(例如点击事件)修改@Input 属性。使用双向模型绑定,模型的更改从子项向上传播到父项。
- 事件触发后,从根开始触发单轮更改检测(重复步骤 1)以重新同步所有绑定。在此过程中,所有应用程序和视图绑定都会更新。
注意:Angular 使用区域来猴子修补浏览器事件,因此它知道何时触发更改检测。
[编辑]
如果您希望在单击标签时更新消息,请像使用 ngModel 一样设置双向绑定:
@Input("str") value : string;
@Output("strChange") valueChange:EventEmitter<string> = new EventEmitter();
onClick(){
this.value = "new string";
this.valueChange.next(this.value);
}
HTML
<span [(str)]="exampleStr"></span><br>
我在这里有点困惑。 使用 ngModel 我可以执行以下操作:
import {NgModel} from "angular2/common";
@Component({
selector: "test",
template: `<input tpye="text" [(ngModel)]="strInObj.str"> {{strInObj|json}}`,
directives: [NgModel]
})
class Test{
strInObj: {str: string} = {str: "this is a string"};
}
现在,当我输入内容时,strInObj.str
会更新。我对此感到困惑,因为据我所知,字符串是不可变的,并且无法找到引用的父级。
在这种情况下,我直接传递了 str
属性,这意味着 ngModel
获得了对字符串的引用。如果它 "changes" 该引用上的字符串会创建一个新字符串,因此不会更改 strInObj.str
指向的原始字符串。至少这是我的理解。
并且无法找到传递给 ngModel 的引用的父级(没有像 str.parent()
这样的概念,这会 return strInObj
)
那么他们是怎么做到的呢?我试图了解 ts 和 js 来源,但是......好吧,我在这里。
我试图构建一个类似的指令,但最终只能传递包装字符串的对象,我还没有找到在传递 str
属性 时修改原始对象的方法直接....(在示例中,我将 strInObj
传递给我的指令,然后它将与传递的对象的 str
属性 一起工作,效果很好)。
如果有人能帮我解开这个谜,我会很高兴:)
编辑
In this plunker 我有我的自定义指令 StrDirective
和一个带有 NgModel 指令的输入字段。两者具有相同的绑定 exampleStr
,它在一个简单的范围内输出。
现在,当我在输入中输入文本时,您可以看到 exampleStr
正在更新。这是预期的行为。我知道这行得通。
StrDirective
在单击时更新其绑定。您可以看到它更新了字符串的 "working copy",但是 exampleStr
没有更新。
我现在的问题是:他们是怎么做到的/我怎样才能让我的指令更新 ExampleStr
而不必将其包装在对象中?
在Javascript中,所有字符串都是不可变的。当有人在输入字段中键入内容时,它会更新字符串的 "working copy",以便工作副本指向新的引用。或者换句话说,每次字符串更改时,它都是一个新的字符串引用。
当按下更改模型的键时,ngModelChange
输出事件被触发,然后使用新引用更新父组件的模型。参考现在是同步的。当你说 "modify the string that was passed to it" 时,这是不可能的,因为字符串是不可变的。
当双向绑定模型时:
[(ngModel)]="str"
绑定等同于:
[ngModel]="str" (ngModelChange)="str=$event"
只要str
(模型绑定)发生变化,就会触发@Output
ngModelChange
事件。通过这种方式,引用的更改会向上传播到设置双向模型绑定的所有组件,以便每个模型指向相同的引用。
[编辑]
在更新问题的Plnkr中,它显示用户在输入框中键入一个键后正在恢复双向绑定。问题是如何以及为什么?
要了解发生了什么,让我们看一下这两种情况:
用户单击标签并触发事件处理程序,从而更改绑定的@Input 值
用户在输入框中键入一个键。两个标签都自动重新绑定到相同的引用,并且双向模型绑定适用于两个标签。
在用户点击标签之前,所有绑定都是同步的:
S1 (app component)
/\
(exampleStr binding) S1 S1 (str component)
点击事件后,绑定的@Input模型发生变化。然后,从根开始进行一轮更改检测,并按深度优先顺序工作到子组件。由于 @Input 绑定向下传播,因此其他绑定没有真正改变。
在第一种情况下,这是单击事件后绑定的状态:
S1 (app component)
/\
(exampleStr binding) S1 S2 (str component)
当用户开始在具有双向绑定设置的文本框中键入时,它会触发一个 ngModelChange
事件,该事件将 exampleStr
的值更改为 S3
。
S3 (app component)
/\
(exampleStr binding) S3 S2 (str component)
然后启动默认的更改检测策略,它从根开始,按深度优先顺序向下处理到子组件。
按下某个键后的绑定状态是:
S3 (app component)
/\
(exampleStr binding) S3 S3 (str component)
如您所见,所有绑定再次同步。默认的变更检测策略检查所有组件;对模型的更改通过组件的 @Input
绑定以可预测的单向流从父级传播到子级。
要了解变化检测的工作原理,请将其视为分阶段进行。这过于简单化了,但它可能有助于您的理解:
- 输入绑定从根传播到子。 Angular 跟踪哪些模型绑定到哪些输入属性。稍后需要进行更改检测。
- 一个事件触发(例如点击事件)修改@Input 属性。使用双向模型绑定,模型的更改从子项向上传播到父项。
- 事件触发后,从根开始触发单轮更改检测(重复步骤 1)以重新同步所有绑定。在此过程中,所有应用程序和视图绑定都会更新。
注意:Angular 使用区域来猴子修补浏览器事件,因此它知道何时触发更改检测。
[编辑]
如果您希望在单击标签时更新消息,请像使用 ngModel 一样设置双向绑定:
@Input("str") value : string;
@Output("strChange") valueChange:EventEmitter<string> = new EventEmitter();
onClick(){
this.value = "new string";
this.valueChange.next(this.value);
}
HTML
<span [(str)]="exampleStr"></span><br>