This repository was archived by the owner on Feb 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathquery_api_implementation_notes.txt
186 lines (121 loc) · 5.2 KB
/
query_api_implementation_notes.txt
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
Query API. Implementation Notes
The Query API allows a directive to get access to other components in the same directive injector subtree.
Query Scopes:
* @children - direct children of the current DirectiveInjector in the light dom.
* @descendents - all descendents of the current DirectiveInjector in the light dom.
* @shadowDomChildren - direct children of the current DirectiveInjector in the shadow dom.
* @shadowDomDescendents - direct children of the current DirectiveInjector in the shadow dom.
To see Query API in action let's look at the following example:
<tabs>
<pane value='first'><pane>
<pane ng-if='showSecondPane' value='second'><pane>
</tabs>
@Component(selector: 'tabs', template: '<content></content>')
class Tabs {
Query<Pane> panes;
Tabs(@children this.panes, Logger log) {
panes.listen((iterable) {
log(iterable.map((c) => c.value));
log(iterable.map((c) => c.value));
});
}
}
@Component(selector: 'pane')
class Pane {
@NgAttr("value") String value;
}
The directive injector of the Tabs component requests an instance of `Query<Pane>` while constructing an instance of Tabs.
The directive injector checks if there is a query matching the requested type. Since there is none, it will create a new one:
query1 = constructQueryFor(Pane);
queryScope1 = CHILDREN;
At this point Angular creates a directive injector for creating the first pane.
In its constructor the directive injector copies the created query object:
inheritedQuery1 = parent.query1;
queryScope1 = SELF;
Note, that it uses a different slot to store the query. This is because in some situations inherited queries are handled differently.
In addition, it changes the scope of the query to SELF.
A bit later, after the `Pane` has been constructed, the query object will be marked as dirty:
inheritedQuery1.markAsDirty();
Note, that marking a query object as dirty just sets the dirty flag to true.
The query object via an async queue notifies the listener, which executes:
log(iterable.map((c) => c.value));
log(iterable.map((c) => c.value));
Since the query has been marked as dirty, the stored cache is not up-to-date. To rebuild the cache, the query objects
traverses the tree of directive injectors. The second call to `map` does not walk the tree of injectors and just uses the cache.
Next, let's set `showSecondPane` to true. This results in creating another directive injector that in its constructor will copy the created query:
inheritedQuery1 = parent.query1;
queryScope1 = SELF;
The creation of the second Pane results in:
inheritedQuery1.markAsDirty();
...
TODO(tbosch): How does this work together with ViewPorts, e.g. ng-repeat'ed tabs?
Invididual Operations:
Inherit Parent's Queries
=================================================
When creating a new DirectiveInject, we need to inherit all the parent's queries that have scope != SELF.
class DirectiveInjector {
DirectiveInjector() {
//...
if (parent.query1 != null && parent.queryScope1 != SELF) {
this.inheritedQuery1 = query1;
this.copiedQueryScope1 = parentToChildQueryScope(parent.queryScope1);
}
//same for parent.query2, parent.inheritedQuery1, parent.inheritedQuery2.
}
}
parentToChildQueryScope(queryScope) {
if (queryScope == CHILDREN) return SELF:
if (queryScope == DESCENDANTS) return DESCENDANTS:
}
Create New Query
=================================================
A new query object gets created when it is requested by a directive. If there is already a query matching the criteria, it should be returned.
We create a new query even if there is an existing copied query matching the criteria.
class DirectiveInjector {
getByKey() {
//...
if(query1 != null && query1.matchers(requestedQueryType)) return query1;
query1 = constructQueryObject(requestedQueryType);
}
}
Mark Query as Dirty
=================================================
class Query {
DirectiveInjector _inj;
//...
void markAsDirty(){
if (_dirty) return;
_dirty = true;
_inj.scope.runAsync(notifyListeners);
}
void notifyListeners(){
}
//...
}
Update Query's Cache
=================================================
The update operation is triggered only if the query is marked as dirty, and there is a client that requested the data by calling `forEach` or any other method on the provided Iterable.
class Query {
DirectiveInjector _inj;
//...
void _updateQueryCache() {
_processDirectiveInjector(DirectiveInjector injector) {
//queries do not cross component boundaries
if (injector is ComponentDirectiveInjector) return;
_list.addAll(injector.directives.where(shouldInclude));
injector._children.forEach(_processDirectiveInjector);
}
_list = [];
_processDirectiveInjector(this._inj);
}
bool shouldInclude(directive) => null;
//...
}
Move DirectiveInjector
=================================================
If a DirectiveInjecter gets removed or moved to another place in the tree, we need to mark all inherited queries as dirty.
Not inherited queries do not have to be marked as dirty.
onMove() {
if (copiedQuery1 != null) copiedQuery1.markAsDirty();
if (copiedQuery2 != null) copiedQuery1.markAsDirty();
}