UNPKG

15.8 kBTypeScriptView Raw
1declare module '@ember/object/observable' {
2 /**
3 @module @ember/object/observable
4 */
5 import Mixin from '@ember/object/mixin';
6 export type ObserverMethod<Target, Sender> =
7 | keyof Target
8 | ((this: Target, sender: Sender, key: string, value: any, rev: number) => void);
9 /**
10 ## Overview
11
12 This mixin provides properties and property observing functionality, core
13 features of the Ember object model.
14
15 Properties and observers allow one object to observe changes to a
16 property on another object. This is one of the fundamental ways that
17 models, controllers and views communicate with each other in an Ember
18 application.
19
20 Any object that has this mixin applied can be used in observer
21 operations. That includes `EmberObject` and most objects you will
22 interact with as you write your Ember application.
23
24 Note that you will not generally apply this mixin to classes yourself,
25 but you will use the features provided by this module frequently, so it
26 is important to understand how to use it.
27
28 ## Using `get()` and `set()`
29
30 Because of Ember's support for bindings and observers, you will always
31 access properties using the get method, and set properties using the
32 set method. This allows the observing objects to be notified and
33 computed properties to be handled properly.
34
35 More documentation about `get` and `set` are below.
36
37 ## Observing Property Changes
38
39 You typically observe property changes simply by using the `observer`
40 function in classes that you write.
41
42 For example:
43
44 ```javascript
45 import { observer } from '@ember/object';
46 import EmberObject from '@ember/object';
47
48 EmberObject.extend({
49 valueObserver: observer('value', function(sender, key, value, rev) {
50 // Executes whenever the "value" property changes
51 // See the addObserver method for more information about the callback arguments
52 })
53 });
54 ```
55
56 Although this is the most common way to add an observer, this capability
57 is actually built into the `EmberObject` class on top of two methods
58 defined in this mixin: `addObserver` and `removeObserver`. You can use
59 these two methods to add and remove observers yourself if you need to
60 do so at runtime.
61
62 To add an observer for a property, call:
63
64 ```javascript
65 object.addObserver('propertyKey', targetObject, targetAction)
66 ```
67
68 This will call the `targetAction` method on the `targetObject` whenever
69 the value of the `propertyKey` changes.
70
71 Note that if `propertyKey` is a computed property, the observer will be
72 called when any of the property dependencies are changed, even if the
73 resulting value of the computed property is unchanged. This is necessary
74 because computed properties are not computed until `get` is called.
75
76 @class Observable
77 @public
78 */
79 interface Observable {
80 /**
81 Retrieves the value of a property from the object.
82
83 This method is usually similar to using `object[keyName]` or `object.keyName`,
84 however it supports both computed properties and the unknownProperty
85 handler.
86
87 Because `get` unifies the syntax for accessing all these kinds
88 of properties, it can make many refactorings easier, such as replacing a
89 simple property with a computed property, or vice versa.
90
91 ### Computed Properties
92
93 Computed properties are methods defined with the `property` modifier
94 declared at the end, such as:
95
96 ```javascript
97 import { computed } from '@ember/object';
98
99 fullName: computed('firstName', 'lastName', function() {
100 return this.get('firstName') + ' ' + this.get('lastName');
101 })
102 ```
103
104 When you call `get` on a computed property, the function will be
105 called and the return value will be returned instead of the function
106 itself.
107
108 ### Unknown Properties
109
110 Likewise, if you try to call `get` on a property whose value is
111 `undefined`, the `unknownProperty()` method will be called on the object.
112 If this method returns any value other than `undefined`, it will be returned
113 instead. This allows you to implement "virtual" properties that are
114 not defined upfront.
115
116 @method get
117 @param {String} keyName The property to retrieve
118 @return {Object} The property value or undefined.
119 @public
120 */
121 get<K extends keyof this>(key: K): this[K];
122 get(key: string): unknown;
123 /**
124 To get the values of multiple properties at once, call `getProperties`
125 with a list of strings or an array:
126
127 ```javascript
128 record.getProperties('firstName', 'lastName', 'zipCode');
129 // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
130 ```
131
132 is equivalent to:
133
134 ```javascript
135 record.getProperties(['firstName', 'lastName', 'zipCode']);
136 // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
137 ```
138
139 @method getProperties
140 @param {String...|Array} list of keys to get
141 @return {Object}
142 @public
143 */
144 getProperties<L extends Array<keyof this>>(
145 list: L
146 ): {
147 [Key in L[number]]: this[Key];
148 };
149 getProperties<L extends Array<keyof this>>(
150 ...list: L
151 ): {
152 [Key in L[number]]: this[Key];
153 };
154 getProperties<L extends string[]>(
155 list: L
156 ): {
157 [Key in L[number]]: unknown;
158 };
159 getProperties<L extends string[]>(
160 ...list: L
161 ): {
162 [Key in L[number]]: unknown;
163 };
164 /**
165 Sets the provided key or path to the value.
166
167 ```javascript
168 record.set("key", value);
169 ```
170
171 This method is generally very similar to calling `object["key"] = value` or
172 `object.key = value`, except that it provides support for computed
173 properties, the `setUnknownProperty()` method and property observers.
174
175 ### Computed Properties
176
177 If you try to set a value on a key that has a computed property handler
178 defined (see the `get()` method for an example), then `set()` will call
179 that method, passing both the value and key instead of simply changing
180 the value itself. This is useful for those times when you need to
181 implement a property that is composed of one or more member
182 properties.
183
184 ### Unknown Properties
185
186 If you try to set a value on a key that is undefined in the target
187 object, then the `setUnknownProperty()` handler will be called instead. This
188 gives you an opportunity to implement complex "virtual" properties that
189 are not predefined on the object. If `setUnknownProperty()` returns
190 undefined, then `set()` will simply set the value on the object.
191
192 ### Property Observers
193
194 In addition to changing the property, `set()` will also register a property
195 change with the object. Unless you have placed this call inside of a
196 `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
197 (i.e. observer methods declared on the same object), will be called
198 immediately. Any "remote" observers (i.e. observer methods declared on
199 another object) will be placed in a queue and called at a later time in a
200 coalesced manner.
201
202 @method set
203 @param {String} keyName The property to set
204 @param {Object} value The value to set or `null`.
205 @return {Object} The passed value
206 @public
207 */
208 set<K extends keyof this, T extends this[K]>(key: K, value: T): T;
209 set<T>(key: string, value: T): T;
210 /**
211 Sets a list of properties at once. These properties are set inside
212 a single `beginPropertyChanges` and `endPropertyChanges` batch, so
213 observers will be buffered.
214
215 ```javascript
216 record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
217 ```
218
219 @method setProperties
220 @param {Object} hash the hash of keys and values to set
221 @return {Object} The passed in hash
222 @public
223 */
224 setProperties<
225 K extends keyof this,
226 P extends {
227 [Key in K]: this[Key];
228 }
229 >(
230 hash: P
231 ): P;
232 setProperties<T extends Record<string, unknown>>(hash: T): T;
233 /**
234 Convenience method to call `propertyWillChange` and `propertyDidChange` in
235 succession.
236
237 Notify the observer system that a property has just changed.
238
239 Sometimes you need to change a value directly or indirectly without
240 actually calling `get()` or `set()` on it. In this case, you can use this
241 method instead. Calling this method will notify all observers that the
242 property has potentially changed value.
243
244 @method notifyPropertyChange
245 @param {String} keyName The property key to be notified about.
246 @return {Observable}
247 @public
248 */
249 notifyPropertyChange(keyName: string): this;
250 /**
251 Adds an observer on a property.
252
253 This is the core method used to register an observer for a property.
254
255 Once you call this method, any time the key's value is set, your observer
256 will be notified. Note that the observers are triggered any time the
257 value is set, regardless of whether it has actually changed. Your
258 observer should be prepared to handle that.
259
260 There are two common invocation patterns for `.addObserver()`:
261
262 - Passing two arguments:
263 - the name of the property to observe (as a string)
264 - the function to invoke (an actual function)
265 - Passing three arguments:
266 - the name of the property to observe (as a string)
267 - the target object (will be used to look up and invoke a
268 function on)
269 - the name of the function to invoke on the target object
270 (as a string).
271
272 ```app/components/my-component.js
273 import Component from '@ember/component';
274
275 export default Component.extend({
276 init() {
277 this._super(...arguments);
278
279 // the following are equivalent:
280
281 // using three arguments
282 this.addObserver('foo', this, 'fooDidChange');
283
284 // using two arguments
285 this.addObserver('foo', (...args) => {
286 this.fooDidChange(...args);
287 });
288 },
289
290 fooDidChange() {
291 // your custom logic code
292 }
293 });
294 ```
295
296 ### Observer Methods
297
298 Observer methods have the following signature:
299
300 ```app/components/my-component.js
301 import Component from '@ember/component';
302
303 export default Component.extend({
304 init() {
305 this._super(...arguments);
306 this.addObserver('foo', this, 'fooDidChange');
307 },
308
309 fooDidChange(sender, key, value, rev) {
310 // your code
311 }
312 });
313 ```
314
315 The `sender` is the object that changed. The `key` is the property that
316 changes. The `value` property is currently reserved and unused. The `rev`
317 is the last property revision of the object when it changed, which you can
318 use to detect if the key value has really changed or not.
319
320 Usually you will not need the value or revision parameters at
321 the end. In this case, it is common to write observer methods that take
322 only a sender and key value as parameters or, if you aren't interested in
323 any of these values, to write an observer that has no parameters at all.
324
325 @method addObserver
326 @param {String} key The key to observe
327 @param {Object} target The target object to invoke
328 @param {String|Function} method The method to invoke
329 @param {Boolean} sync Whether the observer is sync or not
330 @return {Observable}
331 @public
332 */
333 addObserver<Target>(
334 key: keyof this,
335 target: Target,
336 method: ObserverMethod<Target, this>
337 ): this;
338 addObserver(key: keyof this, method: ObserverMethod<this, this>): this;
339 /**
340 Remove an observer you have previously registered on this object. Pass
341 the same key, target, and method you passed to `addObserver()` and your
342 target will no longer receive notifications.
343
344 @method removeObserver
345 @param {String} key The key to observe
346 @param {Object} target The target object to invoke
347 @param {String|Function} method The method to invoke
348 @param {Boolean} sync Whether the observer is async or not
349 @return {Observable}
350 @public
351 */
352 removeObserver<Target>(
353 key: keyof this,
354 target: Target,
355 method: ObserverMethod<Target, this>
356 ): this;
357 removeObserver(key: keyof this, method: ObserverMethod<this, this>): this;
358 /**
359 Set the value of a property to the current value plus some amount.
360
361 ```javascript
362 person.incrementProperty('age');
363 team.incrementProperty('score', 2);
364 ```
365
366 @method incrementProperty
367 @param {String} keyName The name of the property to increment
368 @param {Number} increment The amount to increment by. Defaults to 1
369 @return {Number} The new property value
370 @public
371 */
372 incrementProperty(keyName: keyof this, increment?: number): number;
373 /**
374 Set the value of a property to the current value minus some amount.
375
376 ```javascript
377 player.decrementProperty('lives');
378 orc.decrementProperty('health', 5);
379 ```
380
381 @method decrementProperty
382 @param {String} keyName The name of the property to decrement
383 @param {Number} decrement The amount to decrement by. Defaults to 1
384 @return {Number} The new property value
385 @public
386 */
387 decrementProperty(keyName: keyof this, decrement?: number): number;
388 /**
389 Set the value of a boolean property to the opposite of its
390 current value.
391
392 ```javascript
393 starship.toggleProperty('warpDriveEngaged');
394 ```
395
396 @method toggleProperty
397 @param {String} keyName The name of the property to toggle
398 @return {Boolean} The new property value
399 @public
400 */
401 toggleProperty(keyName: keyof this): boolean;
402 /**
403 Returns the cached value of a computed property, if it exists.
404 This allows you to inspect the value of a computed property
405 without accidentally invoking it if it is intended to be
406 generated lazily.
407
408 @method cacheFor
409 @param {String} keyName
410 @return {Object} The cached value of the computed property, if any
411 @public
412 */
413 cacheFor<K extends keyof this>(key: K): unknown;
414 }
415 const Observable: Mixin;
416 export default Observable;
417}
418
\No newline at end of file