I am writing a custom filter which involves needing some data map to be initialised so that the data map is not created everytime the filter is invoked.
I do this:
myModule.filter("myfilter", function($filter) {
return function(letter) {
// ...
var blah = myOtherFunction(letter)
}
}
var myOtherFunction = function() {
// initialise some data
var myData = {
"a":"letterA"
"b":"letterB"
}
return function(letter) {
return myData[letter];
}
}();
This means the file where I define my filter has a utility function which uses a closure to close over data which is initialised once and once only.
I am wondering is there a more angular way to achieve this?
Thanks
In general, data should be manipulated/fetched/sent and shared throught services.
But if the "data" you are referring to are:
1.) static and
2.) specific to the logic of the filter
then I believe it does not fall into the general category of "application data"; it is rather "filter logic" stuff.
As such their place is right in the filter.
(BTW, in order to initialize it only once, you don't need all that complex "calling IIFE returned function" stuff. Just put the data in the filter definition function (see below).)
app.filter("myFilter", function() {
var staticFilterSpecificData = {
"a": "letterA",
"b": "letterB"
};
console.log('Initialized only once !');
return function(letter) {
return staticFilterSpecificData[letter] || 'unknown';
};
});
See, also, this short demo.
If your data are static, just create a new module/service and inject it in your filter :
myModule.filter('myfilter', ['myService', function(myService) {
return function(amount, currency) {
return myService.getLetter("a"); //don't know what you want to do here, using amount or currency I guess ?
};
}]);
myModule.factory('myService', function() {
var service = {};
var myData = {
"a":"letterA"
"b":"letterB"
}
service.getLetter = function (letter) {
return myData[letter];
}
return service;
});
If your data are retrieved asynchronously, follow this post : Asynchronously initialize AngularJS filter
Related
I have a simple factory in AngularJS:
(function(){
'use strict';
angular
.module('myModule', [])
.factory('myService', service);
function service(){
var products= function(p1, p2, p3, ..., pn) {
var url = "http://something.url/api/action";
var data = {
'p1': p1,
'p2': p2,
...
'pn': pn,
}
// return data
return $http
.post(url, data)
.then(function (response) {
return response.data;
});
}
return {
Products : products
};
}
})();
I use this service inside a controller like this:
myInjectedService
.Products(vm.p1, vm.p1, ... , vm.pn)
.then(successCallbackFn)
.catch(failureCallbackFn);
Each parameter (p1, ..., pn) are used to filter the final result. This works like a charm! But with a little drawback: there are to many accepted arguments for Products and is really difficult to know if I'm sending the right parameters and this sounds a little error prone. What I would is a fluent API for service that make everything more human readable, this would be great:
myInjectedService
.Products()
.FilterById(p1)
.WhereCategoryIs(p2)
...
.WhereSomethingElseIs(pn)
.Send()
.then(successCallbackFn)
.catch(failureCallbackFn);
Previously the task of HTTP call was handled by Products call. Right now Products(), only make an empty query (i.e. {}). Each subsequent FilterByX will enrich the query (i.e. {'productId': 'xxx-yyy-1111'}). Calling Send() will make the real HTTP POST call. This call will use the data builded through various filter applied. How can I do that? I'm playing with prototype but without success.
You can archieve what you want by define a new class and use prototype like this.
In a fluent method, remember to return the object itself.
function service(){
var products = function(url) {
// Define a new Product class
var Products = function() {
this.url = url;
this.data = {};
};
// Add the function
Products.prototype.FilterById = function(id) {
this.data.id = id;
// To make it fluent, return the object itself
return this;
};
Products.prototype.FilterByCategory = function(category) {
this.data.category = category;
return this;
};
Products.prototype.send = function() {
console.log(this.data);
};
// Return an instance of the Products class
return new Products();
};
return {
Products : products
};
};
service().Products().FilterById(1).FilterByCategory("Item").send();
You can read more about it here: https://www.sitepoint.com/javascript-like-boss-understanding-fluent-apis/
I'm not really sure about this issue but it seems that sometimes when I activate $watch for a function then it doesn't work.
for example I have this simple service
angular.module('sp-app').factory('mediaSources', function() {
var storages = [];
return {
addStorage: function(storage) {
storages.push(storage);
},
getStorages: function() {
return storages;
}
}
});
and when I watch getStorage method in order to update my view it doesn't call change callback or calls only at initialization stage
$scope.$watch(function($scope) {
return mediaSources.getStorages();
}, function() {
console.log('call')
});
and I can only track changes by watching length property of returned array
return mediaSources.getStorages().length;
and I wonder because I have written similar think somewhere else within my application and it works fine.
If i interpret what you are trying to do, you should not need to set a watch on something like this, you can just use a factory like so :
angular.module('app').factory('mediaSources', function(){
var storages = {};
storages.list = [];
storages.add = function(message){
storages.list.push(message);
};
return storages;
});
then in the controller you want to receive/update the data to for instance, you would do
$scope.myControllerVar = mediaSources.list;
No need to watch over it, it should update for you.
You will have to set up watcher with equality flag as the third argument:
$scope.$watch(function($scope) {
return mediaSources.getStorages();
}, function() {
console.log('call');
}, true);
In the following code i am trying to change to another page on click and want to pass the object i how can i do it. In the following code i get it as undefined.how to go about this
<button ng-href="#/page1" value="{{i.display}}"></button>
app.controller("ctrls",['$scope','$location',function($scope,$location){
$scope.func = function(i) {
$scope.var=i
$location.path("/rel");
};
]);
app.controller("ctrls",'$scope',function($scope) {
console.log($scope.var) //undefined
]);
Pages normally have controller(s), a service can be created to share data between pages ( by injecting service in associated controllers). Like:
app.factory('myService', function() {
var savedData = {}
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
In your controller A:
myService.set(yourSharedData);
In your controller B:
$scope.desiredLocation = myService.get();
Happy Helping!
Use a service (best) https://docs.angularjs.org/guide/services
Or $rootScope (bad, but simpler) https://docs.angularjs.org/api/ng/service/$rootScope
So I have a directive that takes in data objects as an argument into the scope. The problem is that I handle all my data in my service layer.
So this is some normal non-directive code:
angular.module('app').factory('appFactory', ['appValues', function(appValues) {
var getStuff = function() { return appValues.stuff; };
}]);
But if want to reuse the factory inside a directive and get appValues as an argument:
angular.module('app').directive('myDir', [function() {
return {
...
scope: {
values: '='
}
....
};
}]);
But this puts it on the scope and not into my data layer. So now I need to send the values object to every function call in my directive factory:
angular.module('app').factory('myDirFactory', [function() {
var getStuff = function(values) { return values.stuff; };
}]);
Is there any good pattern to solve this and keep data in the data-layer and bypass the scope/controller?
Also, the factory will be a singleton shared amongst instances of the directive? How should I solve that then? Create a new injector somehow? Submit to putting lots of data object logic into the controller (which I've been thought not to do)?
It was a while ago, and I guess that a simple soultion is simply to provide an function initialize(value) {... return {...};} and then the returned object has access to the value argument without providing it as a parameter everywhere:
angular.module('myDir').factory('myDirFactory', [function() {
var initialize = function(values) {
var getStuff = function() {
return values;
};
return {
getStuff: getstuff;
};
};
return {
initialize: initialize
};
}]);
I'm building an app in angularjs, where I have a central notification queue. Any controller can push into the queue and digest the messages.
I have built a service like:
angular.module('app').factory('notificationSvc', ['translateSvc', notification]);
function notification(translate) {
var notificationQ = [];
var service = {
add: add,
getAll: getAll
};
return service;
function add(message, type) {
notificationQ.push({
message: message,
type: type
});
}
function getAll() {
return notificationQ;
}
}
(One of the problems with this is that the notificationQ can be modified unsafely by calling svc.getAll()[3].message = "I have changed a message"; or something similar. I originally wanted a "push only" service with immutable messages, but this problem is outside of the scope of this question.)
If I digest this queue in a controller like:
$scope.notifications = svc.getAll();
$scope.current= 0; // currently visible in the panel
And use it like:
<div ng-repeat="notification in notifications" ng-show="$index == current">
<p>{{notification.message}}</p>
</div>
I can bind to it, see it changing and all is well. I can cycle through past notifications by changing the variable current.
The question:
When the queue gets a new element I want the $scope.index variable to change to notifications.length - 1. How do I do that?
I have seen examples using $rootScope.$broadcast('notificationsChanged'); and $scope.$on('notificationsChanged', function() { $scope.index = $scope.notifications.length - 1; });, but I did not really like the pattern.
I have a controller that knows about the service, has a direct reference to it, and yet we use $rootScope to communicate? Everything else sees the $rootScope, and all the events from different services will clutter up there.
Can't I just put the event on the service instead? Something like this.$broadcast('notificationsChanged') in the service and svc.$on('notificationsChanged', function() { ... }); in the controller.
Or would it be cleaner to watch the data directly? If yes, how? I don't like this as I was not planning on exposing the full array directly (I was planning on get(index) methods) it just sort of happened along the lines where I had no idea what I was doing and was happy that at least something works.
You could just manage events yourself. For example (untested):
function EventManager() {
var subscribers = [];
var service = {
subscribe: subscribe;
unsubscribe: unsubscribe;
publish: publish
}
return service;
function subscribe(f) {
subscribers.push(f);
return function() { unsubscribe(f); };
}
function unsubscribe(f) {
var index = subscribers.indexOf(f);
if (index > -1)
subscribers.splice(index, 1);
}
function publish(e) {
for (var i = 0; i < subscribers.length; i++) {
subscribers[i](e);
}
}
}
function notification(translate) {
var notificationQ = [];
var addEvent = new EventManager();
var service = {
add: add,
getAll: getAll,
onAdded: addEvent.subscribe;
};
return service;
function add(message, type) {
var notification = {
message: message,
type: type
};
notificationQ.push(notification);
addEvent.publish(notification);
}
function getAll() {
return notificationQ;
}
}
Then, from your controller:
...
var unsubscribe = notificationSvc.onAdded(function(n) { /* update */ });
Caveat: using this method the service will maintain a reference to the subscriber function that is passed to it using subscribe, so you have to manage the subscription using $scope.$on('$destroy', unsubscribe)
The notification approach would definitely work. Depending on your implementation it would be the right solution.
Another approach would be to watch the notifications array in your controller, like this:
$scope.$watchCollection('notifications', function(newValue, oldValue) {
$scope.index = newValue.length - 1;
});
This should work, because your controller receives a direct reference to the notifications array and therefore can watch it directly for changes.
As runTarm pointed out in the comments, you could also directly $watch the length of the array. If you're only interested in length changes this would be a more memory saving approach (since you don't need to watch the whole collection):
$scope.$watch('notifications.length', function (newLength) {
$scope.index = newLength - 1;
});