Registering global variable accessable from VIews in AngularJS - javascript

I want t expose images storage URL to views so I can use it like this in all my views:
<img ng-src="{{storageUrl}}/logo.png"
Right now I have config service which I inject in my every controller, and then expose storageUrl variable to view via $scope.
angular.module('myApp').controller('MyCtrl', [
'$scope',
'AppConfigService',
function ($scope, AppConfigService) {
$scope.storageUrl = AppConfigService.storageUrl;
}
But the problem is that almost in every controller I need to inject this service and expose this varialbe to the view. I don't want to duplicate code so much. So i'm intersting in other ways to globally expose some config variable to the ALL views. What you can suggest?
Thanks

The "global" scope way
Set it on $rootScope. Although global scope is ill-advised.
Also, if you must use global scope ($rootScope) to track this, you can set it in a run() block, and it will be set as soon as the application is ready:
angular.module('myApp').run([
'$rootScope', 'AppConfigService',
function($rootScope, AppConfigService) {
$rootScope.storageUrl = AppConfigService.storageUrl
}
]);
The problem with global scope is that any other modules you load into your app could easily clobber your variable on $rootScope and it will be very hard to debug.
Better way: Use the service directly in an "outer controller":
app.controller('OuterCtrl', [
'$scope', 'AppConfigService',
function($scope, AppConfigService) {
$scope.config = AppConfigService;
}
]);
Then wrap your whole app in that controller:
<body ng-controller="OuterCtrl">
<div ng-controller="MyCtrl"> other stuff here </div>
</body>
Why does this work? Because all controllers and directives under this controller prototypically inherit their scope from this controller's scope.

One option is to create a directive for this, and use that directive everywhere instead of ng-src. Something like this:
myModule.directive('mySrc', ['AppConfigService', function(AppConfigService) {
return {
restrict: 'A',
compile: function(element, attrs) {
element.attr('src', AppConfigService.storageUrl + attrs.mySrc);
}
};
}]);
Then you can just use relative paths for your images everywhere
<img my-src="/logo.png" />

Related

Global function not defined in Angular controller code

I have a global function declared as follows (only necessary bits):
initiateCheckList = {};
$(function() {
initiateCheckList = function() {
...
}
And then I have a function inside an Angular controller that tries to call that function, but I get the error initiateCheckList is not defined when the following function is called:
$scope.updateSuburbs = function () {
$scope.suburbs.getModel($scope.areas.areaId);
initiateCheckList();
};
This function is nested inside a controller, and is bound to a dropdown change event like so:
<select class="form-control" ng-model="areas.areaId" ng-change="updateSuburbs()">
What is Angular doing so that I can't call a global function, and how can I fix things so I can call it?
Below is the ideal way of defining global properties/function in angular:
You've got basically 2 options for "global" variables:
use a $rootScope [http://docs.angularjs.org/api/ng.$rootScope][1]
use a service [http://docs.angularjs.org/guide/services][1]
$rootScope is a parent of all scopes so values exposed there will be visible in all templates and controllers. Using the $rootScope is very easy as you can simply inject it into any controller and change values in this scope. It might be convenient but has all the problems of global variables.
Services are singletons that you can inject to any controller and expose their values in a controller's scope. Services, being singletons are still 'global' but you've got far better control over where those are used and exposed.
Using services is a bit more complex, but not that much, here is an example:
var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
return {
name : 'anonymous',
printData: function() {console.log('Print Data');}
};
});
and then in a controller:
function MyCtrl($scope, UserService) {
$scope.name = UserService.name;
UserService.printData();
}
I hope it would help you to understand adding global properties/functions in angular.

How to add a consistent variable or function to every AngularJS $scope?

I made an App (build using AngularJS 1.X) that has a lot of directives, most of which have there own isolated scope. So, for every new $scope i assign Underscore.js + Underscore.string variables _ and s to them, like so:
controller: function($scope){
$scope._ = _;
$scope._s = s;
$scope.foo = 'my-example-here';
}
Then do some cool stuff like this within there isolated scope templates:
<div ng-bind='s.humanize(some_id)'></div> # output: "My example here"
Unfortunately you cannot access global variables in these scopes.
So, ultimately, how to add a consistent variable or function to every AngularJS $scope?
Try this:
app.controller('MyCtrl', function($rootScope) {
$rootScope.derp = "foo!";
});
app.directive('myDir', function($rootScope) {
return {
scope { ... },
controller: function($rootScope) {
console.log($rootScope.derp);
}
};
});
Make sure to put ng-controller="MyCtrl" directive somewhere on the parent DOM element of your custom directive like so:
<div ng-controller="MyCtrl">
<div my-dir></div>
</div>
Try it please.
EDIT
Also, you can use constants as described by #georgeawg in comment.

Why can't I inject my service?

I have the following new service:
var SignatureService = function ($scope) {
this.announce = function () {
alert($scope.specificName);
}
};
SignatureService.$inject = ['$scope'];
This is declared for the app as follows:
MyAngularApp.service('SignatureService', SignatureService);
Several other services are added to the app in exactly the same way, and they all seem to work OK. I then have a controller declared and defined as follows:
MyAngularApp.controller('MyController', MyController);
...
var MyController = function ($scope, Service1, Service2, $location, $modal, SignatureService) {
...
}
MyController.$inject = ['$scope', 'Service1', 'Service2', '$location', '$modal', 'SignatureService'];
I am simply using the slightly unconvcentionaly manner of defining the servivce and injecting it that is standard in the app I am working on, as this works for all existing services, and I would prefer to simply slot mine in as per standard.
When the controller loads, I get an [$injector:unpr] in the browser console, with the error info:
$injector/unpr?p0=$scopeProvider <- $scope <- SignatureService
You can't inject $scope into your custom service. It just doesn't make sense since SignatureService can be injected anywhere including other services and other controlles. What $scope is supposed to be if you say inject it into two nested controllers, which one should be injected?
Scope object ($scope) is always associated with some DOM node, it is attached to it. That's why you see $scope in controllers and directives. And this is the reason why you can't have it in service: services are not related to specific DOM elements. Of course you can inject $rootScope but this is unlikely what you need in your question.
Summary: $scope is created from the $rootScope and injected in necessary controllers, but you can't injected it into custom service.
UPD. Based on comments you want to use service to define reusable controller methods. In this case I would go with what I call mixin approach. Define methods in the service and mix them in the necessary controllers.
app.service('controllerMixin', function() {
this.getName = function() {
alert(this.name);
};
});
and then extend controller scope with angular.extend:
app.controller('OneController', function($scope, controllerMixin) {
angular.extend($scope, controllerMixin);
// define controller specific methods
});
app.controller('TwoController', function($scope, controllerMixin) {
angular.extend($scope, controllerMixin);
// define controller specific methods
});
This is pretty effective, because controllerMixin doesn't have any notion of $scope whatsoever, when mixed into controller you refer to the scope with this. Also service doesn't change if you prefer to use controllerAs syntax, you would just extend this:
angular.extend(this, controllerMixin);
Demo: http://plnkr.co/edit/ePhSF8UttR4IgeUjLRSt?p=info

Angular root scope and scope issues

I have noticed that in one of my controllers I am getting $rootScope and $scope injected, and they both point to the same object.
Furthermore, in all my other controllers, the $scope object is shared. So whenever I inject scope, it contains the properties/methods assigned to it in all the other controllers that have so far been instantiated.
This isn't an app that I worked on from the beginning and it's pretty massive. I haven't seen this behavior before and I don't know where to begin diagnosing it. Any ideas what is causing this?
The way that we are setting up our controllers/directives is pretty standard and it looks like this:
angular.module('myApp')
.directive('mainNav', function() {
return {
restrict: 'A',
templateUrl: 'scripts/directives/mainNav/mainNav.html',
controller: 'mainNavCtrl',
replace: true,
link: function(scope, element) {
//Do DOM-related stuff
});
}
};
})
.controller('mainNavCtrl', function($rootScope, $scope, $state) {
//Do controller stuff
});
We do also configure our app as follows:
angular.module('myApp', ['ui.router', 'kendo.directives'])
.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('app', {
url: '/',
templateUrl: 'views/app.html',
resolve: {
//Fetch stuff
}
})
; });
In response to Kursad Gulseven's comment, this is what I'm seeing in Batarang:
The scope with ID 002 gets passed in as $scope and $rootScope to the first controller. When properties are added to $scope, they show up on $rootScope. Then all the other controllers are receiving the scope with ID 00A. So properties added to $scope in those controllers are visible to all other controllers getting $scope injected.
$rootScope and $scope are not the same object.
If you have an object named anObject in root scope and it has an attribute named anAttribute; then you can access that attribute using $scope like this:
$scope.anObject.anAttribute
That's because Angular looks up parent scopes if it cannot find an object in $scope.
UPDATE:
Your mainNav directive should have an inherited child scope by adding scope: true.
When "scope" is false, which is the default setting for directives, the parent controller and directive's controller share the same scope object. Setting scope true will create a new child scope for the directive.

AngularJS: dynamically assign controller from ng-repeat

I'm trying to dynamically assign a controller for included template like so:
<section ng-repeat="panel in panels">
<div ng-include="'path/to/file.html'" ng-controller="{{panel}}"></div>
</section>
But Angular complains that {{panel}} is undefined.
I'm guessing that {{panel}} isn't defined yet (because I can echo out {{panel}} inside the template).
I've seen plenty of examples of people setting ng-controller equal to a variable like so: ng-controller="template.ctrlr". But, without creating a duplicate concurrant loop, I can't figure out how to have the value of {{panel}} available when ng-controller needs it.
P.S. I also tried setting ng-controller="{{panel}}" in my template (thinking it must have resolved by then), but no dice.
Your problem is that ng-controller should point to controller itself, not just string with controller's name.
So you might want to define $scope.sidepanels as array with pointers to controllers, something like this, maybe:
$scope.sidepanels = [Alerts, Subscriptions];
Here is the working example on js fiddle http://jsfiddle.net/ADukg/1559/
However, i find very weird all this situation when you might want to set up controllers in ngRepeat.
To dynamically set a controller in a template, it helps to have a reference to the constructor function associated to a controller. The constructor function for a controller is the function you pass in to the controller() method of Angular's module API.
Having this helps because if the string passed to the ngController directive is not the name of a registered controller, then ngController treats the string as an expression to be evaluated on the current scope. This scope expression needs to evaluate to a controller constructor.
For example, say Angular encounters the following in a template:
ng-controller="myController"
If no controller with the name myController is registered, then Angular will look at $scope.myController in the current containing controller. If this key exists in the scope and the corresponding value is a controller constructor, then the controller will be used.
This is mentioned in the ngController documentation in its description of the parameter value: "Name of a globally accessible constructor function or an expression that on the current scope evaluates to a constructor function." Code comments in the Angular source code spell this out in more detail here in src/ng/controller.js.
By default, Angular does not make it easy to access the constructor associated to a controller. This is because when you register a controller using the controller() method of Angular's module API, it hides the constructor you pass it in a private variable. You can see this here in the $ControllerProvider source code. (The controllers variable in this code is a variable private to $ControllerProvider.)
My solution to this issue is to create a generic helper service called registerController for registering controllers. This service exposes both the controller and the controller constructor when registering a controller. This allows the controller to be used both in the normal fashion and dynamically.
Here is code I wrote for a registerController service that does this:
var appServices = angular.module('app.services', []);
// Define a registerController service that creates a new controller
// in the usual way. In addition, the service registers the
// controller's constructor as a service. This allows the controller
// to be set dynamically within a template.
appServices.config(['$controllerProvider', '$injector', '$provide',
function ($controllerProvider, $injector, $provide) {
$provide.factory('registerController',
function registerControllerFactory() {
// Params:
// constructor: controller constructor function, optionally
// in the annotated array form.
return function registerController(name, constructor) {
// Register the controller constructor as a service.
$provide.factory(name + 'Factory', function () {
return constructor;
});
// Register the controller itself.
$controllerProvider.register(name, constructor);
};
});
}]);
Here is an example of using the service to register a controller:
appServices.run(['registerController',
function (registerController) {
registerController('testCtrl', ['$scope',
function testCtrl($scope) {
$scope.foo = 'bar';
}]);
}]);
The code above registers the controller under the name testCtrl, and it also exposes the controller's constructor as a service called testCtrlFactory.
Now you can use the controller in a template either in the usual fashion--
ng-controller="testCtrl"
or dynamically--
ng-controller="templateController"
For the latter to work, you must have the following in your current scope:
$scope.templateController = testCtrlFactory
I believe you're having this problem because you're defining your controllers like this (just like I'm used to do):
app.controller('ControllerX', function() {
// your controller implementation
});
If that's the case, you cannot simply use references to ControllerX because the controller implementation (or 'Class', if you want to call it that) is not on the global scope (instead it is stored on the application $controllerProvider).
I would suggest you to use templates instead of dynamically assign controller references (or even manually create them).
Controllers
var app = angular.module('app', []);
app.controller('Ctrl', function($scope, $controller) {
$scope.panels = [{template: 'panel1.html'}, {template: 'panel2.html'}];
});
app.controller("Panel1Ctrl", function($scope) {
$scope.id = 1;
});
app.controller("Panel2Ctrl", function($scope) {
$scope.id = 2;
});
Templates (mocks)
<!-- panel1.html -->
<script type="text/ng-template" id="panel1.html">
<div ng-controller="Panel1Ctrl">
Content of panel {{id}}
</div>
</script>
<!-- panel2.html -->
<script type="text/ng-template" id="panel2.html">
<div ng-controller="Panel2Ctrl">
Content of panel {{id}}
</div>
</script>
View
<div ng-controller="Ctrl">
<div ng-repeat="panel in panels">
<div ng-include src="panel.template"></div>
</div>
</div>
jsFiddle: http://jsfiddle.net/Xn4H8/
Another way is to not use ng-repeat, but a directive to compile them into existence.
HTML
<mysections></mysections>
Directive
angular.module('app.directives', [])
.directive('mysections', ['$compile', function(compile){
return {
restrict: 'E',
link: function(scope, element, attrs) {
for(var i=0; i<panels.length; i++) {
var template = '<section><div ng-include="path/to/file.html" ng-controller="'+panels[i]+'"></div></section>';
var cTemplate = compile(template)(scope);
element.append(cTemplate);
}
}
}
}]);
Ok I think the simplest solution here is to define the controller explicitly on the template of your file. Let's say u have an array:
$scope.widgets = [
{templateUrl: 'templates/widgets/aWidget.html'},
{templateUrl: 'templates/widgets/bWidget.html'},
];
Then on your html file:
<div ng-repeat="widget in widgets">
<div ng-include="widget.templateUrl"></div>
</div>
And the solution aWidget.html:
<div ng-controller="aWidgetCtrl">
aWidget
</div>
bWidget.html:
<div ng-controller="bWidgetCtrl">
bWidget
</div>
Simple as that! You just define the controller name in your template. Since you define the controllers as bmleite said:
app.controller('ControllerX', function() {
// your controller implementation
});
then this is the best workaround I could come up with. The only issue here is if u have like 50 controllers, u'll have to define them explicitly on each template, but I guess u had to do this anyway since you have an ng-repeat with controller set by hand.

Categories

Resources