-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
changeDetectionService.ts
155 lines (139 loc) · 5.28 KB
/
changeDetectionService.ts
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
export enum ChangeDetectionStrategyType {
IdentityCheck = 'IdentityCheck',
DeepValueCheck = 'DeepValueCheck',
NoCheck = 'NoCheck'
}
export interface ChangeDetectionStrategy {
areEqual(a: any, b: any): boolean;
}
class SimpleFunctionalStrategy implements ChangeDetectionStrategy {
private strategy: (a: any, b: any) => boolean;
constructor(strategy: (a: any, b: any) => boolean) {
this.strategy = strategy;
}
areEqual(a: any, b: any): boolean {
return this.strategy(a, b);
}
}
class DeepValueStrategy implements ChangeDetectionStrategy {
areEqual(a: any, b: any): boolean {
return DeepValueStrategy.areEquivalent(DeepValueStrategy.copy(a), DeepValueStrategy.copy(b));
}
/*
* deeper object comparison - taken from https://stackoverflow.com/questions/1068834/object-comparison-in-javascript
*/
static unwrapStringOrNumber(obj: any) {
return obj instanceof Number || obj instanceof String ? obj.valueOf() : obj;
}
// sigh, here for ie compatibility
static copy(value: any): any {
if (!value) {
return value;
}
if (Array.isArray(value)) {
// shallow copy the array - this will typically be either rowData or columnDefs
const arrayCopy = [];
for (let i = 0; i < value.length; i++) {
arrayCopy.push(this.copy(value[i]));
}
return arrayCopy;
}
// for anything without keys (boolean, string etc).
// Object.keys - chrome will swallow them
if (typeof value !== "object") {
return value;
}
return [{}, value].reduce((r, o) => {
Object.keys(o).forEach(function (k) {
r[k] = o[k];
});
return r;
}, {});
}
static isNaN(value: any) {
if(Number.isNaN) {
return Number.isNaN(value);
}
// for ie11...
return typeof(value) === 'number' && isNaN(value);
}
/*
* slightly modified, but taken from https://stackoverflow.com/questions/1068834/object-comparison-in-javascript
*
* What we're trying to do here is determine if the property being checked has changed in _value_, not just in reference
*
* For eg, if a user updates the columnDefs via property binding, but the actual columns defs are the same before and
* after, then we don't want the grid to re-render
*/
static areEquivalent(a: any, b: any) {
a = DeepValueStrategy.unwrapStringOrNumber(a);
b = DeepValueStrategy.unwrapStringOrNumber(b);
if (a === b) return true; //e.g. a and b both null
if (a === null || b === null || typeof a !== typeof b) return false;
if(DeepValueStrategy.isNaN(a) && DeepValueStrategy.isNaN(b)) {
return true;
}
if (a instanceof Date) {
return b instanceof Date && a.valueOf() === b.valueOf();
}
if (typeof a === "function") {
// false to allow for callbacks to be reactive...
return false;
}
if (typeof a !== "object" ||
(a.$$typeof && a.$$typeof.toString() === "Symbol(react.element)")) {
return a == b; //for boolean, number, string, function, xml
}
if(Object.isFrozen(a) || Object.isFrozen(b)) {
return a === b;
}
const newA = a.areEquivPropertyTracking === undefined,
newB = b.areEquivPropertyTracking === undefined;
try {
let prop;
if (newA) {
a.areEquivPropertyTracking = [];
} else if (
a.areEquivPropertyTracking.some(function (other: any) {
return other === b;
})
)
return true;
if (newB) {
b.areEquivPropertyTracking = [];
} else if (b.areEquivPropertyTracking.some((other: any) => other === a)) {
return true;
}
a.areEquivPropertyTracking.push(b);
b.areEquivPropertyTracking.push(a);
const tmp = {};
for (prop in a)
if (prop != "areEquivPropertyTracking") {
(tmp as any)[prop] = null;
}
for (prop in b)
if (prop != "areEquivPropertyTracking") {
(tmp as any)[prop] = null;
}
for (prop in tmp) {
if (!this.areEquivalent(a[prop], b[prop])) {
return false;
}
}
return true;
} finally {
if (newA) delete a.areEquivPropertyTracking;
if (newB) delete b.areEquivPropertyTracking;
}
}
}
export class ChangeDetectionService {
private strategyMap: { [key in ChangeDetectionStrategyType]: ChangeDetectionStrategy } = {
[ChangeDetectionStrategyType.DeepValueCheck]: new DeepValueStrategy(),
[ChangeDetectionStrategyType.IdentityCheck]: new SimpleFunctionalStrategy((a, b) => a === b),
[ChangeDetectionStrategyType.NoCheck]: new SimpleFunctionalStrategy((a, b) => false)
};
public getStrategy(changeDetectionStrategy: ChangeDetectionStrategyType): ChangeDetectionStrategy {
return this.strategyMap[changeDetectionStrategy];
}
}