accessing rootScope in directive - javascript

I've tried a million ways to access a rootScope variable from within this elasticui based directive with zero luck. Here is the directive, bearing in mind $rootScope is indeed being passed into its parent controller.
var elasticui;
(function (elasticui) {
var directives;
(function (directives, rootScope) {
var IndexDirective = (function () {
function IndexDirective() {
var directive = {};
directive.restrict = 'EAC';
directive.controller = elasticui.controllers.IndexController;
directive.link = function (scope, element, attrs, indexCtrl, rootScope) {
console.log("Updated Index: " + $rootScope.esIndex);
indexCtrl.indexVM.index = scope.$eval(attrs.euiIndex);
};
return directive;
}
return IndexDirective;
})();
directives.IndexDirective = IndexDirective;
directives.directives.directive('euiIndex', IndexDirective);
})(directives = elasticui.directives || (elasticui.directives = {}));
})(elasticui || (elasticui = {}));
Elsewhere I'm setting $rootScope.esIndex to the index I'd like to point to but I'm getting "$rootScope is not defined". I've tried setting the directive's scope property to false, tried setting up a watcher on $rootScope.esIndex as part of the link functions return value but no matter what I do, I can't seem to figure it out.
I think part of the problem for me is this structure is a little cryptic from a directive standpoint for a newer angular person to digest. This is a block in a far larger set of directive definitions so it's just hard for me to grasp.
Any ideas how I can easy grab $rootScope in here?
Thanks a TON in advance!

Seeing more of your code would help. Specifically when you say "$rootScope is indeed being passed". However, with this limited info why don't you try changing this line:
(directives = elasticui.directives || (elasticui.directives = {}));
to something like
(directives = elasticui.directives || (elasticui.directives = {}, rootScope));
At a glance it appears your self executing function isn't being passed the rootScope parameter it expects. Good luck!
edit: Also
console.log("Updated Index: " + $rootScope.esIndex);
Should probably not have the dollar sign.
edit: Comments have much more info!

Related

Is there any downside to using $injector.get() to get my dependencies in Angular?

My team wants to move to a style of dependency injection that is closer to the CommonJS/Node JS syntax for out Angular codebase:
var myDependency = require('myDependency');
I've started using $injector.get() directly inside at the top of my functions, so far with no obvious trouble. That means I've converted this:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['serviceA', 'serviceB', 'serviceC', function(serviceA, serviceB, serviceC) {
//My code here
}])
Into:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['$injector', function($injector) {
var serviceA = $injector.get('serviceA');
var serviceB = $injector.get('serviceB');
var serviceC = $injector.get('serviceC');
//My code here
}]);
Is there something I'm missing. Does not declaring the required dependencies outside of the function definition cause any sort of performance issues?
Note: this answer has not been tested.
After digging into the angular's code, I find this:
// in function createInjector
function provider(name, provider_) {
...
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
...
return providerCache[name + providerSuffix] = provider_;
}
...
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
...
// in function createInternalInjector
function invoke (...){
...
for (i = 0, length = $inject.length; i < length; i++) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
...
return fn.apply(self, args);
}
function instantiate(...){
...
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
...
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
Its calls seems like:
service invoked factory invoked instantiate
factory invoked provider invoked instantiate
So I think your questions is equal to Is calling $injector.get() one by one has the same performance as calling $injector.instantiate() once?
As the code shows, instantiate invoked invoke which actually invoked getService for each services injected by you. And $injector.get is just binding to getService.
So the answer to my equal question is True.
And the answer to your question is No, their performance is very close.
Please correct me if I'm wrong, thank you!
There are no significant difference between both pieces of code in the case of a service. $injector.get(...) call doesn't provide any overhead. But it provides a significant amount of extra characters per dependency.
There is a difference when the same thing is done with injectables that have local dependencies - controllers and route/state resolvers.
When these dependencies
app.controller('SomeCtrl', function ($scope, service) { ... });
are replaced with $injector.get(...), it will choke on $scope - it is local dependency. And with other dependencies being retrieved like that, controller loses its testability. The dependencies cannot be mocked with
$controller('SomeCtrl', { service: mockedService });
I personally don't see how $injector.get(...) may benefit the style of the project (and haven't seen a good style guide that would suggest it).
Node uses require function because it works for it, not because it is better or cooler. The alternative would be to pack each Node script into into AMD-like wrappers, which would be painful. Fortunately, we already have wrappers around Angular units!

How do I use parameters + dependency injection in a service

Here is what I want to do
(function(){angular.module('app').factory("loadDataForStep",ls);
ls.$inject = ['$scope','stepIndex']
function ls ($scope, stepIndex) {
if ($routeParams.code != undefined) {
$scope.code = $routeParams.code;
$scope.retrieveData($routeParams.code);
}
$rootScope.stepIndex = stepIndex-1;
$rootScope.currentStep = stepIndex;
$rootScope.resultsLoaded = false;
if (!fieldDefinitionsLoaded) {
loadAllElementsDefinition();
loadStepsWithOverrides(stepIndex, loadStep);
} else {
loadStep(stepIndex);
}
}
})();
I know there are plenty of issues here, but the issue of the moment (the question) is How do I get $scope injected and pass step index as a parameter? As you can see, $scope needs to come from angular, but I need to provide stepIndex.
Any help is appreciated.
Service don't have scope So can't inject scope in factory.
Scope is an instance of controller.
If you wanna to deal scope object in factory then declare variable in factory then bind factory with scope variable.
Then try kinda like this
var app = angular.module("app",[]);
app.factory("mySvc",function(){
return {
dataList : []
}
});
app.controller("myCtrl",function($scope,mySvc){
$scope.model=mySvc;
console.log($scope.model);
});

How can I stop having to type "$scope" before every variable and function in my Angular code?

It's really annoying to have to type $scope before any of the fields or methods I use. I feel like I'm doing something wrong. I've seen some people use methods instead of controllers and use this, but I really would like something like:
use($scope) {
var one = 'test';
var two = function(v) { return v * 2 };
two(one);
}
instead of:
$scope.one = 'test';
$scope.two = function(v) { return v * 2 };
$scope.two($scope.one);
Maybe I just don't get how to write code the angular way.
If you're using $scope purely to bind variables to your view, then you should look into controllerAs syntax. When you use controller as, a controller that looks like this:
app.controller('MainCtrl', function ($scope) {
$scope.title = 'Some title';
});
would instead look like this:
app.controller('MainCtrl', function () {
this.title = 'Some title';
});
The recommended way is the second way that you posted, as it's the way that most programmers are familiar with, and doesn't lead to any weird edge cases.
In JavaScript you can technically use a with statement to define a scope for subsequent commands, though according to MDN it is not recommended since it can cause bugs and compatibility issues.
WARNING: USE OF THE WITH STATEMENT IN JAVASCRIPT IS NOT RECOMMENDED. The following example is for completeness only:
var a, x, y;
var r = 10;
with (Math) {
a = PI * r * r;
x = r * cos(PI);
y = r * sin(PI / 2);
}
More info on the with statement on MDN.

Angular binding to service value not updating

I cannot get a binded service value to update when it is changed. I have tried numerous methods of doing so but none of them have worked, what am I doing wrong? From everything I have seen, this seems like it should work...
HTML:
<div class="drawer" ng-controller="DrawerController">
{{activeCountry}}
</div>
Controller:
angular.module('worldboxApp')
.controller('DrawerController', ['$scope', 'mapService', function($scope, mapService) {
$scope.$watch(function() { return mapService.activeCountry }, function(newValue, oldValue) {
$scope.activeCountry = mapService.activeCountry;
});
}]);
Service:
angular.module('worldboxApp').
service('mapService', function(dbService, mapboxService, userService) {
this.init = function() {
this.activeCountry = {};
}
this.countryClick = function(e) {
this.activeCountry = e.layer.feature;
};
this.init();
});
I put a break point to make sure the mapService.activeCountry variable is being changed, but all that ever shows in the html is {}.
If you work with objects and their properties on your scope, rather than directly with strings/numbers/booleans, you're more likely to maintain references to the correct scope.
I believe the guideline is that you generally want to have a '.' (dot) in your bindings (esp for ngModel) - that is, {{data.something}} is generally better than just {{something}}. If you update a property on an object, the reference to the parent object is maintained and the updated property can be seen by Angular.
This generally doesn't matter for props you're setting and modifying only in the controller, but for values returned from a service (and that may be shared by multiple consumers of the service), I find it helps to work with an object.
See (these focus on relevance to ngModel binding):
https://github.com/angular/angular.js/wiki/Understanding-Scopes
If you are not using a .(dot) in your AngularJS models you are doing it wrong?
angular.module('worldboxApp', []);
/* Controller */
angular.module('worldboxApp')
.controller('DrawerController', ['$scope', 'mapService',
function($scope, mapService) {
//map to an object (by ref) rather than just a string (by val), otherwise it's easy to lose reference
$scope.data = mapService.data;
$scope.setCountry = setCountry; //see below
function setCountry(country) {
// could have just set $scope.setCountry = mapService.setCountry;
// however we can wrap it here if we want to do something less generic
// like getting data out of an event object, before passing it on to
// the service.
mapService.setCountry(country);
}
}
]);
/* Service */
angular.module('worldboxApp')
.service('mapService', ['$log',
function($log) {
var self = this; //so that the functions can reference .data; 'this' within the functions would not reach the correct scope
self.data = {
activeCountry: null
}; //we use an object since it can be returned by reference, and changing activeCountry's value will not break the link between it here and the controller using it
_init();
function _init() {
self.data.activeCountry = '';
$log.log('Init was called!');
}
this.setCountry = function _setCountry(country) {
$log.log('setCountry was called: ' + country);
self.data.activeCountry = country;
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="worldboxApp">
<div ng-controller="DrawerController">
<button ng-click="setCountry('USA')">USA</button>
<br />
<button ng-click="setCountry('AUS')">AUS</button>
<br />Active Country: {{data.activeCountry}}
</div>
</div>
In some case $watch is not working with factory object. Than you may use events for updates.
app.factory('userService',['$rootScope',function($rootScope){
var user = {};
return {
getFirstname : function () {
return user.firstname;
},
setFirstname : function (firstname) {
user.firstname = firstname;
$rootScope.$broadcast("updates");
}
}
}]);
app.controller('MainCtrl',['userService','$scope','$rootScope', function(userService,$scope,$rootScope) {
userService.setFirstname("bharat");
$scope.name = userService.getFirstname();
$rootScope.$on("updates",function(){
$scope.name = userService.getFirstname();
});
}]);
app.controller('one',['userService','$scope', function(userService,$scope) {
$scope.updateName=function(){
userService.setFirstname($scope.firstname);
}
}]);
Here is the plunker
Note:- In Some case if broadcast event is not fired instantly you may use $timeout. I have added this in plunker and time depends on your needs. this will work for both factories and services.

Angular save watch expression from attr in directive

I want to pass some html to attr in directive, the problem i have that what i do does not save binding it is only display the initial value, how can i accomplish data binding here?
function link( $scope, elem, attrs, ctrl, transclude ) {
$scope.dropDownClass = attrs.ddClass || 'default';
var main = $compile(attrs.main)($scope);
elem.find('button').first().append(main);
}
<my-drop-down main='<a><img src="images/flags/en.png" alt="en"/>{{name}}</a>'>
<li><a><img src="images/flags/en.png" alt="en"/>En</a></li>
</my-drop-down>
I want the {{name}} to still be binding to my controller.
http://plnkr.co/edit/IrF1dIZslCEQf0FOfNJi?p=preview
Okay, I took a quick look. From what I could see, the main attribute is being evaluated before your attr lookup. So, your actually compiling <a>World</a> not <a>{{name}}</a>.
I don't think there is a way to tell angular not to evaluate an attribute (there is ngNonBindable for markup, but that doesn't help us). I see two solutions for your problem.
Option #1: You could pull out your desired template string from an attribute and, instead, attach it to your scope in MainCtrl. That looks like this: http://plnkr.co/edit/M6GZJDVHuW8op2Zo11mJ?p=preview
// Markup:
<my-drop-down> ...
// MainCtrl:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.main = '<a>{{name}}</a>';
});
// Then, in your directive link:
function link($scope, elem, attrs, ctrl, transclude ) {
$scope.dropDownClass = attrs.ddClass || 'default';
// Use scope.main, instead of attrs.main:
var main = $compile($scope.main)($scope);
elem.find('button').first().append(main);
}
Option #2: If you wanted to keep the passing of the template string in an attribute we cannot, as far as I can tell, use curly braces and expect them to be passed through as curly braces. So, we could try to use something else that is unique enough for our code. I chose %%, but it could be anything you wanted, really. That would look like this: http://plnkr.co/edit/760CFsq9sF9lIBHgO2Ic?p=preview
// Markup:
<my-drop-down main="<a>%%name%%</a>">
// Then, in your directive link:
function link($scope, elem, attrs, ctrl, transclude ) {
$scope.dropDownClass = attrs.ddClass || 'default';
// Replace our template string and compile that w/ braces:
var tpl = attrs.main.replace(/%%([a-z]+)%%/g, '{{$1}}');
var main = $compile(tpl)($scope);
elem.find('button').first().append(main);
}
There are other options you could take, like creating an isolate scope, etc. but they require more refactoring of your code. The above two seem to be the easiest to drop-in.
Problem is that when your link function is executed, name is already interpolated.
In addition to what #rgthree said you could also use the compile function on directive to only call var compiled = $compile(tAttrs.main) and then in the link function call the return value with the scope var main = compiled($scope):
compile: function compile(tElement, tAttrs, transclude) {
var compiled = $compile(tAttrs.main);
return function( $scope, elem, attrs, ctrl, transclude ) {
$scope.dropDownClass = attrs.ddClass || 'default';
console.log(3);
var main = compiled($scope);
elem.find('button').first().append(main);
}
},
Check this plunker

Categories