I have an AngularJS project, and I'm using a modified version of md-steppers, whose interesting functions boil down to this:
var enableNextStep = function () {
//do not exceed into max step
if ($scope.selectedStep >= $scope.maxStep) {
return;
}
//do not increment $scope.stepProgress when submitting from previously completed step
if ($scope.selectedStep === $scope.stepProgress - 1) {
$scope.stepProgress = $scope.stepProgress + 1;
}
};
var completeCurrentStep = function (CurrentStep) {
$scope.stepData[CurrentStep].completed = true;
};
$scope.moveToNextStep = function moveToNextStep() {
if ($scope.selectedStep < $scope.maxStep) {
enableNextStep();
$scope.selectedStep = $scope.selectedStep + 1;
completeCurrentStep($scope.selectedStep - 1); //Complete After changing Step
}
};
$scope.moveToPreviousStep = function moveToPreviousStep() {
if ($scope.selectedStep > 0) {
$scope.selectedStep = $scope.selectedStep - 1;
}
};
The problem is that I would like to use these four functions in two different controllers (so as to not repeat them), that have different stepProgress, selectedStep and maxStep values. I couldn't find a way to do so with services, but I might just be confused about the way AngularJS work, as I am more used to Python.
Thanks.
Abstracting out that functionality into a factory that accepts an array of callbacks and a controller's ng-model would make it more reusable. Of course, ultimately the API you want is up to you. The goal is that you don't want any $scope business inside the factory, it shouldn't be concerned about what's inside the callbacks, it just steps through them.
/**
* #param steps {array} - array of callbacks
*/
function stepperFactory(steps) {
iterate(0, steps);
}
function iterate(current, steps) {
if (!steps[current])
return;
if (typeof steps[current] === 'function')
// pass an async "done" callback
// so your array of input callbacks can be async
// you could also use promises or $q for this
steps[current](() => iterate(current + 1, steps));
}
And so the api you expose would be like:
['stepperFactory', function(stepperFactory) {
this.model = { step: 0, msg: 'start' };
this.steps = [
(done) => {
this.model.step++;
done();
},
(done) => {
setTimeout(() => {
this.model.msg = '3rd step';
this.model.step++;
done();
});
}
];
stepperFactory(this.model, this.steps);
}]
You can use service to share functions which will take maxStep, stepProgress, etc as arguments and instead of modifying the $scope, they will return updated values.
In service:
function moveToPreviousStep(step) {
if (step > 0) {
return (step - 1);
}
return step;
};
and in controller
function moveToPreviousStep() {
$scope.selectedStep = service.moveToPreviousStep($scope.selectedStep);
}
$scope.moveToPreviousStep = moveToPreviousStep;
Related
For clarity, this question applies to the observable pattern implemented in javascript (the code I'll write here is all ES6).
Recently I've been trying to figure out how observables actually work. I found a couple articles/posts that describe them within the context of creating a class that takes a function as its constructor argument. This function just waits around for an observer, and the method .subscribe(observer) finally triggers this function with the observer as argument.
Here is my attempt at creating an observable with class syntax that implements an interval with a limit on the number of interval ticks (so that there is something to complete with):
class Observable {
constructor(pushFn) {
this.pushFn = pushFn; // waits for observer as argument
}
static interval(milliSecs, tickLimit) {
return new Observable(observer => {
if (milliSecs <= 0) {
observer.error("Invalid interval. Interval value must be larger than 0.")
} else {
let tick = 0;
const inter = setInterval(() => {
observer.next(tick += 1);
if (tick === tickLimit) {
clearInterval(inter);
observer.complete(tickLimit);
}
}, milliSecs);
}
});
}
subscribe(observer) {
return this.pushFn(observer);
}
}
What I find kind of surprising (given that I always thought of observables as some sort mysterious super complex thing) is that this functionality can be replicated with the following function:
const observableIntervalOf = (milliSecs, tickLimit) => observer => {
if (milliSecs <= 0) {
observer.error("Invalid interval. Interval value must be larger than 0.")
} else {
let tick = 0;
const inter = setInterval(() => {
observer.next(tick += 1);
if (tick === tickLimit) {
clearInterval(inter);
observer.complete(tickLimit);
}
}, milliSecs);
}
};
Then, the effect of each of the following is identical:
Observable.interval(1000, 5).subscribe(someObserver);
const fiveSeconds = observableIntervalOf(1000, 5);
fiveSeconds(someObserver);
Am I right in assuming that its best to think of observables as partially applied functions? Are there instances where this is not the case?
I know some javascript function declarations. Like exression function, anonymous function, but I do not understand what kind of syntax of these two functions? Can anybody tell me what is the name of these two functions? I mean "manipulateData: function (input)" and "getDataById: function (id)".
Why return statement can have this syntax to return two functions? Why not return one function in one time instead of two functions? It will be great if you can give me some reference documents? Thanks.
app.service('MyService', function ($http, $q, $angularCacheFactory) {
var _dataCache = $angularCacheFactory('dataCache', {
maxAge: 3600000 // items expire after an hour
});
/**
* #class MyService
*/
return {
manipulateData: function (input) {
var output;
// do something with the data
return output;
},
getDataById: function (id) {
var deferred = $q.defer();
if (_dataCache.get(id)) {
deferred.resolve(_dataCache.get(id));
} else {
// Get the data from the server and populate cache
}
return deferred.promise;
}
};
});
These functions are just anonymous functions that happen to be values in an object. Consider this:
var object = {
add: function(x, y) {
return x + y;
}
};
object.add(1, 2); // = 3
This is the same as:
function addFunction(x, y) {
return x + y;
}
var object = {
add: addFunction
};
object.add(1, 2); // = 3
There's nothing special about these functions, as they're just normal properties of an object.
You are not returning a function in this case but an Object.
When you define a service in angularjs you have to provide its implementation in the callback (the second argument of app.service)
This callback has to return methods you want to make available to other parts of your application.
Then in a controller or in another service you will be able to write:
app.controller("MyCtrl", ["MyService", function(MyService) {
MyService.getDataById('an id');
}]);
Angular Service returns an instance of the service you bind to the app namespace, those functions in the return statement are public methods that can be worked with. Basically an Object that contains two methods manipulateData, and getDataById.
It's similar to this
function company() {
let product; // This is private
// Public Methods
return {
setLatestProduct: function(value) {
product = value;
console.log(product, ' set');
},
getLatestProduct: function() {
return product;
}
}
}
const apple = company();
console.log(apple); // { setLatestProduct: function, getLatestProduct: function }
I am trying to mock the times function from the JavaScript library Underscore.js.
This function accepts two syntaxes :
_.times(3, function(n) {
console.log("hello " + n);
});
and
_(3).times(function(n) {
console.log("hello " + n);
});
So far I succeeded to mock the first one by creating an _ object like this :
var _ = {
times: function(reps, iteratee) {
// a loop
}
};
And the second syntax by creating an _ function which returns an object :
function _(n) {
return {
times: function(iteratee) {
// a loop
}
};
}
But I can't use these 2 methods together. I need to find a way that will allow both syntaxes.
Do you have any idea how I could use the _ character as an object as well as a function ?
You should be able to combine two syntaxes like this:
var _ = (function() {
var times = function(n, iteratee) {
// a loop
};
function _(n) {
return {times: function(iteratee) {
return times(n, iteratee);
}}; // or shorter: {times: times.bind(null, n)}
}
_.times = times;
return _;
})();
Here you benefit from the fact that functions are also objects and hence can have properties.
Functions are objects in Javascript, so you could just do something like this:
var _ = function(a,b) { /* ... */ };
_.times = _;
You could extent the function after defining it. Try this:
function _(n) {
return {
times: function(iteratee) {
while (n-- > 0)
iteratee();
}
};
}
_.times = function(reps, iteratee) {
while (reps-- > 0)
iteratee();
};
function iter() {
console.log('iter!');
}
_(3).times(iter);
console.log('----');
_.times(5, iter);
I'm calling this factory function in Angular, passing in a percentage value as a parameter:
if(scroll_factory.fetch(50)) {
// If the user has scrolled 50% of page height, do something.
};
scroll_factory.$inject = ['$window', '$document'];
function scroll_factory($window, $document) {
var factory = {
fetch: fetch,
fetch_position: fetch_position
}
return factory;
function fetch(percent) {
$document.on('scroll', fetch_position)
function fetch_position() {
var window_inner_height = $window.innerHeight;
var y = pageYOffset;
var percentage = (y / window_inner_height) * 100;
if (percentage > percent) {
// Condition true - return true for the outer fetch.
}
}
return false;
}
What I want is for the fetch function to return true when the scroll condition is true. Thanks.
Your approach will not work. In:
if(scroll_factory.fetch(50)) { ... }
you are already evaluating (the function returns a value a this point) the fetch function.
You need to rethink that the fetch_position callback is continuously evaluated.
You want to have a scroll-spy functionality as a separated module?
.factory('ScrollSpy', function($document, $window) {
return function(callback) {
$document.on('scroll', function(e) {
var y = ...
if ( y > 123)
callback(y);
})
};
})
One way to solve this is to provide a factory which accepts a callback which can be evaluated depending on your demands.
I made a small example, hope it helps: http://plnkr.co/edit/du3dpMlaKYZFuy8WA0QL?p=preview
I have a problem with callback function. I want write a function, who
can iterate the object (i want use a callback method), but it's not
working and i don't know what is wrong with this.
I'll be glad from any help.
services = [
{
name: "a",
},
{
name: "b"
}
]
function Service (data) {
this.name = data.name
}
function getData (i) {
sample = new Service(services[i])
console.log(sample)
}
getData(0) /* this function work*/
function getAll(index, count, callback) {
service = new Service(services[index]);
console.log(service)
if (index < count) {
callback(index + 1, count, getAll)
}
}
getAll (0, services.length, getAll) /* this function is not working */
The error you get is due to calling services[2] that doesn't exists.
This getAll function below solves your problem
function getAll(index, count, callback) {
if (index < count) {
service = new Service(services[index]);
console.log(service)
callback(index + 1, count, getAll)
}
}
The problem is the
getAll (0, services.length, getAll)
services.length return the length of an array, but arrays start at position 0
to fix this error use
getAll (0, services.length-1, getAll)