How do I change a variable in MainController from PartialController? (angularjs) - angularjs

I have a partial view that is using angular. How do I change a variable in the MainController from PartialController? I am not sure how to create the interdependence...
angularApp.controller('MainController', ['$scope', '$http', '$compile', function MainController($scope, $http, $compile) {
$scope.myVariable = "0";
//Had the following before refactoring due to repetitive code.
//Code now in PartialController
//$scope.searchData = function ($event) {
// //code
// $scope.myVariable = "1";
//}
}]);
angularApp.controller('PartialController', ['$scope', '$http', '$compile', function PartialController($scope, $http, $compile) {
$scope.searchData = function ($event) {
//code
$scope.myVariable = "1";
}
}]);

For sake of completeness, there are at least 3 ways:
With a service as #tymeJV suggested (BEST answer)
app.factory('dataStore', function () {
var dataStore = {};
return dataStore;
});
app.controller('ParentCtrl', function($scope, dataStore) {
$scope.dataStore = dataStore;
$scope.dataStore.foo = 'bar';
});
app.controller('ChildCtrl', function($scope, dataStore) {
dataStore.foo = 'not bar anymore';
});
With an object reference on the parent scope (A bit hackish)
app.controller('ParentCtrl', function($scope) {
$scope.data = {
foo: 'bar'
};
});
app.controller('ChildCtrl', function($scope) {
$scope.data.foo = 'not bar anymore';
});
With $parent (equally hackish)
app.controller('ParentCtrl', function($scope) {
$scope.foo = 'bar';
});
app.controller('ChildCtrl', function($scope) {
$scope.$parent.foo = 'not bar anymore';
});
Why are #2 and #3 hackish?
Because they create a dependency in your ChildCtrl of having it always be a child of the ParentCtrl... otherwise it will break.
So why include #2 and #3 at all?
For a few reasons:
Directives can have controllers, and required parent directives. Because of this, there are cases where you can "safely" use $parent or scope inheritance because you'll always know that ChildCtrl has ParentCtrl as a parent.
Sometimes you just need to hack something together.
As I said, for the sake of completeness.

This is a prime use for a service that can be injected to controllers when you need it and pull data from it:
app.factory("myService", function() {
var myVariable = null;
return {
get: function() {
return myVariable;
},
set: function(value) {
myVariable = value;
}
}
});
//Inject
angularApp.controller('MainController', ['$scope', '$http', 'myService', '$compile', function MainController($scope, $http, $compile, myService) {
myService.set(3);
});

tymeJV's answer is correct and is probably best practice in this case. I believe the reason it wasn't working in your example is because in Javascript, primitives (strings, numbers, booleans) are passed by value, whereas objects are passed by reference.
i.e. if you had $scope.obj.myVariable=1 in your main controller and edit $scope.obj.myVariable in your child controller, you should see the new value in both. (this is kinda #2 in blesh's answer). This is a common source of "bugs" in Angular so it's good to be aware of it.

Related

how angular controller (and scope) inheritance works

I'm trying to figure out how controller inheritance works. I have three controllers:
var myApp = angular.module('app', []);
myApp.controller('MainController', ['$scope', function($scope) {
$scope.name = 'main';
$scope.getName = function() {
return $scope.name;
};
}]);
myApp.controller('Child1', ['$scope', function($scope) {
$scope.name = 'child1';
}]);
myApp.controller('Child2', ['$scope', function($scope) {
$scope.name = 'child2';
}]);
and my view
<div ng-app='app'>
<div ng-controller='MainController'>
<div ng-bind='getName()'></div>
<div ng-controller='Child1'>
<div ng-bind='getName()'></div>
<div ng-controller='Child2'>
<div ng-bind='getName()'></div>
</div>
</div>
</div>
</div>
but they're all showing "main". How do I fix this?
here's a fiddle http://jsfiddle.net/g3xzh4ov/3/
Here's an example of how controllers can be extended in Angular.
myApp.service('baseCtrl', function () {
this.name = 'base';
this.getName = function() {
return this.name;
};
});
myApp.controller('MainController', ['baseCtrl', function (baseCtrl) {
angular.extend(this, baseCtrl);
this.name = 'main';
}]);
myApp.controller('Child1', ['baseCtrl', function (baseCtrl) {
angular.extend(this, baseCtrl);
this.name = 'child1';
}]);
myApp.controller('Child2', ['baseCtrl', function (baseCtrl) {
angular.extend(this, baseCtrl);
this.name = 'child2';
}]);
It obliges to use controllerAs, which replaces $scope with this, it is especially good for such cases.
Notice the usage of service instead of other Angular service types, it uses new under the hood, so this... statements can be brought right from a controller to separate service.
There are several ways of doing controller inheritance. Here is another approach.
Regarding the original code, there is no 'controller iheritance' in Angular. And $scope prototypical inheritance assumes that
$scope.getName = function() {
return $scope.name;
};
returns $scope.name from the context where it was defined, it is MainController function in your case.
The problem that you're facing is actually based on core Javascript functionality.
You see, the confusion that you're facing stems from the mix of scoping, and prototypical inheritance. The properties are copied over, but the scoping remains the same, preventing you from accessing the variables that you expect to be able to access. To understand this better, perhaps instead of $scope, we can look at a simpler variable:
myApp.controller('MainController', ['$scope', function($scope) {
var a = 1;
$scope.getName = function() {
console.log(a); // -> 1
console.log(b); // Error! `Uncaught ReferenceError: b is not defined`
};
}]);
myApp.controller('Child1', ['$scope', function($scope) {
var b = 2;
}]);
Obviously, MainController doesn't know that Child1 defined some variable called b, so it errors. That variable is strictly out of lexical scope.
Likewise, if we renamed b to a, it won't turn the value in MainController to 2. This demonstrates exactly what's happening for you: you have three things called $scope, and only one is in the lexical scope.
Two options to fix it:
1) use this:
$scope.getName = function() {
return this.name;
};
The this solution works because of how Javascript determines "this" based on context. Basically, since it's attached to a given $scope Object, that Object's a good candidate. But Mozilla can explain this better than I can, so view their page on the topic here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
2) Otherwise, you can simply pass the $scope:
$scope.getName = function(item) {
return item.name;
};
If you want to overwrite name property in child scopes ,convert your primitive name property into object.
$scope.user = {};
$scope.user.name='main';
$scope.getName = function() {
return $scope.user.name;
};
And you should read https://github.com/angular/angular.js/wiki/Understanding-Scopes for detailed information.

A better way to use filters/objects in Angular controllers?

I'm setting a rootScope variable to maintain the state of the program. This works, but I don't think it's quite 'right'. Is there a better way to select an object from an array?
Here's my current code.
angular.module('myApp.controllers', [])
.controller('packingCtrl', ['$scope', '$http', '$filter', '$rootScope', function($scope, $http, $filter, $rootScope) {
$http.get('data/trayData.json').success(function(data) {
$scope.trays = data;
});
var currentOrder = $rootScope.currentlyPacking;;
$http.get('data/orderData.json').success(function(data) {
$scope.orders = data;
$scope.current = $filter('filter')($scope.orders, {orderId: currentOrder});
});
}])
Thanks in advance for any insight / best practices.
You can create a service to hold your state. Each service instance is a singleton, so when the service is injected into various controllers, all will see the same state.
var currentlyPackingSvc = function($http) {
var currentlyPacking = {
}
return {
currentlyPacking: currentlyPacking,
getTrays: function() { /* make $http call and update currentlyPacking */ },
getOrders: function() { /* make $http call and update currentlyPacking */ }
}
}
angular.service('currentlyPackingSvc', ['$http', currentlyPackingSvc]);
angular.controller('packingCtrl', ['$scope', '$http', '$filter', '$rootScope', 'currentlyPackingSvc'
function($scope, $http, $filter, $rootScope, currentlyPackingSvc) {
...
var currentOrder = currentlyPackingSvc.currentlyPacking;
...
}]);
Assuming you leave your 'currentlyPacking' property as an object, the changes should automatically be pushed to your scope.
This way, you have all your state isolated to one service that can be utilized anywhere.

Call a global function

I come to you because I do not know how to access two different controllers to a global function.
I try to access a generic function that should be called two different controllers. The disconnect feature allows you to disconnect from the application, but also reset the global variables.
controller_1 and controller_2:
function CONTROLLER_1 ($scope, $rootScope) {
$scope.disconnect = function() {
// RESET $rootScope variable
$rootScope.var1 = 0;
$rootScope.var2 = 0;
etc...
};
}
function CONTROLLER_2 ($scope, $rootScope) {
$scope.disconnect = function() {
// RESET $rootScope variable
$rootScope.var1 = 0;
$rootScope.var2 = 0;
etc...
};
}
I would like something like this:
function CONTROLLER_1 ($scope, $rootScope) {
$scope.disconnect = function() {
//CALL GLOBAL_METHOD
}
}
function CONTROLLER_2 ($scope, $rootScope) {
$scope.disconnect = function() {
//CALL GLOBAL_METHOD
}
}
My GLOBAL_METHOD {
// RESET $rootScope variable
$rootScope.var1 = 0;
$rootScope.var2 = 0;
etc...
}
Is it possible to have a similar solution in this case?
If I'm not precise enough, do not hesitate to ask me for more information
Thank you for your help.
It seems that you should use a service for that:
I can see two solutions:
1°) Since each controllers inherit from rootScope, you can define disconnect function on rootScope you will be able to call disconnect automatically:
angular.module('app').run(['$rootScope', function($rootScope) {
$rootScope.disconnect = function() {
$rootScope.var1 = 0;
$rootScope.var2 = 0;
};
}]);
angular.controller('Ctrl1', ['$scope', '$rootScope', function($scope, $rootScope) {
// Disconnect function is already define
$scope.applyDisconnect = function() {
$scope.disconnect(); // Or $rootScope.disconnect();
};
}]);
angular.controller('Ctrl2', ['$scope', '$rootScope', function($scope, $rootScope) {
// Disconnect function is already define
}]);
Be careful, you should not redefine disconnect function.
See this fiddle: http://jsfiddle.net/37cMw/2
2°) Use a dedicated service and inject it in your controllers:
angular.module('myApp').service('DisconnectService', ['$rootScope' function($scope) {
this.disconnect = function() {
$rootScope.var1 = 0;
$rootScope.var2 = 0;
};
}]);
angular.module('myApp').controller('Ctrl1', ['$scope', 'DisconnectService', function($scope, service) {
$scope.disconnect = function() {
service.disconnect();
};
}]);
angular.module('myApp').controller('Ctrl2', ['$scope', 'DisconnectService', function($scope, service) {
$scope.disconnect = function() {
service.disconnect();
};
}]);
See this fiddle: http://jsfiddle.net/QFvyj/
Second solution is my favourite since you don't pollute rootScope namespace.
Yes you can! Add the method to the .run of your module
angular.module('app').run(function($rootScope) {
$rootScope.GLOBAL_METHOD = function() {
//YOUR STUFF
})
})
And you can call this method from every controller like this:
$rootScope.GLOBAL_METHOD();
So in your case:
function CONTROLLER_1 ($scope, $rootScope) {
$scope.disconnect = function() {
$rootScope.GLOBAL_METHOD();
}
}
function CONTROLLER_2 ($scope, $rootScope) {
$scope.disconnect = function() {
$rootScope.GLOBAL_METHOD();
}
}
While the provided solution by user2874153 would probably work, it's best to avoid polluting the $rootScope. To share functions between controllers you should create a service.
You should also avoid declaring controllers in the global namespace, and should use the angular.module syntax instead (see http://docs.angularjs.org/guide/controller#setting-up-the-initial-state-of-a-object, the part where it says 'NOTE: ').
angular.module('myApp', []);
angular.module('myApp')
.controller('OneCtrl', function ($scope, SharedService) {
SharedService.doSomething();
});
angular.module('myApp')
.controller('TwoCtrl', function ($scope, SharedService) {
SharedService.doSomething();
});
angular.module('myApp')
.service('SharedService', function () {
return {
doSomething: function () {
// do something here.
}
};
});

AngularJS $injector.invoke - ParentController is not defined

I have 2 controllers defined:
var myApp = angular.module('nestedControllersModule',[]);
myApp.controller('ParentController', ['$scope', function($scope) {
}]);
myApp.controller('ChildController', ['$scope', '$injector', function($scope, $injector) {
$injector.invoke(ParentController, this, {$scope: $scope});
}]);
This gives: ReferenceError: ParentController is not defined.
This code works only if ParentController is defined as:
function ParentController($scope) {}
I am trying to inject the parent in the child as then I can inherit the common functions defined in the parent.
var myApp = angular.module('nestedControllersModule',[]);
myApp.controller('ParentController', ['$scope', function($scope) {
$scope.name = 'ParentName';
$scope.Type = 'ParentType';
$scope.clickme = function() {
alert('This is parent controller "ParentController" calling');
}
}]);
myApp.controller('ChildController', ['$scope', '$injector', '$ParentController', function($scope, $injector, $ParentController) {
$injector.invoke(ParentController, this, {$scope: $scope});
$scope.name = 'Child';
}]);
myApp.controller('ParentController', ['$scope', function($scope) {
}]);
myApp.controller('ChildController', ['$scope', 'ParentController', function($scope, ParentController) {
// ok now you have ParentController
}]);
But I think you need to use Services to share data/functions between Controllers or using PubSub model:
What's the correct way to communicate between controllers in AngularJS?
This reduces coupling between parts of your app.
This is a basic workaround to achieve what you're after:
var myApp = angular.module('nestedControllersModule',[]);
myApp.factory('ParentControllerFactory', function () {
function ParentControllerFactory($scope) {
$scope.name = 'ParentName';
$scope.Type = 'ParentType';
$scope.clickme = function() {
alert('This is parent controller "ParentController" calling');
}
}
return (ParentControllerFactory);
})
.controller('ParentController', ['$scope', '$injector', 'ParentControllerFactory', function ($scope, $injector, ParentControllerFactory) {
$injector.invoke(ParentControllerFactory, this, {
$scope: $scope
});
}])
.controller('ChildController', ['$scope', '$injector', 'ParentControllerFactory', function ($scope, $injector, ParentControllerFactory) {
$injector.invoke(ParentControllerFactory, this, {
$scope: $scope
});
}]);
I say workaround because it's probably worthwhile looking into properly implementing a service to manage any commonality as previously mentioned (or better yet, splitting commonality into directives, clickme for example is a good candidate)
...also note that $injector.invoke(ParentControllerFactory as it is above will most likely chuck a hissy fit if/when you minify your scripts later on, so be careful where and how it used.
Consider using the Mixin pattern possible by using the $controller service.
In your example, you would replace the $injector service with the $controller service:
var myApp = angular.module('nestedControllersModule',[]);
myApp.controller('ParentController', ['$scope', function($scope) {
$scope.name = 'ParentName';
$scope.Type = 'ParentType';
$scope.clickme = function() {
alert('This is parent controller "ParentController" calling');
}
}]);
myApp.controller('ChildController', ['$scope', '$controller', '$ParentController', function($scope, $controller, $ParentController) {
$controller('ParentController',{$scope: $scope})
$scope.name = 'Child';
}]);
This is a good overview of using the $controller service:
http://vadimpopa.com/split-large-angularjs-controllers-using-the-mixin-pattern/

AngularJs $scope undefined when controllers are inside a module

I'm trying to use the angular-seed template with the default settings. In controllers.js I use
angular.module('myApp.controllers', []).
controller('MyCtrl1', [function($scope) {
$scope.test = 'scope found!';
}])
.controller('MyCtrl2', [function() {
}]);
There the $scope is always undefined.
When I take the controller out of the module and register it globally it works fine. As here:
function MyCtrl1($scope) {
$scope.test = "scope found!";
}
MyCtrl1.$inject = ['$scope'];
Could someone explain to me why this is?
You cannot mix things like that. You need to decide on one of the two possibilities:
app = angular.module('test', []);
// possibility 1 - this is not safe for minification because changing the name
// of $scope will break Angular's dependency injection
app.controller('MyController1', function($scope) {
// ...
});
// possibility 2 - safe for minification, uses 'sc' as an alias for $scope
app.controller('MyController1', ['$scope', function(sc) {
// ...
}]);
I would not advise using the other syntax which declares Controller directly. Sooner or later with the growth of you app it will become hard to maintain and keep track. But if you must, there are 3 possibilities:
function myController1 = function($scope) {
// not safe for minification
}
function myController2 = ['$scope', function(sc) {
// safe for minification, you could even rename scope
}]
var myController3 = function(sc) {
// safe for minification, but might be hard
// to read if controller code gets longer
}
myController3.$inject = ['$scope'];
This is the proper way:
angular.module('myApp.controllers', []);
angular.module('myApp.controllers').controller('MyCtrl1', ['$scope', function($scope) {
}]);
I was also searching for that one, it seems that you need to type '$scope' before the function, as below:
angular.module('myApp.controllers', []).
controller('MyCtrl1', ['$scope', function($scope) {
$scope.test = 'scope found!';
}])
.controller('MyCtrl2', ['$scope',function() {
}]);
It kinda makes sense, I think it should be more clear though..
You can simply remove '[' and ']' when You are using $scope.
angular.module('myApp.controllers', []).
controller('MyCtrl1', function($scope) {
$scope.test = 'scope found!';
})
.controller('MyCtrl2', [
function() {
}
]);

Resources