Angular services with default values for non-existing attributes - javascript

Working on an Ionic application that performs both in Android and Windows.
There are services, such as Ionic's $ionicLoading, which we override functionality in order to work properly in windows:
angular.factory('$ionicLoading', function(){
return {
show: function (){...} // custom implementation
hide: function (){...} // custom implementation
}
});
But there are other services which we have to override only to not break the app.
In this cases it would be really useful to provide a service that won't do anything. For example:
angular.factory('$ionicExampleService', function(){
return {
*foo*: angular.noop // for operations
*bar*: promise // returns promise
}
});
Note: I know that a better way of doing this would be with a service that chooses between Ionic's implementation or a made one, but this is just for the sake of learning.
The ideal would be going even further, it would be magnificent to be able to return something even more bulletproof. Something like a generic flexible services:
angular.factory('$ionicPopup', function(){
return /*magic*/;
});
$ionicPopup.show({...}) // show was not defined
.then(foo); // won't break and will execute foo()
It is possible?

From what I understood you need to override implementation of existing services. You can do that with an angular service decorator.
A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
For more information you can check angular documentation. One simple example would be:
app.factory('someService', function () {
return {
method1: function () { return '1'; }
method2: function () { return '2'; }
};
});
app.decorator('someService', function ($delegate) {
// NOTE: $delegate is the original service
// override method2
$delegate.method2 = function () { return '^2'; };
// add new method
$delegate.method3 = function () { return '3'; };
return $delegate;
});
// usage
app.controller('SomeController', function(someService) {
console.log(someService.method1());
console.log(someService.method2());
console.log(someService.method3());
});
EDIT: Question - How to override every method in the service?
var dummyMethod = angular.noop;
for(var prop in $delegate) {
if (angular.isFunction($delegate[prop])) {
$delegate[prop] = dummyMethod;
}
}
I hope that this helps you.

Using an evaluation for each assignment based on an object property, similar to this:
myVar = myObj.myPropVar === undefined ? "default replacement" : myObj.myPropVar;
Basically you're using a check for if the property has been defined, substituting a default value if it hasn't, and assigning it if it has.
Alternatively, you can use a modified version of the global function in Sunny's linkback to define defaults for all those properties you might assume to be undefined at specific points in your code.
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else if(prop == "foo") return "default value for foo";
else if(prop == "bar") return "default value for bar";
/* etc */
else return "default for missing prop";
}
Hope that helps,
C§

use var a = {}; to declare new variable.

Related

Nested JS decorator get/set's, how to properly chain them?

The ember framework has adopted decorators aggressively. In order to utilize data binding now i have to decorate my properties with #tracked which gets me all my nice UI updates anytime i change a property.
#tracked username = 'dave';
This works well, but i'm encountering some serious problems if i need to add a custom decorator on top of the tracked decorator.
#typed(StateTrackMap)
#tracked
mapConfigsArray = [create(StateTrackMap)];
I'm able to get this to work by having my #typed decorator check to see if it is above another decorator or not.
export default function typed(classType) {
let weak = new WeakMap();
return function(object, property, descriptor) {
return {
get() {
// Check if there is another decorator attached below us in the chain
// i.e. "tracked"
if (typeof descriptor.get == 'function') {
return descriptor.get.call(this);
}
// If we haven't initialized before but there is one ready, return that
if (!weak.has(this) && typeof descriptor.initializer == 'function') {
weak.set(this, descriptor.initializer.call(this));
}
return weak.get(this);
},
set(value) {
// my set code which does the type checking/converting this descriptor is for
// Apply the converted value to the lower level object
// This may be the object itself, or it may be another setter in the chain
if (typeof descriptor.set == 'function') {
descriptor.set.call(this, typedValue);
} else {
return weak.set(this, typedValue);
}
}
}
}
}
But this feels, weird... and doesn't look like any of the usages of descriptors i've seen.
Mostly because if i change the order of the decorators things explode
#tracked
#typed(StateTrackMap)
mapConfigsArray = [create(StateTrackMap)];
index.js:172 Uncaught Error: Assertion Failed: The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both.
So i guess my question is, what is the proper way to chain decorators that have get & set? It seems to me that the order of the decorators determines if i can go up/down the chain or not. Also it seems to me that this chaining logic has to be baked into every decorator or else it doesn't work. Is there some generic way i can pass decorators to other decorators?
I've seen some examples where i return the descriptor reference but that doesn't appear to help the problem here either as i am not quite sure how i can still inject my get/set on it without erasing the property property chain or getting into the same boat as above where my code has to be designed to work with other descriptors specifically.
export default function typed(classType) {
return function(object, property, descriptor) {
const set = descriptor.set;
const get = descriptor.get;
const weak = new WeakMap();
descriptor.get = function() {
if (typeof get == 'function') {
return get.call(this);
}
// If we haven't initialized before but there is one ready, return that
if (!weak.has(this) && typeof descriptor.initializer == 'function') {
weak.set(this, descriptor.initializer.call(this));
}
return weak.get(this);
}
descriptor.set = function(value) {
// My type checking / conversion code
// Apply the converted value to the lower level object
// This may be the object itself, or it may be another setter in the chain
if (typeof set == 'function') {
set.call(this, typedValue);
} else {
return weak.set(this, typedValue);
}
}
return descriptor;
}
}
BTW this method gives a different explosion.
Assertion Failed: You attempted to use #tracked on mapConfigsArray, but that element is not a class field.

How to mock a function in Karma.js

Does somebody knows how to mock a function result with karma.js ?
The function uses a var given by thymeleaf framework (java, spring boot, etc..).
function isFlooring() {
var isMyChoiceOk = [[${mychoice}]];
if(typeof isMyChoiceOk !== 'undefined') {
return isMyChoiceOk;
}
else {
return false;
}
}
What I want to do is to tell karma.js that the result of this function is TRUE or FALSE.
You can create a spy on the function (this assumes your function isn't part of an object) and then replace it with your own function:
spyOn(window, 'isFlooring')
.and.callFake( function(arguments) {
// return whatever you want to here
return true
}
The spy just listens for that function to be called and then 'callFake' replaces the functionality with what you want it to be.
If your function is part of an object, replace 'window' in the 'spyOn' call with the name of the object.

Jasmine test for javascript getter not working

I'm writing some test for for an angularjs factory and some of the expectations are not working and I really don't know why.
This is my factory (part of it).
'use strict';
angular.module('myAppMod')
.factory('Person', function(BaseModel) {
return BaseModel.extend({
get fullname() {
var name = [];
if (this.first_name) {
name.push(this.first_name);
}
if (this.person_extra && this.person_extra.middle_name) {
name.push(this.person_extra.middle_name);
}
if (this.last_name) {
name.push(this.last_name);
}
return name.join(' ');
}
});
});
and Jasmine tests:
var p;
beforeEach(function() {
p = new Person({
first_name: 'first_name',
person_extra: {
middle_name: 'middle_name',
media_item_id: null
},
last_name: 'last_name',
security_level: 'security_level'
}, true);
});
it("has a fullname", function() {
expect(p.fullname).toEqual('first_name middle_name last_name');
});
p.fullnameis returning ""(empty string) and in the factory, console.log(this.first_name), is undefined.
Any help is really appreciated.
Thank you in advance
EDIT: After further investigation, I have changed my answer.
It is not working because you are using the getter shorthand (get fnName() { }) through the extend method. The getter's this is the anonymous object itself and does not inherit the methods and properties of the Backbone model, whereas the this in function properties do. I have made a codepen that illustrate your problem.
That is, if this is your Model
var Model = BaseModel.extend({
get isBackboneModelThroughGetter() {
return !!this.get;
},
isBackboneModel: function() {
return !!this.get;
},
});
Then an instance of Model will make this test pass:
it('should make you wonder', function() {
var model = new Model();
expect(model.isBackboneModel()).toBe(true);
expect(model.isBackboneModelThroughGetter).not.toBe(true);
});
Thus, to make your Person factory work, you will need:
To replace every property access by the proper Backbone call: this.get('propertyName') instead of this.propertyName
Replace all getters by function properties: full_name : function() { /*...*/ } instead of get full_name() { /* ... */ }
Replace calls to model.full_name by model.full_name();
I assume that you're using the built-in angular.extend. angular.extend does not copy getters and setters. There's been an open issue on GitHub on this specific subject since the 12th of August 2014.
As for why it still isn't implemented:
Angular exposes some of the helper functions that it uses internally. This is the case for extend, copy and many others. There are other libraries that specialize in these functions, keep their
focus is there and can do a better job.
It is not in the best interest of most users to make these helper functions big nor slow, as these are used internally and any change in that direction can have a direct impact in download size and performance. At the same time, apps that need the most accurate version, should be better served with other libraries.
There are many ways to solve this issue. decaf.js provides an example implementation that should work for most cases. GitHub is probably a better environment to dive into their code, but it comes down to this:
function extend (me) {
var args = Array.prototype.slice.call(arguments, 1);
decaf.each(args, function (o) {
for (var key in o) {
if (o.hasOwnProperty(key)) {
var desc = Object.getOwnPropertyDescriptor(o, key);
var g = desc.get;
var s = desc.set;
if (g || s) {
Object.defineProperty(me, key, { get: g, set: s, enumerable: true });
} else {
me[key] = o[key];
}
}
}
});
return me;
}

Override jQuery functions simply by extending?

This is related to, but not a duplicate of, another SO Q&A Override jQuery functions.
It is clear from the answer to the above question that the pattern to override a jQuery function is:
(function($){
// store original reference to the method
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
})(jQuery);
But why!?
I've been able to override a jQuery function simply by defining a function of the same name as the function to be overridden, within $.extend or $.fn.extend.
Consider this:
// random example showing jquery function overriding
$.fn.extend({
hide: function() {
$(this).css({"color":"red"});
}
});
$("#test").hide(); // this will actually paint the #test element red!
jsFiddle
I'd like to understand why _old.apply(this,arguments) would be the preferred way to override a jQuery function, as listed here and here.
From glancing at references provided at original post, summary of pattern could be to keep both "old" and "new" methods available ?
Edit, updated
Sorry, I don't get this. As far as I see, the reference to the overridden method is saved in a local variable in a closure is
unquestionably lost outside the closure. Can you explain how the "old"
method is still available? –SNag
I'd like to understand why _old.apply(this,arguments) would be the
preferred way to override a jQuery function, as listed here and
here.
Utilizing pattern at 1st link , above , if interpret pieces correctly, appear arguments test within if statement of jquery method within "self-executing anonymous function" determine return value of "old" or "new" (newly included; override) jquery method ?
i.e.g., try
html
<div>abc</div>
js
// See http://www.paulirish.com/2010/duck-punching-with-jquery/ , at
// `First we start off with a self-executing anonymous function,
// that makes a happy closure while remapping jQuery to $:`
// `closure` start
(function ($) {
// jquery `.css()`
var _oldcss = $.fn.css;
// jquery `.hide()`
var _oldhide = $.fn.hide;
// "new" `.css()`
$.fn.css = function (prop, value) {
// "new" `.css()` `test`
if (/^background-?color$/i.test(prop)
&& value.toLowerCase() === 'burnt sienna') {
return _oldcss.call(this, prop, '#EA7E5D');
} else {
return _oldcss.apply(this, arguments);
}
};
// "new" `.hide()`
$.fn.hide = function (prop, value) {
// "new" `.hide()` `test`
if (/color/i.test(prop) && /[a-f]|[0-9]/i.test(value)) {
return $.fn.css.call(this, prop, value);
} else {
return _oldhide.apply(this, arguments);
}
};
})(jQuery);
// `closure` stop
// and using it...
// "new" `.css()`
jQuery(document.body).css('backgroundColor', 'burnt sienna');
// "old" `.css()`
$("div").css("color", "yellow");
// "old" `.hide()`
$("div").hide(7500, function () {
// "old" `.css()`
$(document.body)
.css({
"transition": "background 2s",
"background": "#edd452"
})
.find($("div")).show(2500)
// "new" `.hide()`
.hide("color", "red")
});
jsfiddle http://jsfiddle.net/guest271314/5bEe4/
(function($){
// store original reference to the method
// stored locally
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
// call apply method, in order to pass the this context.
return _old.apply(this,arguments);
}
};
})(jQuery);
Here in the above code, we are calling an anonymous function, in which we are declaring a local variable _old. When this anonymous function execute, it save the _old method reference and form a closure.
Now, when we call the new method, i.e,
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
we also have an access to _old method, since its scope exists in the current context. And then, we can use it inside the new method.
Here we are calling _old method with the help of apply, because we want to have the same this context for that as well.
With this approach, we can easily override the jQuery method by preserving its original functionality.

Add methods to a collection returned from an angular resource query

I have a resource that returns an array from a query, like so:
.factory('Books', function($resource){
var Books = $resource('/authors/:authorId/books');
return Books;
})
Is it possible to add prototype methods to the array returned from this query? (Note, not to array.prototype).
For example, I'd like to add methods such as hasBookWithTitle(title) to the collection.
The suggestion from ricick is a good one, but if you want to actually have a method on the array that returns, you will have a harder time doing that. Basically what you need to do is create a bit of a wrapper around $resource and its instances. The problem you run into is this line of code from angular-resource.js:
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
This is where the return value from $resource is set up. What happens is "value" is populated and returned while the ajax request is being executed. When the ajax request is completed, the value is returned into "value" above, but by reference (using the angular.copy() method). Each element of the array (for a method like query()) will be an instance of the resource you are operating on.
So a way you could extend this functionality would be something like this (non-tested code, so will probably not work without some adjustments):
var myModule = angular.module('myModule', ['ngResource']);
myModule.factory('Book', function($resource) {
var service = $resource('/authors/:authorId/books'),
origQuery = service.prototype.$query;
service.prototype.$query = function (a1, a2, a3) {
var returnData = origQuery.call(this, a1, a2, a3);
returnData.myCustomMethod = function () {
// Create your custom method here...
return returnData;
}
}
return service;
});
Again, you will have to mess with it a bit, but that's the basic idea.
This is probably a good case for creating a custom service extending resource, and adding utility methods to it, rather than adding methods to the returned values from the default resource service.
var myModule = angular.module('myModule', []);
myModule.factory('Book', function() {
var service = $resource('/authors/:authorId/books');
service.hasBookWithTitle = function(books, title){
//blah blah return true false etc.
}
return service;
});
then
books = Book.list(function(){
//check in the on complete method
var hasBook = Book.hasBookWithTitle(books, 'someTitle');
})
Looking at the code in angular-resource.js (at least for the 1.0.x series) it doesn't appear that you can add in a callback for any sort of default behavior (and this seems like the correct design to me).
If you're just using the value in a single controller, you can pass in a callback whenever you invoke query on the resource:
var books = Book.query(function(data) {
data.hasBookWithTitle = function (title) { ... };
]);
Alternatively, you can create a service which decorates the Books resource, forwards all of the calls to get/query/save/etc., and decorates the array with your method. Example plunk here: http://plnkr.co/edit/NJkPcsuraxesyhxlJ8lg
app.factory("Books",
function ($resource) {
var self = this;
var resource = $resource("sample.json");
return {
get: function(id) { return resource.get(id); },
// implement whatever else you need, save, delete etc.
query: function() {
return resource.query(
function(data) { // success callback
data.hasBookWithTitle = function(title) {
for (var i = 0; i < data.length; i++) {
if (title === data[i].title) {
return true;
}
}
return false;
};
},
function(data, response) { /* optional error callback */}
);
}
};
}
);
Thirdly, and I think this is better but it depends on your requirements, you can just take the functional approach and put the hasBookWithTitle function on your controller, or if the logic needs to be shared, in a utilities service.

Categories

Resources