Integrating native JavaScript classes in an Angular app - javascript

I have a native JavaScript class:
var Holder = new function(elements) {
this.elements = elements;
this.anyFunction() {
// use of this.elements
};
};
How to use it in an Angular-way? For example, if I would like to use:
.controller('AnyController', ['Holder',
function (Holder) {
var elements = [
{id: 1, label: 'foo'},
{id: 2, label: 'bar'}
];
$scope.holder = new Holder(elements);
}])
How should I register my Holder class then? What are the options (if any)?
In parallel, is it that bad to use native JavaScript classes in an Angular app (i.e. without integrating it within the framework)?

You could return a class with a factory
.factory('Holder', function() {
return (function (){
this.foo = foo;
this.bar = bar;
});
});
Now to use it
.controller('AnyController', ['Holder', function (Holder) {
var holder = new Holder();
}]);
EDIT
Use a factory instead of a service, as suggested in the comments

As I understand it, a factory is a singleton, but a factory can generate a class that can create instances. So the factory would return a reference to the constructor when you inject it, or a wrapper function around the constructor to use it without using new:
.factory('Holder', function() {
function Holder(elements) {
this.elements = elements;
}
Holder.prototype.get = function() {
return this.elements;
};
return function(elements) {
return new Holder(elements);
};
})
.controller('Main', function($scope, Holder) {
var elements = [
{id: 1, label: 'foo'},
{id: 2, label: 'bar'}
];
$scope.elements = Holder(elements).get();
});

Related

How to properly share data between angular services

I'm attempting to rewrite a large and complex form that is doing everything in a controller. I started by separating related functions into their own modules/services. I don't understand how I am supposed to maintain the form data without crowding up the controller or requiring an excessive amount of arguments to be passed to the service function.
My current approach is to set variables on the service, then use that service in other services and try to access the saved data. This doesn't seem to be working. I think this is because injecting the service into another creates a new instance without all the saved values.
Here is a plunker that summarizes this approach: https://plnkr.co/edit/vyKtlXk8Swwf7xmoCJ4q
let app = angular.module('myApp', []);
app.service('productService', [function() {
let products = [
{ name: 'foo', value: 'foo' },
{ name: 'bar', value: 'bar' },
{ name: 'baz', value: 'baz' }
];
let selectedProduct = null;
this.getAvailableProducts = function() {
return products;
}
this.setSelectedProduct = function(product) {
selectedProduct = product;
}
}]);
app.service('storeService', ['productService', function(productService) {
let states = [
{ name: 'SC', value: 'SC' },
{ name: 'GA', value: 'GA' },
{ name: 'LA', value: 'LA' }
];
let selectedState = '';
this.getAvailableStates = function() {
return states;
}
this.setSelectedState = function(state) {
selectedState = state;
}
this.getPrice = function() {
// This console.log will always return undefined.
// productService.selectedProduct is not available.
console.log(productService.selectedProduct);
if (productService.selectedProduct == "foo" && selectedState == 'SC') {
return 10;
}
return 5;
}
}]);
app.controller('myController', function($scope, storeService, productService) {
$scope.name = '';
$scope.deliveryState = '';
$scope.selectedProduct = null;
$scope.price = 0;
$scope.productSelection = productService.getAvailableProducts();
$scope.states = storeService.getAvailableStates();
$scope.productChanged = function() {
productService.setSelectedProduct($scope.selectedProduct);
$scope.price = storeService.getPrice();
}
$scope.stateChanged = function() {
storeService.setSelectedState($scope.deliveryState);
$scope.price = storeService.getPrice();
}
});
I am trying to avoid something like this:
$scope.price = storeService.getPrice(
$scope.state,
$scope.selectedProduct,
$scope.servicePackage,
$scope.serviceFee,
$scope.shippingSelection,
// etc…
);
Should I be creating a third service that sets and gets all the data on the other services?
Should I just maintain all the data on the controller?
why do I get undefined when accessing a variable on the injected service?
The let declaration creates a private variable.
Add a getter for the variable:
app.service('productService', [function() {
let products = [
{ name: 'foo', value: 'foo' },
{ name: 'bar', value: 'bar' },
{ name: 'baz', value: 'baz' }
];
let selectedProduct = null;
this.getAvailableProducts = function() {
return products;
}
this.setSelectedProduct = function(product) {
selectedProduct = product;
}
//ADD getter
this.getSelectedProduct = function() {
return selectedProduct;
}
}]);
And use the getter:
this.getPrice = function() {
// This console.log will always return undefined.
// productService.selectedProduct is not available.
console.log(productService.selectedProduct);
̶i̶f̶ ̶(̶p̶r̶o̶d̶u̶c̶t̶S̶e̶r̶v̶i̶c̶e̶.̶s̶e̶l̶e̶c̶t̶e̶d̶P̶r̶o̶d̶u̶c̶t̶ ̶=̶=̶ ̶"̶f̶o̶o̶"̶ ̶&̶&̶ ̶s̶e̶l̶e̶c̶t̶e̶d̶S̶t̶a̶t̶e̶ ̶=̶=̶ ̶'̶S̶C̶'̶)̶ ̶{̶
if (productService.getSelectedProduct() == "foo" && selectedState == 'SC') {
return 10;
}
return 5;
}
Update
Should my services be communicating like that or is there a different, more accepted method?
I am trying to avoid something like this:
$scope.price = storeService.getPrice(
$scope.state,
$scope.selectedProduct,
$scope.servicePackage,
$scope.serviceFee,
$scope.shippingSelection,
// etc…
);
One way to avoid this is use an object as an argument to provide multiple options:
$scope.options = {};
$scope.price = storeService.getPrice(
$scope.selectedProduct,
$scope.options
);
The form can populate the options object directly:
<select ng-model="options.state">
<option ng-repeat="state in states">{{ state.name }}</option>
</select><br>
<select ng-model="options.serviceFee">
<option ng-repeat="fee in feeList">{{ fee.name }}</option>
</select><br>
<!-- //etc... -->
The setting of a variable in one service before computing something in another service creates an undesirable coupling that makes the code difficult to understand, debug, maintain, and test.
Instead all the information needed from the controller should be provided to the pricing service in a coherent manner.
You should not be injecting $scope, $scope is an outdated way of developing AngularJs and you should look into components or controllerAs syntax.
The controller should only be marshalling data between services and your view.
Services should provide data functions like get a product or create a new product and the controller should be doing things like
$ctrl = this;
$ctrl.product = productService.new();
or
$ctrl.product = productService.get(productId);
Then in your view you bind to properties of the product
<input name="name" ng-model="$ctrl.product.name">
And when you save a product you pass the whole thing back to the service
<form name="productForm" ng-submit="productForm.$valid && $ctrl.save()">
and in the controller
$ctrl.save = function() {
productService.save($ctrl.product);
}

How to chain JavaScript methods natively from an iterator?

Say I'm using js methods that have the 'this' returned, so one can chain like such:
something.add({a: 'xyz', b: 123}).add({a: 'abc', b: 456});
How can I chain these on something from an iterator? For example:
$scope.bindings = [
{
key: 'up',
func: function() {
$scope.scroll(1)
}
},{
key: 'down',
func: function() {
$scope.scroll(-1);
}
},
];
---EDIT---
The library I was using is Angular hotkeys. I wanted to ask in a generic way to help anyone else in a similar position.
I have:
var hotBindings = hotkeys.bindTo(scope);
for (var bind of scope.bindings) {
hotBindings = hotBindings.add({
combo: bind.key,
callback: function(e) {
e.preventDefault();
bind.func();
}
})
}
This assigns the 'down' method to both keypresses. If I write out the code without the loop, using scope.bindings[index].key (for example) and chain the .add() method then it works. I also tried without "hotBindings =".
Please don't mention the scopve vs $scope as this is being passed into a link function in a angular directive - angular almost certainly has nothing to do with it.
The only problem I see is the not working for (var bind of bindings).
Edit: thought it had something to do with the for (var bind of bindings) syntax and var v.s. let, turns out the provided code just works. I'll delete this answer if the real problem surfaces. Must be in the Something class?
Everything seems to work:
var Something = function() {
this.data = [];
this.add = function(item) {
this.data.push(item);
return this;
}.bind(this);
};
var bindings = [{
x: 'hi',
func: function() {
console.log("hi");
}
}, {
x: 'bye',
func: function() {
console.log("bye");
}
}];
var something = new Something();
for (var bind of bindings) {
something.add({
x: bind.x,
callback: bind.func
})
};
console.log(something.data);
I'm not sure what SomeThing is or what it's add method returns, but you can replicate the chaining by doing
let something = new Something();
for (const bind of bindings) {
something = something.add({
x: bind.x,
callback: bind.func
});
}
If you do this a lot (perhaps using different methods than just "add"), you might consider using a helper function:
function chain(obj, ...calls) {
for(let {method, args} of calls) {
obj = obj[method](...args)
}
return obj
}
chain(new Somthing(), [
{meth: 'add', args: [
{x: 'hi', func: () => console.log('hi')}]},
{meth: 'add', args: [
{x: 'bye', func: () => console.log('bye')}]}
]})

pass 'this' to callback in a class ecmascript 6 [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I have a Class in ecmascript 6 . I need to pass a value of 'this' to a callback.
I tried using .bind(this). So far does not seem to work. I also tried setting var _this = this; and using _this within the callback. it still does not work
class Modal {
constructor(modal) {
this._modal = modal;
this.id = this._options.id;
}
}
open(opts) {
let modalOptions = {
size: opts.size || '',
templateUrl: 'modal.html',
controller: function controller($scope, $uibModalInstance) {
var _this = this;
this._options = {
id: opts.id
};
this.hcbuttons: [{id: '1', name: 'test'}, {id: '2', name: 'abc'}];
publisher.subscribe('triggered', this._options.id, function(event, creator) {
//as soon as we subscribe to the published event
var result = this.hcbuttons.filter(function( obj ) {
return obj.id == creator;
})[0];
if(result.sync === true) {
console.log('disabledall');
}
}).bind(this);
}
}
You are wrongly binding the this. You are calling the bind over the returned value of subscribe function. Function object only has the function bind in its prototype. So chance your code from this }).bind(this); to }.bind(this)).
Since you want to set this as the modal class,
//change one
open(opts) {
var _this = this;
let modalOptions = {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~
//change two
}.bind(_this));
If you are using ES2015, why not use lambdas (arrow functions)? They bind this automatically
open(opts) {
let modalOptions = {
size: opts.size || '',
templateUrl: 'modal.html',
controller: function controller($scope, $uibModalInstance) {
this._options = {
id: opts.id
};
this.hcbuttons = [{
id: '1',
name: 'test'
}, {
id: '2',
name: 'abc'
}];
publisher.subscribe('triggered', this._options.id, (event, creator) => {
let result = this.hcbuttons.filter(obj => obj.id === creator)[0];
if (result.sync) {
console.log('disabledall');
}
});
}
}
}
Here you can read more on arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions and how they work (might help you in the future).

unable to access function from another function using this within same object

I have the following:
$scope.option = {
generateID:function(){
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
},
values : [
{id:this.generateId()},
{id:this.generateId()},
{id:this.generateId()},
{id:this.generateId()}
],
markCorrect : function(option){
},
remove:function(option)
{
this.values = this.values.filter(function(value){return value.id!=option.id})
}
}
I always get a this.generateId is not a function error. I am pretty sure that i am missing something fundamental here!
It may be better to store the id generator function in a separate function so it is easier to reference:
function generateId = function() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
}
$scope.option = {
generateID: generateId,
values : [
{id: generateId()},
{id: generateId()},
{id: generateId()},
{id: generateId()}
],
markCorrect : function(option){
},
remove:function(option)
{
this.values = this.values.filter(function(value){return value.id!=option.id})
}
}
The primary issue is that you're trying to access properties of $scope.option in the middle of declaring it. Try doing something like this instead:
$scope.option = (function () {
function generateId () {
/* logic */
}
return {
values: [
{id: generateId()}
// ...
],
markCorrect: function () {},
remove: function () {}
};
}) ();
This is the 'revealing module pattern', i.e. a function that returns an object forming a closure on some other data or functionality.
There is a typo; rename generateID to generateId.

Javascript simple MVC + module pattern implementation

Here is a very basic attempt to create a "hello world"-like JS app using the module and MVC patterns.
var appModules = {};
appModules.exampleModul = (function () {
var _data = ['foo', 'bar']; // private variable
return {
view: {
display: function() {
$('body').append(appModules.exampleModul.model.getAsString());
},
},
model: {
getAsString: function() {
return _data.join(', ');
},
}
};
})();
appModules.exampleModul.view.display();
This works fine, but I'm not happy how I have to reference the model function from the view, using the full object path: appModules.exampleModul.model.getAsString(). How can I expose the public model methods to the view, so I could simply use something like model.getAsString()? Or do I need to organize the code differently?
One option is you can convert those objects into private implementations.
appModules.exampleModul = (function() {
var _data = ['foo', 'bar'];
// private variable
var _view = {
display : function() {
$('body').append(_model.getAsString());
},
};
var _model = {
getAsString : function() {
return _data.join(', ');
},
};
return {
view : _view,
model : _model
};
})();
You could do something like this:
var appModules = {};
appModules.exampleModul = (function () {
var _data = ['foo', 'bar']; // private variable
return {
view: {
display: function() {
$('body').append(this.model.getAsString());
},
},
model: {
getAsString: function() {
return _data.join(', ');
},
}
};
})();
var display = appModules.exampleModul.view.display.bind(appModules.exampleModul);
display();
Which isn't really the prettiest of solutions, but does offer a more generic solution inside the display function!

Categories

Resources