How to have multiple keys referencing one value in AngularJS - javascript

I have the following AngularJS value provider :
myApp.value('myValueProvider',
{
'k1': 'v1',
'k2': 'v2',
'k3': 'v2'
});
Where 'v2' is a complex Object so I don't want to duplicate every v2 values from k2 to k3.
So my goal is to obtain something like this:
myApp.value('myValueProvider',
{
'k1': 'v1',
'k2': 'v2',
'k3': myValueProvider.k2
});
Do you know if it's possible?

How about something like this:
// create the complex object that you want to assign to multiple properties
var complexObj = {
... your complex obj props ...
};
// create the provider object and assign the complex object reference to multiple properties
var valueProviderObj = {
k1: 'v1',
k2: complexObj,
k3: complexObj
};
// associate the provider object to the angular value by key name
myApp.value('myValueProvider', valueProviderObj);

You can change it in run stage.
var myApp = angular.module('myApp', []);
myApp.value('myValueProvider',
{
'k1': 'v1',
'k2': 'v2'
});
myApp.run(function(myValueProvider) {
myValueProvider.k3 = myValueProvider.k2;
});
myApp.controller('ctrl', function(myValueProvider) {
console.log('from controller', myValueProvider);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl"></div>

Related

How to set contents into the World object in Before in cucumber.js?

How to put contents into the World object before a scenario (e.g. in Before()) in cucumber.js?
I want to inject a test context object and inject some initial values into it before the first step of the scenario.
But in the first step, this is not referring to the World object.
How can I access the World object in Before()?
const { Given, Before } = require('cucumber');
Before((scenario) => {
const world = this;
world.put = 'hello';
world.myContext = {
fileName: null,
fileContent: null,
};
});
Given( /^step 1$/, { 90000 },
async function() {
const world = this;
console.log('step 1: world: ', world);
console.log('step 1: world.myContext: ', world.myContext);
});
As per this document, world can be accessed as 'this' in only world and hooks file- https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/world.md. Also, 'this' will be a json object which expects key-value.
Create a seperate class called world.ts and add sample code like below:
import { setWorldConstructor } from 'cucumber';
const _defaultOptions = {
env: 'stage',
};
function World(input) {
this.World = input;
}
Object.assign(this, _defaultOptions);
}
setWorldConstructor(World);
Using this, if we call this.env in hooks file, you will get a value mapped above..

Why is the data in the service not updated from the directive?

With the following setup I try to update data that is being held in a service and shared with the controller to assign it to the view.
In the example you can see 2 variables. One containing an array, another just a string.
What I don't understand is why is the array updated and consumed in the view and the string is not?!
JavaScript:
function fooService() {
var mystring = 'old string';
var myarray = [];
var updateArray = function(data) {
myarray.push(data);
};
var updateString = function(data) {
mystring = data;
};
return {
myarray: myarray,
mystring: mystring,
updateString: updateString,
updateArray: updateArray
}
}
function MainCtrl($scope, fooService) {
this.myarray = fooService.myarray;
this.mystring = fooService.mystring;
}
function fooDirective(fooService) {
function link(scope) {
fooService.updateArray(scope.vm.name);
fooService.updateString('new string');
}
return {
restrict: 'EA',
replace: true,
template: '<h2 style="color: {{vm.color}};">{{vm.name}}</h2>',
scope: {},
controller: 'MainCtrl',
controllerAs: 'vm',
bindToController: {
name: '#',
color: '#'
},
link: link
};
}
angular
.module('app', [])
.service('fooService', fooService)
.controller('MainCtrl', MainCtrl)
.directive('fooDirective', fooDirective);
HTML:
<div ng-app="app">
<div ng-controller="MainCtrl as vm">
{{vm.myarray}}
{{vm.mystring}}
<foo-directive data-name="Markus" data-color="red"></foo-directive>
<foo-directive data-name="Nemanja" data-color="green"></foo-directive>
<foo-directive data-name="Luke" data-color="blue"></foo-directive>
</div>
</div>
It might just be that I understand it the wrong way but services should hold data that is shared across the app right?
Here is the working example: http://jsfiddle.net/markus_falk/f00y3tL3/6/
services should hold data that is shared across the app right?
This is correct, however when you do
return {
myarray: myarray,
mystring: mystring,
// ...
}
you return new object (that will be your service instance) that has reference to myarray and copy of mystring. So since there is reference to myarray (all objects are passed as a reference) it updates in the service just fine. However, it will not for with the string (primitive types are not-mutable, passed as values), because service returns just a copy of it.
Instead of modifying a string (primitive value) use getter/setter approach.

How to create a nested $resource - AngularJS

I'm trying to figure out how to create a nested $resource. I have come up with the following, which seems to work..ish, but seems awfully wrong. Hoping someone can point me in the right direction.
My data structure is:
allData = [{
id:1,
info:'someinfo',
foo :'foobar',
bar : {...},
baz : [{...}, {...}, {...}]
},
{id:2, ...},
{id:3, ...}
];
I would like each object in allData to be a $resource object. I also want each object in the baz array to be a $resource object as well.
What I have come up with is:
var InfoService = angular.module('InfoServices', ['ngResource']);
// The inner resource
InfoService.factory('Baz', ['$resource',
function($resource) {
var baz = $resource('/server/baz', {});
// custom baz methods
baz.prototype.getBaz = function() {};
return baz;
}]);
// Outer resource
InfoService.factory('Info', ['$resource',
function($resource) {
var info = $resource('/server/info', {});
// custom data methods
info.prototype.getInfoStats = function() {};
return info;
}]);
// Array of resources.
InfoService.factory('AllInfo', ['$resource','Info', 'Baz',
function($resource,Info,Baz) {
var resource = $resource('/server/allinfo', {},
{ query : {
method:'get',
isArray:true,
transformResponse:function(data) {
var allinfo = JSON.parse(data);
for (var i=0;i<allinfo.length;i++) {
allinfo[i] = new Info(allinfo[i]);
for (var j=0;j<allinfo[i].baz.length;j++) {
allinfo[i].baz[j] = new Baz(allinfo[i].baz[j]);
}
}
return allinfo;
}
});
return resource;
}]);
Like I said..seems awfully wrong, what's the Angular way to achieving the above?
InfoService.factory('resources', function($resource) {
return {
id: $resource(...),
info: $resource(...),
//...and so on...
}
})
//example usage
InfoService.controller('ctrl', function(resources) {
this.id = resources.id.query();
})
If you want to make everything a resource, this is the way to go. But ask yourself...is it really necessary to make every attribute a separate resource? What about grouping in a resource all attributes that are strictly related? I ask because I am not sure of what these data rapresent to you.

Find an object in a list based on attribute value in Angular JS

Is there a simple way to find an object in a list based on an attribute value, without looping on the list?
For example given a list like the following :
var lst = [
{
name: "foo",
value: "fooValue"
},
{
name: "bar",
value: "barValue"
}
];
Is there some kind of "find" method, such that lst.find("name", "foo") would return the object which has a "name" attribute with the value "foo"?
You can use the $filter service:
angular.module('app', [])
function ParentCtrl($scope, $filter){
var lst = [{name : "foo", value : "fooValue"}, {name: "bar", value: "barValue"}, { name: 'foo', value: 'something else'}];
var newTemp = $filter("filter")(lst, {name:'foo'});
console.log(newTemp);
}
jsFiddle: http://jsfiddle.net/W2Z86/
If you want a strict comparison, you need to set the comparator (3rd argument) to true like the following (based on Mathew's answer):
var newTemp = $filter("filter")(lst, {name:'foo'}, true);
See the doc on $filter on AngularJS site: https://docs.angularjs.org/api/ng/filter/filter

Nested Models in Backbone.js, how to approach

I've got the following JSON provided from a server. With this, I want to create a model with a nested model. I am unsure of which is the way to achieve this.
//json
[{
name : "example",
layout : {
x : 100,
y : 100,
}
}]
I want these to be converted to two nested backbone models with the following structure:
// structure
Image
Layout
...
So I define the Layout model like so:
var Layout = Backbone.Model.extend({});
But which of the two (if any) techniques below should I use to define the Image model? A or B below?
A
var Image = Backbone.Model.extend({
initialize: function() {
this.set({ 'layout' : new Layout(this.get('layout')) })
}
});
or, B
var Image = Backbone.Model.extend({
initialize: function() {
this.layout = new Layout( this.get('layout') );
}
});
I have the very same issue while I'm writing my Backbone application. Having to deal with embedded/nested models. I did some tweaks that I thought was a quite elegant solution.
Yes, you can modify the parse method to change a attributes around in the object, but all of that is actually pretty unmaintainable code IMO, and feels more of a hack than a solution.
Here's what I suggest for your example:
First define your Layout Model like so.
var layoutModel = Backbone.Model.extend({});
Then here's your image Model:
var imageModel = Backbone.Model.extend({
model: {
layout: layoutModel,
},
parse: function(response){
for(var key in this.model)
{
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {parse:true});
}
return response;
}
});
Notice that I have not tampered with the model itself, but merely pass back the desired object from the parse method.
This should ensure the structure of the nested model when you're reading from the server. Now, you would notice that saving or setting is actually not handled here because I feel that it makes sense for you to set the nested model explicitly using the proper model.
Like so:
image.set({layout : new Layout({x: 100, y: 100})})
Also take note that you are actually invoking the parse method in your nested model by calling:
new embeddedClass(embeddedData, {parse:true});
You can define as many nested models in the model field as you need.
Of course, if you want to go as far as saving the nested model in its own table. This wouldn't be sufficient. But in the case of reading and saving the object as a whole, this solution should suffice.
I'm posting this code as an example of Peter Lyon's suggestion to redefine parse. I had the same question and this worked for me (with a Rails backend). This code is written in Coffeescript. I made a few things explicit for people unfamiliar with it.
class AppName.Collections.PostsCollection extends Backbone.Collection
model: AppName.Models.Post
url: '/posts'
...
# parse: redefined to allow for nested models
parse: (response) -> # function definition
# convert each comment attribute into a CommentsCollection
if _.isArray response
_.each response, (obj) ->
obj.comments = new AppName.Collections.CommentsCollection obj.comments
else
response.comments = new AppName.Collections.CommentsCollection response.comments
return response
or, in JS
parse: function(response) {
if (_.isArray(response)) {
return _.each(response, function(obj) {
return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
});
} else {
response.comments = new AppName.Collections.CommentsCollection(response.comments);
}
return response;
};
Use Backbone.AssociatedModel from Backbone-associations :
var Layout = Backbone.AssociatedModel.extend({
defaults : {
x : 0,
y : 0
}
});
var Image = Backbone.AssociatedModel.extend({
relations : [
type: Backbone.One,
key : 'layout',
relatedModel : Layout
],
defaults : {
name : '',
layout : null
}
});
I'm not sure Backbone itself has a recommended way to do this. Does the Layout object have its own ID and record in the back end database? If so you can make it its own Model as you have. If not, you can just leave it as a nested document, just make sure you convert it to and from JSON properly in the save and parse methods. If you do end up taking an approach like this, I think your A example is more consistent with backbone since set will properly update attributes, but again I'm not sure what Backbone does with nested models by default. It's likely you'll need some custom code to handle this.
I'd go with Option B if you want to keep things simple.
Another good option would be to use Backbone-Relational. You'd just define something like:
var Image = Backbone.Model.extend({
relations: [
{
type: Backbone.HasOne,
key: 'layout',
relatedModel: 'Layout'
}
]
});
I use Backbone DeepModel plugin for nested models and attributes.
https://github.com/powmedia/backbone-deep-model
You can bind to change events 'n levels deep. for example:
model.on('change:example.nestedmodel.attribute', this.myFunction);
CoffeeScript version of rycfung's beautiful answer:
class ImageModel extends Backbone.Model
model: {
layout: LayoutModel
}
parse: (response) =>
for propName,propModel of #model
response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )
return response
Ain't that sweet? ;)
I had the same issue and I've been experimenting with the code in rycfung's answer, which is a great suggestion.
If, however, you do not want to set the nested models directly, or do not want to constantly
pass {parse: true} in the options, another approach would be to redefine set itself.
In Backbone 1.0.0, set is called in constructor, unset, clear, fetch and save.
Consider the following super model, for all models that need to nest models and/or collections.
/** Compound supermodel */
var CompoundModel = Backbone.Model.extend({
/** Override with: key = attribute, value = Model / Collection */
model: {},
/** Override default setter, to create nested models. */
set: function(key, val, options) {
var attrs, prev;
if (key == null) { return this; }
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
// Run validation.
if (options) { options.validate = true; }
else { options = { validate: true }; }
// For each `set` attribute, apply the respective nested model.
if (!options.unset) {
for (key in attrs) {
if (key in this.model) {
if (!(attrs[key] instanceof this.model[key])) {
attrs[key] = new this.model[key](attrs[key]);
}
}
}
}
Backbone.Model.prototype.set.call(this, attrs, options);
if (!(attrs = this.changedAttributes())) { return this; }
// Bind new nested models and unbind previous nested models.
for (key in attrs) {
if (key in this.model) {
if (prev = this.previous(key)) {
this._unsetModel(key, prev);
}
if (!options.unset) {
this._setModel(key, attrs[key]);
}
}
}
return this;
},
/** Callback for `set` nested models.
* Receives:
* (String) key: the key on which the model is `set`.
* (Object) model: the `set` nested model.
*/
_setModel: function (key, model) {},
/** Callback for `unset` nested models.
* Receives:
* (String) key: the key on which the model is `unset`.
* (Object) model: the `unset` nested model.
*/
_unsetModel: function (key, model) {}
});
Notice that model, _setModel and _unsetModel are left blank on purpose. At this level of abstraction you probably can't define any reasonable actions for the callbacks. However, you may want to override them in the submodels that extend CompoundModel.
Those callbacks are useful, for instance, to bind listeners and propagate change events.
Example:
var Layout = Backbone.Model.extend({});
var Image = CompoundModel.extend({
defaults: function () {
return {
name: "example",
layout: { x: 0, y: 0 }
};
},
/** We need to override this, to define the nested model. */
model: { layout: Layout },
initialize: function () {
_.bindAll(this, "_propagateChange");
},
/** Callback to propagate "change" events. */
_propagateChange: function () {
this.trigger("change:layout", this, this.get("layout"), null);
this.trigger("change", this, null);
},
/** We override this callback to bind the listener.
* This is called when a Layout is set.
*/
_setModel: function (key, model) {
if (key !== "layout") { return false; }
this.listenTo(model, "change", this._propagateChange);
},
/** We override this callback to unbind the listener.
* This is called when a Layout is unset, or overwritten.
*/
_unsetModel: function (key, model) {
if (key !== "layout") { return false; }
this.stopListening();
}
});
With this, you have automatic nested model creation and event propagation. Sample usage is also provided and tested:
function logStringified (obj) {
console.log(JSON.stringify(obj));
}
// Create an image with the default attributes.
// Note that a Layout model is created too,
// since we have a default value for "layout".
var img = new Image();
logStringified(img);
// Log the image everytime a "change" is fired.
img.on("change", logStringified);
// Creates the nested model with the given attributes.
img.set("layout", { x: 100, y: 100 });
// Writing on the layout propagates "change" to the image.
// This makes the image also fire a "change", because of `_propagateChange`.
img.get("layout").set("x", 50);
// You may also set model instances yourself.
img.set("layout", new Layout({ x: 100, y: 100 }));
Output:
{"name":"example","layout":{"x":0,"y":0}}
{"name":"example","layout":{"x":100,"y":100}}
{"name":"example","layout":{"x":50,"y":100}}
{"name":"example","layout":{"x":100,"y":100}}
I realize I'm late to this party, but we recently released a plugin to deal with exactly this scenario. It's called backbone-nestify.
So your nested model remains unchanged:
var Layout = Backbone.Model.extend({...});
Then use the plugin when defining the containing model (using Underscore.extend):
var spec = {
layout: Layout
};
var Image = Backbone.Model.extend(_.extend({
// ...
}, nestify(spec));
After that, assuming you have a model m which is an instance of Image, and you've set the JSON from the question on m, you can do:
m.get("layout"); //returns the nested instance of Layout
m.get("layout|x"); //returns 100
m.set("layout|x", 50);
m.get("layout|x"); //returns 50
Use backbone-forms
It supports nested forms, models and toJSON. ALL NESTED
var Address = Backbone.Model.extend({
schema: {
street: 'Text'
},
defaults: {
street: "Arteaga"
}
});
var User = Backbone.Model.extend({
schema: {
title: { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
name: 'Text',
email: { validators: ['required', 'email'] },
birthday: 'Date',
password: 'Password',
address: { type: 'NestedModel', model: Address },
notes: { type: 'List', itemType: 'Text' }
},
constructor: function(){
Backbone.Model.apply(this, arguments);
},
defaults: {
email: "x#x.com"
}
});
var user = new User();
user.set({address: {street: "my other street"}});
console.log(user.toJSON()["address"]["street"])
//=> my other street
var form = new Backbone.Form({
model: user
}).render();
$('body').append(form.el);
If you don't want to add yet another framework, you might consider creating a base class with overridden set and toJSON and use it like this:
// Declaration
window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
nestedTypes: {
background: window.app.viewer.Model.Image,
images: window.app.viewer.Collection.MediaCollection
}
});
// Usage
var gallery = new window.app.viewer.Model.GallerySection({
background: { url: 'http://example.com/example.jpg' },
images: [
{ url: 'http://example.com/1.jpg' },
{ url: 'http://example.com/2.jpg' },
{ url: 'http://example.com/3.jpg' }
],
title: 'Wow'
}); // (fetch will work equally well)
console.log(gallery.get('background')); // window.app.viewer.Model.Image
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
console.log(gallery.get('title')); // plain string
You'll need BaseModel from this answer (available, if you fancy, as a gist).
We have this problem too and a team worker has implemented a plugin named backbone-nested-attributes.
The usage is very simple. Example:
var Tree = Backbone.Model.extend({
relations: [
{
key: 'fruits',
relatedModel: function () { return Fruit }
}
]
})
var Fruit = Backbone.Model.extend({
})
With this, the Tree model can access then fruits:
tree.get('fruits')
You can see more informations here:
https://github.com/dtmtec/backbone-nested-attributes

Categories