I'm currently developping an angular app and I have found two ways to call a function that does a simple multiplication.
First
function calcul(contexte) {
contexte.proposition.marge_theorique = contexte.proposition.marge_grille * 2;
}
and call it with
calcul($scope)
Second
$scope.dynamicChange = function () {
$scope.proposition.marge_theorique = $scope.proposition.marge_grille * 2;
}
and call it with
$scope.dynamicChange()
What is the difference between those usages?
Thanks a lot
There is no execution difference between your two approaches, but I would recommend you the second one, cause passing scope in parameter is not very usual, adds nothing, and it not allows you to use method directly in your view.
Using the second way, (I mean, the $scope.dynamicChange one), is also good cause you take profit of the Angular controllers inheritance. So every child scope of your controller scope will get this method.
Conclusion, no very difference for your specific task, but I recommend you to use the more "Angular" way.
In the example above, calling the function with dynamicChange() would throw an error, since dynamicChange() is a method of $scope and not a function.
You would need to call $scope.dynamicChange
You won't be able to call the function of the first example from within your html-templates.
So you can't just use something like.
<div>
{{myFunction()}}
</div>
Related
I've got a series of controllers that make a common call to a function in a parent controller. The behavior of that function will depend on which controller calls it. Currently, I handle this by passing a key as an argument:
In controllerOne:
var result = $scope.getQuery('controllerOne');
In controllerTwo:
var result = $scope.getQuery('controllerTwo');
This works, but I can't help but feel like there's got to be some way to autogenerate that key so that it remains consistent every time the parent function is called. Something along these lines:
var result = $scope.getQuery(this.$id);
So that others can just drop the call into different places without worrying about what the key is. this.$id doesn't work, as it changes on each call, but something like that. Is there a way to get the controller's name, maybe? Anything that is unique and static would suffice.
I am using angular-meteor and would like to perform a function on each object. I tried running this function within an ng-repeat in the view, but I am getting massive amounts of function calls and can't figure out why. I tried to make it as simple as possible to demonstrate what is going on.
constructor($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.loaderCount = 0;
this.helpers({
loaders() {
return Loaders.find( {isloader:true}, {sort: { name : 1 } })
}
});
That gives me 26 Loaders. My function just adds 1 to the count every time the function is called:
displayLoaderCount()
{
return ++this.loaderCount;
}
Now in my view, I am looping through each loader, and calling the function. This should in my mind give me 26, but instead I am getting 3836.
<tr ng-repeat="loader in loaderExhaustion.loaders">
<td>{{loaderExhaustion.displayLoaderCount()}}</td>
Can anyone help explain this to me? Ideally I would like to loop over the contents in my module but as the collection is async, when the loop starts the length of the collection is 0, hence why I made the call in the view.
THANKS!
Every time angular enters a change detection cycle, it evaluates loaderExhaustion.displayLoaderCount(), to know if the result of this expression has changed, and update the DOM if it has. This function changes the state of the controller (since it increments this.loaderCount), which thus triggers an additional change detection loop, which reevaluates the expression, which changes the state of the controller, etc. etc.
You MAY NOT change the state in an expression like that. For a given state, angular should be able to call this function twice, and get the same result twice. Expressions like these must NOT have side effects.
I can't understand what you want to achieve by doing so, so it's hard to tell what you should do instead.
I need to test every listener in a controller with Jasmine 2.0, really this question is just to reinforce my logic, and perhaps there is a more elegant way to approach testing listeners, or maybe I am being too thorough!
This might be a better question for codereview but I'll leave it here. How to properly test multiple keypress/event listeners in a controller effectively?
it("should trigger the correct actions from key events", function () {
var listenerSpy = jasmine.createSpy('listenerSpy');
angular.forEach(scope.$$listeners, function (fn, eventName) {
listenerSpy(eventName, fn);
expect(listenerSpy).toHaveBeenCalledWith(eventName, fn);
});
});
What you have above is not really testing anything other than JavaScript itself. You are calling a function and then expecting that you just called that function.
A code coverage report would show that the listener function has not executed at all.
Without seeing the code you are testing, I am unable to properly advise on how to structure your test.
There are two possible intentions:
1) Do you want to test that the scope is listening to a set of known elements?
2) Do you want to test the outcome of listener executions?
Usually, it would be best to take path number two, because with it, you also get number one.
Are all of your listeners performing the same action?
If they are, it may make sense to loop through a list of known elements and change them to verify the proper listener execution output.
If the listeners perform differently, each execution's output should be evaluated.
I couldn't find an answer or a solution to a challenge yet: How can I bind a variable (Session variable, no mongo collection) reactively in angular-meteor?
I'm converting from standalone meteor. Here I could use the template.helper method. As I can't use templates (and iron:router) anymore with angular-meteor and angularui-router, I can't bind reactivity to the helper anymore (at least in my understanding).
I tried this in an meteor-angular controller, which belongs to a sentence.tpl file:
$scope.parsestring = function(input_string){
tokenizer(input_string);
};
$scope.sentence_type = Session.getJSON("current_sentence.sentence_type");
Tokenizing works (I can see it in the debugger), but the value is only displayed, when I reload the page. What I want to achieve is tokenizing a string from an input field into a JSON representation (the tokenizer takes care of that) and displaying it similtaniously from the JSON representation in a structured way (separate html input elements, which are created dynamically). sentence_type is the variable that should be used on the html-page to show and change the sentence type, which can change while typing.
Anybody has some hints? Maybe, I could also use some Angular feature that I don't know?
Cheers,
Jan
Code repo:
My current code looks like this:
My code looks similar to this:
angular.module('ngaignt').controller("InteractorCtrl", ['$scope', '$meteor', '$meteorCollection',
function ($scope, $meteor, $meteorCollection) {
// Autorun is necessary to make reactive variables out of the JSON returns
var c = Tracker.autorun(function (comp) {
$scope.verb_type = Session.getJSON("current_verb.type");
$scope.object_type = Session.getJSON("current_object.type");
$scope.verb_attributes = _.toArray(Session.getJSON("current_verb.attributes"));
$scope.object_attributes = _.toArray(Session.getJSON("current_object.attributes"));
if (!comp.firstRun) {
// only do not do aply at first run becaulse then apply is already running.
$scope.$apply();
}
});
$scope.parsestring = function (input_string) {
interactor(input_string);
};
//$scope.on('$destroy', function () {c.stop()});
}]);
To use reactive variables, you need a reactive computation. You may need to use Tracker.autorun:
$scope.parsestring = Tracker.autorun(function(someStringInSession){
tokenizer(Session.get(someStringInSession));
});
Or you can use Tracker.autorun(func) wherever you use a reactive variable to reactively rerun a function when the variable changes.
good question and the best answer depend on your needs.
There are 2 possible solutions:
If you want to bind a Session variable to a scope variable, use the $meteorSession service.
What it does is that every time the scope variable will change, it will change to Session variable (and trigger an autorun if it's placed inside one).
and every time the Session variable will change, the scope variable will change as well (and change the view that it's placed upon).
If you are using the Session variable just to get a variable reactive (meaning trigger an autorun), you should use getReactively . this just returns the already existing scope variable but trigger an autorun every time it changes. a good example of this can be found it our tutorial.
Note: In anyway, when you use Tracker.autorun inside Angular, you need to connect it to a scope. this can be easily done if you replace Tracker.autorun with the $meteorUtils autorun function
Would be great if you could share a repo so that I can look on the broader perspective and could better determine what's the best solution from the two.
Based on another answer about "session" reacitivity, I could solve the problem. Just use the approach described in the link https://stackoverflow.com/a/21046935/4035797. You have to substitute Deps.autorun by Tracker.autorun though as Deps is deprecated and you have to make the scope variables for use in the template reactive (e.g., $scope.sentence_type = Session.getJSON("current_sentence.sentence_type");) and not the tokenizer.
When I use this it works:`
angular.module('app').service('DataService', function() {
return {theme: "amelia"}
});
But when I use this, there is no update? Can you tell me the difference?
angular.module('app').service('DataService', function() {
return {
theme: function() {
return {theme: "amelia"}
}
};
});
Controller
$scope.settings = DataService.theme();
Jade
select.form-control(ng-model="settings.theme", ng-options="theme for theme in themes")
Is it possible to get the second way working? Because I will share more data then one Object!
Thank you!
The first version of the code calls the function once to instantiate the service. After that, because services are singletons in angular the function isn't called again, but rather the return value (a "static" object) is accessed in every controller that uses the service after that.
The second version, each controller you inject the service into calls the theme function, which instantiates a brand new object each time. You have now effectively mitigated the fact that the service is a singleton. This is why data will not be shared with the second set of code.
If you put a break point on the function call in each case and run your code you should see the first version called once while the second version will be called many times.
"Get It Working"...
You can't really make it work with a function call but if you need to share multiple data objects there isn't any reason not to nest them. You could very easily do something like:
angular.module('app').service('DataService', function() {
return {
dataObjects: [
{"type":"theme", "theme":"amelia"},
{"type":"user", "id":123, "name":"ABC"}
]};
});
In the example I added a second object which is a user object to make shared "dataObjects" array. To find a specific object in the "dataObjects" array, you could loop till you find the correct type ("theme", for example). If necessary, you could even nest one level deeper if you needed the objects to be pristine (without the added type attribute).
Hope that helps!
It should be theme: function().... inside your service. Replace "=" with ":".