I am using angular framework and trying to pass $scope to different controller in my app.
UPDATE:
My problem is I wan't obtain the $scope data until user click a button.
.controller('firstCtrl', function($scope) {
$scope.getTest = function(){
$scope.test1 = 'good.'
$scope.test2 = 'bad.'
…..more
}
})
I need to pass $scope object to different controller
.controller('secondCtrl', function($scope) {
console.log($scope.test1)
})
How do I do it? Thanks!
In order to share data between two controllers you need to use factory.
For more details, please watch this video: "AngularJS Video Tutorial: Sharing Data Between Controllers" by John Lindquist.
To share information between controllers, you use services. A service can be created like this:
//Create angular main app module
var app = angular.module('myApp', []);
//create a service
app.service('sharedService', function () {
this.test1 = [1, 2, 3, 4];
});
//one controller, injecting the service
app.controller('firstCtrl', function ($scope, sharedService) {
sharedService.test1[0] = 5;
console.log(sharedService.test1[0]) //5, 2, 3, 1
});
//two controller, injecting the same service
app.controller('secondCtrl', function ($scope, sharedService) {
sharedService.test[1] = 4;
console.log(sharedService.test1[0]) //5, 4, 3, 1
});
Here is an example I just created on jsFiddle:
http://jsfiddle.net/NHtFu/
Use custom events on the $rootScope
.controller('firstCtrl',['$rootScope', function($rootScope) {
$scope.getTest = function(){
$rootScope.your_object = {foo:bar}
$rootScope.$emit('custom_event');
}
}])
.controller('secondCtrl', function($scope,$rootScope) {
$rootScope.$on('custom_event',function(){
//do stuff
//$rootScope.your_object is available
})
});
You may need to unbind the root scope from that event if the controllers instantiate more then once
There may be an objection against 'polluting' the root scope but that's what its there for.
Related
I'm using ruby on rails with angularjs one, and testing it with teaspoon-jasmine for the first time and am running into issues. Basically, I have a controller that creates an empty array and upon load calls a factory method to populate that array. The Factory makes an http request and returns the data. Right now, i'm trying to test the controller, and i'm trying to test that 1) the factory method is called upon loading the controller, and 2) that the controller correctly assigns the returned data through it's callback. For a while I was having trouble getting a mocked factory to pass a test, but once I did, I realized I wasn't actually testing my controller anymore, but the code below passes. Any tips on how I can still get it to pass with mock, promises/callbacks, but accurately test my controller functionality. Or should I even test the this at all in my controller since it calls a factory method and just gives it a callback? My 3 files are below. Can anyone help here? It would be greatly appreciated
mainController.js
'use strict';
myApp.controller('mainController', [ 'mainFactory', '$scope', '$resource', function(factory, scope, resource){
//hits the /games server route upon page load via the factory to grab the list of video games
scope.games = [];
factory.populateTable(function(data){
scope.games = data;
});
}]);
mainFactory.js
'use strict';
myApp.factory('mainFactory', ['$http', '$routeParams', '$location', function(http, routeParams, location) {
var factory = {};
factory.populateTable = function(callback) {
http.get('/games')
.then(function(response){
callback(response.data);
})
};
return factory;
}]);
And finally my mainController_spec.js file
'use strict';
describe("mainController", function() {
var scope,
ctrl,
deferred,
mainFactoryMock;
var gamesArray = [
{name: 'Mario World', manufacturer: 'Nintendo'},
{name: 'Sonic', manufacturer: 'Sega'}
];
var ngInject = angular.mock.inject;
var ngModule = angular.mock.module;
var setupController = function() {
ngInject( function($rootScope, $controller, $q) {
deferred = $q.defer();
deferred.resolve(gamesArray);
mainFactoryMock = {
populateTable: function() {}
};
spyOn(mainFactoryMock, 'populateTable').and.returnValue(deferred.promise);
scope = $rootScope.$new();
ctrl = $controller('mainController', {
mainFactory: mainFactoryMock,
$scope: scope
});
})
}
beforeEach(ngModule("angularApp"));
beforeEach(function(){
setupController();
});
it('should start with an empty games array and populate the array upon load via a factory method', function(){
expect(scope.games).toEqual([])
mainFactoryMock.populateTable();
expect(mainFactoryMock.populateTable).toHaveBeenCalled();
mainFactoryMock.populateTable().then(function(d) {
scope.games = d;
});
scope.$apply(); // resolve promise
expect(scope.games).toEqual(gamesArray)
})
});
Your code looks "non-standard" e.g still using scope.
If you are just starting with angular I hardly recommend you to read and follow this:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
Angular controllers cannot be tested, extract the logic into factories/services and test from there.
I would like to do something like this:
app.config(function($routeProvider){
$routeProvider.when('products/list', {
controller: 'ProductListCtrl',
templateUrl : 'products/list/view.html',
resolve : { data : function(){
...
},
loadingTemplateUrl : 'general/loader.html'
}
});
I would like to have the loading page in a different view.
This would make the code in the view and controller of every page cleaner, (no <...ng-include ng-show="loading"...>). This would also mean that I don't have to $scope.$watch the data for changes. Is there a clean solution to do something similar (not necessarily in the .config method) or an alternative library to do this?
Assuming you want to show some general template for all state transitions while the data is resolved, my suggestion is to listen to the events fired by the routing library. This allows to use one central point to handle all state transitions instead of polluting the routing config (which I think will not be that easy to do).
Please see the docs for $routeChangeStart, $routeChangeSuccess and of course $routeChangeError at the angular router docs
Maybe someone could be interested in what I did: I created a new service and a new view directive. It could seem like a lot of work, but doing this was much easier than I had expected. The new service enables me to separate the main view from the loading view, that I could reuse in all pages of the application. I also provided the possibility to configure an error template url and error controller, for when the loading failed.
The Angular $injector, $templateRequest and $controller services do most of the work. I just had to connect a directive, that depends on these services, to the right event ($locationChangeSuccess), and to the promise, retrieved (using $q.all) from the resolve object's functions. This connection was done in the route service. The service selects the right template url and comtroller, and passes it on for the directive to handle.
A shortened version (with the getCurrentConfig method left out):
RouteService:
(function () {
'use strict';
// provider:
angular.module('pikcachu')
.provider('pikaRouteService', [function () {
var routeConfigArray;
var otherwiseRouteConfig;
//configuration methods
this.when = function (url, routeConfig){
routeConfigArray.push({url: url, routeConfig: routeConfig});
return this;
}
this.otherwise = function(routeConfig){
otherwiseRouteConfig = routeConfig;
return this;
}
// service factory:
this.$get = ['$rootScope', '$location', '$q', '$injector', '$templateRequest',
function ($rootScope, $location, $q, $injector, $templateRequest) {
function RouteService() {
this.setViewDirectiveUpdateFn = function(){ /*...*/ }
function init(){
$rootScope.$on('$locationChangeSuccess', onLocationChangeSuccess);
}
function onLocationChangeSuccess(){
// get the configuration based on the current url
// getCurrentConfig is a long function, because it involves parsing the templateUrl string parameters, so it's left out for brevity
var currentConfig = getCurrentConfig($location.url());
if(currentConfig.resolve !== undefined){
// update view directive to display loading view
viewDirectiveUpdateFn(currentConfig.loadingTemplateUrl, currentConfig.loadingController);
// resolve
var promises = [];
var resolveKeys = [];
for(var resolveKey in currentConfig.resolve){
resolveKeys.push(resolveKey);
promises.push($injector.invoke(resolve[resolveKey]));
}
$q.all(promises).then(resolveSuccess, resolveError);
function resolveSucces(resolutionArray){
// put resolve results in an object
var resolutionObject = {};
for(var i = 0; i< promises.length;++i){
resolved[resolveKeys[i]] = resolutionArray[i];
}
viewDirectiveUpdateFn(currentConfig.errorTemplateUrl, currentConfig.errorController);
}
function resolveError(){
viewDirectiveUpdateFn(currentConfig.errorTemplateUrl, currentConfig.errorController);
}
}
}
init();
}
return new RouteService();
}]
})();
View directive
(function () {
'use strict';
angular.module('pikachu')
.directive('pikaView', ['$templateRequest', '$compile', '$controller', 'pikaRouteService', function ($templateRequest, $compile, $controller, pikaRouteService) {
return function (scope, jQdirective, attrs) {
var viewScope;
function init() {
pikaRouteService.listen(updateView);
}
function updateView(templateUrl, controllerName, resolved) {
if(viewScope!== undefined){
viewScope.$destroy();
}
viewScope = scope.$new();
viewScope.resolved = resolved;
var controller = $controller(controllerName, { $scope: viewScope });
$templateRequest(templateUrl).then(onTemplateLoaded);
function onTemplateLoaded(template, newScope) {
jQdirective.empty();
var compiledTemplate = $compile(template)(newScope);
jQdirective.append(compiledTemplate);
}
}
init();
};
}
]);
})();
I have these controller codes from different js files.
NLGoalsCtrl.js
angular.module('mysite').controller('NLGoalsCtrl', function ($scope) {
$scope.goals_selected = [];
});
NLSessionsCtrl.js
angular.module('mysite').controller('NLSessionsCtrl', function ($scope) {
//access $scope.goals_selected here
});
I need to be able to access the $scope.goals_selected from the NLSessionsCtrl. How do I do this? Thanks guys.
Use a factory/service to store the goals which will be responsible for sharing data among the controllers.
myApp.factory('myService', [function() {
var goals = {};
return {
getGoals: function() {
return goals
},
setGoals: function(op) {
goals = op;
},
}
}])
.controller('NLGoalsCtrl', [function($scope, myService) {
$scope.goals_selected = {};
//Update goals_selected
myService.setGoals($scope.goals_selected );
}])
.controller('NLSessionsCtrl', [function($scope, myService) {
//Fetch
$scope.goals_selected = myService.getGoals();
}]);
$scope is an "object" that "binds" to DOM element where you apply controller. So the context of $scope remains inside the controller.
If you want to access the variables in 2 controllers , try to share it via a service/Factory or $rootScope
Here is a sample App
the one that you want to access in any controller assign it to the $rootScope.
and access it in any controller you want.
I'm trying to pass a Javascript object into my AngularJS controller and having no luck.
I've tried passing it into an init function:
<div ng-controller="GalleryController" ng-init="init(JSOBJ)">
And on my controller side:
$scope.init = function(_JSOBJ)
{
$scope.externalObj = _JSOBJ;
console.log("My Object.attribute : " + _JSOBJ.attribute );
};
Though the above doesn't seem to work.
Alternatively, I've tried pulling the attribute from the AngularJS controller that I am interested in for use in an inline <script> tag:
var JSOBJ.parameter = $('[ng-controller="GalleryController"]').scope().attribute ;
console.log("My Object.parameter: " + JSOBJ.attribute );
Can anyone tell me: what is the best practice regarding this?
I don't have the option to rewrite the plain Javascript object as it is part of a 3rd party library.
Let me know if I need to provide further clarification and thanks in advance for any guidance!
-- JohnDoe
Try setting it as a value:
angular.module('MyApp')
.value('JSOBJ', JSOBJ);
Then inject it into your controller:
angular.module('MyApp')
.controller('GalleryController', ['JSOBJ', function (JSOBJ) { ... }]);
Since your object is a part of third-party library you have to wrap it app in something angular.
Your options are:
if it is jquery pluging init'ed for a DOM node etc you can create a directive
example
myApp.directive('myplugin', function myPlanetDirectiveFactory() {
return {
restrict: 'E',
scope: {},
link: function($scope, $element) { $($element).myplugin() }
}
});
if it is something you need to init you can use factory or service
example
myApp.service(function() {
var obj = window.MyLib()
return {
do: function() { obj.do() }
}
})
if it is plain javascript object you can use value
example
myApp.value('planet', { name : 'Pluto' } );
if it is constant ( number, string , etc) you can use constant
example
myApp.constant('planetName', 'Greasy Giant');
Reference to this doc page: https://docs.angularjs.org/guide/providers
Also I strongly encourage you to read answer to this question: "Thinking in AngularJS" if I have a jQuery background?
If you have JSOBJ accessible via global scope (via window), than you can access it through window directly as in plain JavaScript.
<script>
...
window.JSOBJ = {x:1};
...
</script>
Option 1.
<script>
angular.module('app',[]).controller('ctrl', ['$scope', function($scope) {
$scope.someObject = window.JSOBJ;
}]);
</script>
However it makes the controller code not testable. Therefore $window service may be used
Option 2.
<script>
angular.module('app',[]).controller('ctrl', ['$scope', '$window', function($scope, $window) {
$scope.someObject = $window.JSOBJ;
}]);
</script>
If you want to make some abstraction layer to make your controller agnostic for the source from where you get the object, it is recommended to define service which is responsible for fetching value and then inject it to your controllers, directives, etc.
Option 3.
<script>
angular.module('app',[])
.service('myService', function() {
var JSOBJ = ...; // get JSOBJ from anywhere: localStorage, AJAX, window, etc.
this.getObj = function() {
return JSOBJ;
}
})
.controller('ctrl', ['$scope', 'myService', function($scope, myService) {
$scope.someObject = myService.getObj();
}]);
</script>
Besides of it, for simple values you can define constant or value that may be injected to any controller, service, etc.
Option 4.
<script>
angular.module('app',[]).
value('JSOBJ', JSOBJ).
controller('ctrl', ['$scope', 'JSOBJ', function($scope, JSOBJ) {
$scope.someObject = JSOBJ;
}]);
</script>
I have an object that updates via socket.io.
var my_dynobj;
socket.on('update dynobj', function(dynobj){
my_dynobj = dynobj;
});
I want to have it in my angular app as a factory or a service that could be injected as a dependency.
In there I will want to attach my_object to a $scope so that it can be plugged into HTML {{my_object}} and will update whenever it is itself updated in its own factory definition via socket. socket.on('update object', …
But I can't figure out a way to make that happen.
angular.module('app', [])
.factory('dynobj_factory', [
function(){
var my_dynobj;
socket.on('update dynobj', function(dynobj){
my_dynobj = dynobj;
});
return {what?}
}])
.controller('ctrl', ['$scope', 'dynobj_factory',
function($scope, dynobj) {
$scope.my_dynobj = dynobj.what?
}])
<div>{{my_dynobj}}</div>
For
return {/* what ? */}
You'll need to return something like;
return {dynamicObj: my_dynobj}
And that will do the trick. Any changes to my_dynobj will reflect through all directives, controllers, factories it is injected into.