If there is s path of /#/var1/oneVariable/var2/many,more,variables/var3/last,set and I wanted to update the $location, is there a way to only update the variable part of the path without having to re-declare everything from the current path?
Is something like this possible?:
$location.path(var2 = 'changed,all,three';)
and having the result be /#/var1/oneVariable/var2/changed,all,three/var3/last,set
I don't think that it is possible by default. However, you can create a function by yourself that keeps track of the current variables and fills them in for you when you need to change one of them and calls $location.path().
var currentParams = {
var1: 'oneVariable',
var2: 'many,more,variables',
var3: 'last,set'
};
function changePath(params) {
angular.forEach(params, function (value, key) {
currentParams[key] = value;
});
$location.path('/var1/'+ currentParams.var1 + '/var2/' + currentParams.var2 + '/var3/' + currentParams.var3);
}
You can call it now by using changePath({var2: 'changed,all,three'}).
Note that this is a simplified example. You can make it more flexible and it would be good to put it into an Angular service.
Related
In my app.run I have an function which get some text from an global variable from an other file. In that variable there are some keywords with an language attribute, which allows me to only get the requested text and filter out the right language.
I made an function for that but I can't get access the $rootScope.language variable in that function which I need for getting the right text.
function getTourTranslation(key){
console.log("getTourTranslation", key);
var lang = $rootScope.language;
var step = _.get(steps, key);
return step[lang]
}
So language is undefined, and so $rootScope.language is, but the variable exists and contains the right language key for the current language on the site. How can I get the content of that variabele without passing the language as variable into the function?
I also tried as $rootScope.getTourTranslation = function(key) but no luck
edit:
This is how the language variable is filled. We use angular-translate, so that is the $translate service. This code is placed above the getTourTranslation function.
$rootScope.changeLanguage = function (langKey) {
if(langKey){
if(langKey.length == 2) {
$translate.use(langKey.toLowerCase()+"_"+langKey.toUpperCase());
$rootScope.language = langKey;
} else if(langKey.length == 5) {
$translate.use(langKey);
$rootScope.language = langKey.substring(0,2);
}
}
};
$rootScope.$on('$translateChangeSuccess', function () {
if($translate.use()){
$rootScope.language = $translate.use().substring(0,2);
if($state.$current && !$state.$current.self.abstract) {
$state.reload();
}
}
});
$rootScope.changeLanguage($translate.use());
It is not possible that variable defined on $rootScope is not available withing the same context.
I can imagine only one case:
it calls getTourTranslation so early so none of changeLanguage or $translateChangeSuccess handler ran
The problem was, that $rootScope.language was called before it was initialized.
So, I placed the call to the getTourTranslation() function in an function which is called after firing an button. So, the right variables are now only initialized when they are needed instead of always.
So, I fixed two things, and that all based on the comment from #BotanMan
When using attribute binding in components, the data passed to the controller is always a string. I'm trying to pass an integer, however, and am having trouble converting it from a string and having the conversion stick.
I've tried saving the data as an integer in $onInit() but outside of this function, the data returns to its original state (type and value). I understand that components should not modify the data passed in as a general rule, but since this is an attribute binding, and the data is passed by value, I didn't think that applied.
function IntegerBindingController() {
this.$onInit = function() {
// Assuming 'number="2"' in the HTML
// This only changes the data inside this function
this.number = parseInt(this.number)
this.typeofNumber = typeof this.number // evaluates to 'number'
this.simpleAdd = this.number + 5 // evaluates to 7
this.dataAdd = this.numberOneWay + 5
console.log(this)
}
this.test = function() {
// this.number is a string inside this function
this.typeofNumber = typeof this.number // evaluates to 'string'
this.simpleAdd = this.number + 5 // evaluates to 25
}
}
I can solve this by copying the data to a new property on the controller, but I'm curious if someone can explain what's happening here. See this Plunker for a working example of the issue.
Passing number with '#' will always pass it as a string. If you want the object value pass number with '=' instead in the components bindings.
So:
var IntegerBindingComponent = {
controller: IntegerBindingController,
bindings: {
string: '#',
number: '=',
numberOneWay: '<'
},
template: _template
}
A decent explanation can be found here: http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
or here: Need some examples of binding attributes in custom AngularJS tags
"The '=' notation basically provides a mechanism for passing an object into your directive. It always pulls this from the parent scope of the directive..."
The solution I ended up going with was to use $onChanges to handle the bound data values. In my case, at least one of the values could potentially change after an async call in the parent component, so this made sense overall. As Prinay Panday notes above, the # binding always comes through as a string. The $onInit() method guarantees that bindings will be available, but it doesn't guarantee that they will change, so even if you change the value on the component, Angular can change it later. That's another reason why the documentation recommends copying the bound values to a local variable if you need to manipulate them at all. As for the $onChanges() solution, it would look like this
function IntegerBindingController() {
this.$onChanges(changes) {
if (changes.number && changes.number.currentValue) {
this.number = parseInt(changes.number.currentValue)
}
}
this.test = function() {
this.typeofNumber = typeof this.number // evaluates to 'number'
this.simpleAdd = this.number + 5 // evaluates to 7 (assuming this.number was 2)
}
}
Since I started to write code with AngularJS, I've been using factories and found them very useful.
I thought that they work like this (pseudo code):
FACTORY NAMESPACE {
PRIVATE FIELDS AND FUNCTIONS
RETURN {
INTERFACES TO ACCESS PRIVATE DATA
}
}
I thought that the expressions in return evaluates only when accessed directly, but, it seems that I didn't get it right.
I understand that the factories and services are very bottom of AngularJS and maybe someone thinks that this question shouldn't be here because it's trivial, and yet...
I created this plunk, I tried to find out why the variable changed inside factory code, won't keep its value after, when being accessed from outside, and what I found out confused me even more, the code inside of every return functions evaluates before anything else and it doesn't get called when it should be (by my logic)! Is. Is that designed that way, and if so, why?
Snippet from plnkr
var myApp = angular.module('app',[])
myApp.factory('_gl', [function () {
// Private fields
var _x;
function _somefunc(){
// This function evaluates even before the code of 'ctrl'
_x = 6;
console.log("changed:"+ _x);
}
return {
x:_x,
changeX:_somefunc()
}
}]);
myApp.controller('ctrl', ['_gl', function (_gl) {
_gl.x=2;
console.log("x init: " + _gl.x);
_gl.changeX(); // This does nothing at all
console.log("x after change: " + _gl.x);
}]);
/* Expected output
x init: 2
changed: 6
x after change:6
/*
/* Actual output
x init: 2
changed:6
x after change: 2
*/
Result:
After all that I found out from #dfsq (that right way is to use getters and setters), I came to the conclusion that although it can make some memory overhead, using simple JS global vars will do for me better.
the code inside of every return functions evaluates before anything else
Of course, because you are executing it with _somefunc().
It should be:
return {
x: _x,
changeX: _somefunc
}
Note, that there should be no () after _somefunc, which is invocation operator. You want changeX to be a reference to _somefunc, not result of _somefunc execution (_somefunc()).
I currently have 2 pages, page1.php and page2.php, each of the pages uses a controller that completes its own functions etc.
However there are tabs within the pages that are exactly the same that gets a promise from a factory within the module. The lists are exactly the same except for querying on different IDs. For example both controllers have this:
pageListFactory.getData().then(function (result) {
$scope.BasicItems = result; $scope.basicItemsList = [];
angular.forEach($scope.BasicItems, function (BasicItem) {
angular.forEach(BasicItem['SomeInnerArray'], function (BasicSomeInnerItem) {
if (BasicSomeInnerItem == VARIABLE_THAT_CHANGES) {
$scope.basicItemsList.push({
ID: BasicItem.ID, Title: BasicItem.Title
});
}
});
});
});
So this code is used, and the VARIABLE_THAT_CHANGES is just what changes each time. However as I said it is used on multiple pages, is there a way to create just one function call and then each page just can call a specific bit and send the variable with it?
I tried using $rootScope but of course this just clogs and slows, and I'm not exactly happy on constantly using $rootScope to pass the $scope.basicItemsList around as the list could get quite big.
So is there any way to reuse this code without just copying and pasting it into each controller?
Sure you can re-use it...
Convert the factory to a service, its basically a name change, create a local variable to store the data, update the data on first call, and then grab the data if it exists on the second call.
.service('myService', ... stuff ... { // I suggest using a service, as I don't know if a factory would act as a singleton
var myData = null;
return {
getData: function(){
if(myData != null)
return myData; // returns data
else {
return $http()... // ajax call returns promise
}
},
setData: function(dataToSet){
myData = dataToSet;
}
}
Then your controllers:
//controller 1
var promiseOrData = pageListFactory.getData();
if(promiseOrData instanceOf Array){ // or whatever data you expect
$scope.BasicItems = promiseOrData;
....
}
else { // should be a promise
promiseOrData.then(function (result) {
pageListFactory.setData(result); // set the data once you get it.
$scope.BasicItems = result; $scope.basicItemsList = [];
....
}
}
In controller 2 you only need to get the data as the returned data will be an array, not a promise
On top of all this, write a directive which will process the data when you pass it along, then you can pass the variableThatChanges and have the directive take care of it.
Use services and write the function in that service and pass the variable VARIABLE_THAT_CHANGES into it. By this you can reuse the code.
I'm sharing a service across multiple controllers. I've made this simplified fiddle to illustrate it: http://jsfiddle.net/ziaxdk/FFgpX/
app.service("common", function() {
var first = 1, second = 2;
return {
first: first,
second: second,
// {{model.first+model.second}} calculate this down here??
calc: function() { return first + second; } // This line is not watched
}
});
How can I create calculated rules/properties on the service object so they get reflected to the view?
Regards,
Kenneth
It is not really AngularJS-specific problem it is how closures work in JavaScript for primitive types. Basically when you've got a closure over a primitive type you are getting a copy of the original values instead of a reference to them. You are breaking the link. So in your code you've been changing different variable values then ones used to do calculation.
You could modify your service as follows:
app.service("common", function() {
return {
first: 1,
second: 2,
calc: function() { return this.first + this.second; }
}
});
And your code becomes operational.
Here is a working jsfiddle: http://jsfiddle.net/e3nzY/2/