forked from angulardart/angular
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathng_for.dart
204 lines (190 loc) · 7 KB
/
ng_for.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import 'package:ngdart/src/meta.dart';
import 'package:ngdart/src/utilities.dart';
import '../../core/change_detection/differs/default_iterable_differ.dart';
import '../../core/linker.dart';
/// The `NgFor` directive instantiates a template once per item from an
/// iterable. The context for each instantiated template inherits from the outer
/// context with the given loop variable set to the current item from the
/// iterable.
///
/// ### Local Variables
///
/// `NgFor` provides several exported values that can be aliased to local
/// variables:
///
/// * `index` will be set to the current loop iteration for each template
/// context.
/// * `first` will be set to a boolean value indicating whether the item is the
/// first one in the
/// iteration.
/// * `last` will be set to a boolean value indicating whether the item is the
/// last one in the
/// iteration.
/// * `even` will be set to a boolean value indicating whether this item has an
/// even index.
/// * `odd` will be set to a boolean value indicating whether this item has an
/// odd index.
///
/// ### Change Propagation
///
/// When the contents of the iterator changes, `NgFor` makes the corresponding
/// changes to the DOM:
///
/// * When an item is added, a new instance of the template is added to the DOM.
/// * When an item is removed, its template instance is removed from the DOM.
/// * When items are reordered, their respective templates are reordered in the
/// DOM.
/// * Otherwise, the DOM element for that item will remain the same.
///
/// Angular uses object identity to track insertions and deletions within the
/// iterator and reproduce those changes in the DOM. This has important
/// implications for animations and any stateful controls
/// (such as `<input>` elements which accept user input) that are present.
/// Inserted rows can be animated in, deleted rows can be animated out, and
/// unchanged rows retain any unsaved state such as user input.
///
/// It is possible for the identities of elements in the iterator to change
/// while the data does not. This can happen, for example, if the iterator
/// produced from an RPC to the server, and that RPC is re-run. Even if the data
/// hasn't changed, the second response will produce objects with different
/// identities, and Angular will tear down the entire DOM and rebuild it (as if
/// all old elements were deleted and all new elements inserted). This is an
/// expensive operation and should be avoided if possible.
///
/// ### Examples
///
/// <?code-excerpt "docs/template-syntax/lib/app_component.html (NgFor-1)"?>
/// ```html
/// <div *ngFor="let hero of heroes">{{hero.name}}</div>
/// ```
///
/// <?code-excerpt "docs/template-syntax/lib/app_component.html (NgFor-2)"?>
/// ```html
/// <hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
/// ```
///
/// <?code-excerpt "docs/structural-directives/lib/app_component.html (inside-ngfor)"?>
/// ```html
/// <div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
/// [class.odd]="odd">
/// ({{i}}) {{hero.name}}
/// </div>
///
/// <template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
/// [ngForTrackBy]="trackById">
/// <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
/// </template>
/// ```
///
/// For details, see the [`ngFor` discussion in the Template Syntax][guide]
/// page.
///
/// [guide]: https://angulardart.xyz/guide/template-syntax.html#ngFor
@Directive(
selector: '[ngFor][ngForOf]',
)
class NgFor implements DoCheck {
final ViewContainerRef _viewContainer;
DefaultIterableDiffer? _differ;
Iterable<Object?>? _ngForOf;
TrackByFn? _ngForTrackBy;
TemplateRef _templateRef;
NgFor(this._viewContainer, this._templateRef);
@Input()
set ngForOf(Iterable<Object?>? value) {
_ngForOf = value;
if (_differ == null && value != null) {
_differ = DefaultIterableDiffer(_ngForTrackBy);
}
}
@Input()
set ngForTemplate(TemplateRef? value) {
if (value != null) {
_templateRef = value;
}
}
/// Optionally; set a function used to determine uniqueness of an element.
///
/// See [TrackByFn] for more details on how to use this parameter type.
@Input()
set ngForTrackBy(TrackByFn? value) {
_ngForTrackBy = value;
if (_ngForOf != null) {
final differ = _differ;
if (differ == null) {
_differ = DefaultIterableDiffer(_ngForTrackBy);
} else {
_differ = differ.clone(_ngForTrackBy);
}
}
}
@override
void ngDoCheck() {
final differ = _differ;
if (differ != null) {
var changes = differ.diff(_ngForOf);
if (changes != null) _applyChanges(changes);
}
}
void _applyChanges(DefaultIterableDiffer changes) {
// TODO(rado): check if change detection can produce a change record that is
// easier to consume than current.
final insertTuples = <_RecordViewTuple>[];
changes.forEachOperation((
CollectionChangeRecord item,
int? adjustedPreviousIndex,
int? currentIndex,
) {
if (item.previousIndex == null) {
var view = _viewContainer.insertEmbeddedView(
_templateRef,
currentIndex!,
);
var tuple = _RecordViewTuple(item, view);
insertTuples.add(tuple);
} else if (currentIndex == null) {
_viewContainer.remove(adjustedPreviousIndex!);
} else {
var view = _getEmbeddedViewRef(adjustedPreviousIndex!);
_viewContainer.move(view, currentIndex);
var tuple = _RecordViewTuple(item, view);
insertTuples.add(tuple);
}
});
for (var i = 0; i < insertTuples.length; i++) {
_perViewChange(insertTuples[i].view, insertTuples[i].record);
}
for (var i = 0, len = _viewContainer.length; i < len; i++) {
var viewRef = _getEmbeddedViewRef(i);
viewRef.setLocal('first', identical(i, 0));
viewRef.setLocal('last', identical(i, len - 1));
viewRef.setLocal('index', i);
viewRef.setLocal('count', len);
}
changes.forEachIdentityChange((record) {
var viewRef = _getEmbeddedViewRef(record.currentIndex!);
viewRef.setLocal('\$implicit', record.item);
});
}
/// Returns the [EmbeddedViewRef] at [index] in its view container.
///
/// Because [ViewContainerRef] supports inserting [ViewRef], there's no
/// guarantee that [ViewContainerRef.get] returns an [EmbeddedViewRef].
///
/// However, in practice this is the only directive controlling its view
/// container, and it only inserts [EmbeddedViewRef] instances, so its safe to
/// assume that the returned [ViewRef]s are all [EmbeddedViewRef]s.
EmbeddedViewRef _getEmbeddedViewRef(int index) =>
unsafeCast(_viewContainer.get(index));
void _perViewChange(EmbeddedViewRef view, CollectionChangeRecord record) {
view.setLocal('\$implicit', record.item);
var currentIndex = record.currentIndex!;
view.setLocal('even', currentIndex.isEven);
view.setLocal('odd', currentIndex.isOdd);
}
}
class _RecordViewTuple {
final EmbeddedViewRef view;
final CollectionChangeRecord record;
_RecordViewTuple(this.record, this.view);
}