How can I use an Angular constant in a view? - javascript

I am trying to remove a great number of string literal values from angular directives all over my views. I have the beginnings of a constant I wish to make global, e.g:
// UI Validation constants.
MyAngularApp.constant('valid', {
"postalCode": {
"pattern": "/^[0-9]+$/",
"minLength": 4
}
});
Then I would like to use this constant in input directives in my view templates, in a way similar to:
<input type="text" ng-pattern="{{valid.postalCode.pattern}}" blah blah>
But here I have serious doubts about binding within a directive's parameter.
I have seen a few suggestions to add such a constant to root scope, but also some suggestions to avoid root scope and only add this to local scopes inside controllers, but this would involve code changes to several controllers, where I would prefer just one change.
if I decide to go with root scope, how would I add this constant? My misguided attempt at this is:
console.log("Abandon all hope who enter here!");
MyAngularApp.config(function ($rootScope) {
$rootScope.valid = valid; // How to access the constant here?
});
But this code gives me the error:
Uncaught Error: [$injector:modulerr]
ADDED: Some suggestions below involve the Angular run function, but I can't find that. The module call is like this:
var MyAngularApp = angular.module('MlamAngularApp', ['kendo.directives', 'ngRoute', 'ui.bootstrap', 'ngReallyClickModule', 'ngMessages', 'angular-loading-bar', 'ngAnimate']);

Per the OP - If you want to bind to rootScope you when the app loads you can use a run block like this:
app.run(function($rootScope) {
$rootScope.name = 'bound during run'
})
I updated the plunker demonstrating scope inheritance to show it in action.
Plunker
It's bad practice to store stuff in $rootScope.
Here is an example why:
$scope inheritance
It can make things confusing and messy, especially if multiple people are maintaining code. You bind something to rootScope and EVERY controller's scope has that property attached to it's scope. If you have to do it, you should document it very clearly.
Do it like this:
app.constant('myConstant', {
"postalCode": {
"pattern": "/^[0-9]+$/",
"minLength": 4
}
})
Just inject wherever you need it:
app.controller('MainCtrl', function($scope, myConstant) {
$scope.name = myConstant;
});
Banner's method is fine as well, though I think it is better to do it this way to maintain a high level of abstraction rather than attaching a bunch of stuff to the module like that. This way let's you tuck all your constants into constants.js or whatever, and keeps everything clean.

See the snippet.
valid constant is getting injected into $rootScope when calling run.
On the view side, just do {{$root.valid.postalCode.pattern}} where you need to inject it.
And thus... the only thing that you need to change is the run function and the corresponding views' binding.
Injecting into config will not work as per Cannot get to $rootScope
(function() {
'use strict';
angular.module('myApp', []);
angular.module('myApp')
.constant('valid', {
"postalCode": {
"pattern": "/^[0-9]+$/",
"minLength": 4
}
});
angular.module('myApp')
.run(runFn);
runFn.$inject = ['$rootScope', 'valid'];
function runFn($rootScope, valid) {
$rootScope.valid = valid;
}
angular.module('myApp')
.controller('myCtrl', myCtrl);
function myCtrl() {
var self = this;
}
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl as vm">
{{$root.valid.postalCode.pattern}}
</div>

Related

Which is the preferred way of defining an AngularJS controller?

As an example, we have this index.html code:
<!DOCTYPE html>
<html ng-app="sample">
<head>
...
</head>
<body>
<div ng-controller="myController">
...
<script src="js/modules/app.js"></script>
</div>
</body>
</html>
and in app.js we have a module and a controller:
var app = angular.module('sample', []);
// controller here
So my question is that, I have seen controllers defined in two types, as a controller, and as a plain function:
app.controller('myController', function(args){
...
});
or
var myController = function(args){
...
};
Which one should be used and why? I have mostly seen the first one used in Angular-based code, but even in tutorials I have come across the second. I personally don't use the second, as I have read it 'pollutes the global namespace'.
Another question I have is that I have seen this kind of usage for a controller:
app.controller('myController', ['$scope', '$http', function($scope, $http) {
...
}]);
Why do we need the array? Can't we make do with just the arguments?
According to the Angular the array annotation based dependency injection or definition is the preferred way:
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
// ...
}]);
See Inline Array Annotation
This is the preferred way to annotate application components. This is
how the examples in the documentation are written.
While at the other hand, the simplest way to get hold of the dependencies is to assume that the function parameter names are the names of the dependencies (which is not preferred for like production app).
someModule.controller('MyController', function($scope, greeter) {
// ...
});
The Angular can infer the names of the services to inject by examining the function declaration and extracting the parameter names. In the above example, $scope and greeter are two services which need to be injected into the function.
However this method will not work with JavaScript minifiers/obfuscators because of how they rename parameters.
The resulting code after minification will be like this:
someModule.controller('MyController', function(a, b) {
// ...
});
So now, the Angular does not know what is the dependency a & b while if you use the array annotation based, the output will be:
someModule.controller('MyController',['$scope','greeter', function(a,b) {
// ...
}]);
So, now Angular can map a with $scope and b with greeter and will be able to resolve the dependency.
app.controller('myController', ['$scope', '$http', function($scope, $http) {
This is done to prevent JS minifiers from breaking your code, because angular relies on names for dependency resolutions.
As for the two styles of controller
app.controller('myController', function(args){
...
});
vs
var myController = function(args){
...
};
app.controller('myController', myController);
It's a matter of personal taste. There's no functional difference.
The first question is mostly about style, since both methods are correct.
There might be arguments about both ways of defining controllers and other Angular modules. But as it is with every language in Software Development: Find a coding style and stick to it. Inconsistencies are the real problem.
Here's a good style guide to stick to: https://github.com/johnpapa/angular-styleguide
The second question has to do with minification. Please read this article: http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/
Syntax for Controllers
The most preferred way for Angular 1.x version is to use Controller As syntax. Please see code below:
(function () {
'use strict';
angular.module('app.controllers')
.controller('HeadController', HeadController);
HeadController.$inject = ['someService'];
function HeadController(someService) {
/* jshint validthis: true */
var vm = this;
vm.logout = action;
function action() {
someService.doSomeAction();
}
}
})();
In your html it will be used like this:
<div ng-controller="HeadController as vm">
<a href ng-click="vm.logout();" id="item-btn-logout"><i class="icon-off">
</div>
I prefer this syntax. This will let you not to use scope in your views.
Take a look at John Papa AngularJS guide - it is good!
If you are still thinking that it's too complicated and you ain't gonna need it - refer to this article that explains how to avoid Scope Soup in Angular.
Why do we need the array?
Array with plain text is used during injection of dependencies. This will let you minify your code easily and not to lose dependency names during initialization process. If you are not going to do this and minify your code - you are risking that your code won't work.

Declaring functions and properties in the scope of angularjs controller but not attached to $scope

As of recently I have been declaring functions and properties for my angularJS controllers in the following way (app is set to the main apps angular module):
app.controller('myController', ['$scope', function($scope) {
$scope.myProperty = "hello world";
$scope.myFunc = function() {
// do stuff
};
}]);
After a while the controllers' $scope grew to contain many utility functions and properties that are not being used directly in my views and would not be applicable to other controllers, so I changed it to this:
app.controller('myController', ['$scope', function($scope) {
var myProperty = 0, addOne;
addOne = function(i) {
return i++;
};
$scope.myFunc = function() {
myProperty = addOne(myProperty);
// do other stuff
};
}]);
This is working fine but is it okay to declare functions and properties the way shown above or should I extract them out into a service? Can I unit test var declared functions in my controller from jasmine/karma(tried but did not succeed)? What is the best pattern to follow?
I have looked into using the 'this' keyword to accomplish the same thing shown above but I don't think the added value would outweigh the amount of time it would take to convert everything to this pattern. If this is totally wrong please advise a better strategy.
--- Update ---
To answer my own question about testing var declared functions in the controller: How can we test non-scope angular controller methods?
As a general rule, if you're not going to use the variable or function in the view, don't attach it to the scope. So declare your variables and functions within the controller or on the scope based on it's use. You don't want to put unnecessary stuff on the $scope, as this will slow down the digest cycle. As a best practise I follow this format in my controller:
app.controller('MainCtrl', function($scope) {
// controller variables
var age = 123;
// scope variables
$scope.name = 'John';
// controller functions
var helloWorld = function() {
console.log('do work');
}
// scope functions
$scope.isOld = function() {
return age > 50;
}
});
services are singletons while controllers are not.
in addition, services are used to share functionality between many different controllers/directives.
your second code block looks really good to me for internal, controller specific functionality which you don't need to share in other places in your app, and its much better than putting unneeded stuff in the scope.

Angular Service Injection

I am working on a trivial task (that I got working) which adds an an .active class to dom elements in a tab nav. Simple. Got it working by following https://stackoverflow.com/a/12306136/2068709.
Reading over the answer provided and comparing their controller to example controllers on angular's website I came across something interesting that I don't quite understand.
On the AngularJS website, $scope and $location (and other services) are injected into the controller along with an anonymous function which defines the controller. But on the answer, the services are not injected, rather they are passed via the anonymous function. Why does this work? Shouldn't the service always be injected?
In terms of an example: Why does this
angular.module('controllers', [])
.controller('NavigationController', ['$scope', '$location', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
};
}])
and this
angular.module('controllers', [])
.controller('NavigationController', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
};
})
both work?
This may be trivial but its throwing my brain for a loop that I can't figure out.
The two examples are equivalent - they just make use of different syntax. The first example uses what they call "inline array annotation" (see here). The purpose of this alternate syntax is just to allow a convenient way to make the injected variable names different than the name of the dependency.
So for example, if you wanted the variable names to be "s" and "l", then you could write:
angular.module('controllers', [])
.controller('NavigationController', ['$scope', '$location', function(s, l) {
s.isActive = function(route) {
return route === l.path();
};
}]);
Actually they are injected in both cases, the difference between those two cases is in the first scenario you define and name the dependency this could be useful if you minify your js code and that way you are declaring explicitly the dependency for examply it could be:
angular.module('controllers', [])
.controller('NavigationController', ['$scope', '$location', function($s, $l) {
$s.isActive = function(route) {
return route === $l.path();
};
}])
that way angular will know which dependency to inject on which parameter without looking at the naming for each parameter.
the other case you need to be explicit and declare which dependency you'll inject by setting up the name.
I hope that helps.
This is how angular handles code minification. by keeping strings intact it can keep mapping vars when they are renamed.
if you take a look at the code of the controller https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L48
you'll see that the constructor can accept both function and array.

Unit test views - best practice

Can anyone share experience with unit testing views? I read a lot of tutorials about how to do unit testing with views, but everything has some drawbacks.
I came along with the following approach. It works, but I'm wondering if there is a better way to do this. There are also some drawbacks, which I'll explain later on. I'm also doing E2E tests with protractor, but they are always slow, and therefore I limit them to a minimum.
This is my controller. It has two variables bound to its $scope which are used in the view:
// test_ctrl.js
angular.module('app', [])
.controller('TestCtrl', ["$rootScope", "$scope", function ($rootScope, $scope) {
$scope.bar = "TEST";
$scope.jobs = [
{name: "cook"}
];
}]);
The view takes the $scope.bar into a <span> and the $scope.jobs array into an ng-repeat directive:
<!-- test.html the view for this controller -->
<span>
Bar is {{bar || "NOT SET"}}
</span>
<ul>
<li ng-repeat="job in jobs">{{job.name}}</li>
</ul>
And this is the test:
describe('Controller: TestCtrl', function () {
beforeEach(module('templates'));
beforeEach(module('app'));
var TestCtrl, $rootScope, $compile, createController, view, $scope;
beforeEach(inject(function($controller, $templateCache, _$rootScope_, _$compile_, _$httpBackend_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = _$compile_;
createController = function() {
var html = $templateCache.get('views/test.html');
TestCtrl = $controller('TestCtrl', { $scope: $scope, $rootScope: $rootScope });
view = $compile(angular.element(html))($scope);
$scope.$digest();
};
}));
it('should test the view', function() {
createController();
expect(view.find("li").length).toEqual(1)
console.log($scope.jobs)
});
});
In the beforeEach function, I'll set up the controller. The createController function (which is called from the tests itself) takes a view out of the $templateCache, creates a controller with it's own $scope, then it compiles the template and triggers a $digest.
The template cache is prefilled with karmas preprocessor ng-html2js
// karma.conf.js
...
preprocessors: {
'app/views/*.html': 'ng-html2js'
}
...
With this approach, I have a little problem, and some questions:
1. Additional $$hashKey keys in my objects from ng-repeat
The expect($scope.jobs).toEqual([{name: "cook"}]); in my test throws an error:
Expected [ { name : 'cook', $$hashKey : '009' } ] to equal [ { name : 'cook' } ]
I know that ng-repeat adds these keys, but this is silly to test. The only way around I can think of is separating the controller tests and the view tests. But when I check the jobs array inside my controller, the $$hashKey is not present. Any ideas, why this is happening?
2. $scope problem
When I tried this for the first time, I only had my local scope defined as $scope={} and not $scope = $rootScope.$new(), as I have done in my other controller tests. But with just a plain object as a local scope, I wasn't able to compile it ($compile(angular.element(html))($scope); throwed an error).
I also thought if it is a good idea to pass the $rootScope itself as the current local scope for the controller. Is this a good approach? Or are there any drawbacks, I haven't seen yet?
3. Best practices
I would be very happy to know, how everyone else is doing unit tests in AngularJS. I think views have to be tested, because with all the angular directives, there lies a lot of logic in them, which I would be glad to see waterproofed ;)
I think that what you're doing is a great way to unit test views. The code in your question is a good recipe for someone looking to unit test views.
1. ng-repeat $$hashKey
Don't worry about the data. Instead, test the result of various operations, because that's what you really care about at the end of the day. So, use jasmine-jquery to verify the state of the DOM after creation of the controller, and after simulated click()s, etc.
2. $scope = $rootScope.$new() ain't no problem
$rootScope is an instance of Scope, while $rootScope.$new() creates an instance of ChildScope. Testing with an instance of ChildScope is technically more correct because in production, controller scopes are instances of ChildScope as well.
BTW, the same goes for unit testing directives that create isolated scopes. When you $compile your directive with an instance of ChildScope an isolated scope will be created automatically(which is an instance of Scope). You can access that isolated scope with element.isolateScope()
// decalare these variable here so we have access to them inside our tests
var element, $scope, isolateScope;
beforeEach(inject(function($rootScope, $compile) {
var html = '<div my-directive></div>';
// this scope is an instance of ChildScope
$scope = $rootScope.$new();
element = angular.element(html);
$compile(element)($scope);
$scope.$digest();
// this scope is an instance of Scope
isolateScope = element.isolateScope();
}));
3. +1 Testing Views
Some people say test views with Protractor. Protractor is great when you want to test the entire stack: front end to back end. However, Protractor is slow, and unit testing is fast. That's why it makes sense to test your views and directives with unit tests by mocking out any part of the application that relies on the back-end.
Directives are highly unit testable. Controllers less so. Controllers can have a lot of moving parts and this can make them more difficult to test. For this reason, I am in favor of creating directives often. The result is more modular code that's easier to test.

AngularJS directive not showing up on template

I've got a tiny problem with an angular directive that's now working and I don't know why. I think it's a fairly simple issue that I'm overlooking, maybe you can help me out.
Directive is defined like this:
angular.module('directives', [])
.directive('my-directive', function () {
return {
restrict: 'AE',
scope: {
name: '=name'
},
template: '<h1>{{name}}</h1>'
};
});
Then index.cshtml:
<my-directive name="test"></my-directive>
Application.js:
var app = angular.module('MyApp', [
...,
'directives'
]);
And here's controllers.js
angular.module('controllers', ['apiServices', 'directives'])
.controller('homecontroller', function($scope, $resource, webApiService, $log, $translate, $localStorage, $sessionStorage) {
Ok confirmed that directives.js is loaded, otherwise application.js nags about 'unknown module'. There are no error messages in the console, the thing just doesn't show. Any ideas?
EDIT
So as pointed out, I changed the directive name to camelCase, but still no luck:
<my-directive name="John Doe"></my-directive>
And
.directive('myDirective', function () {
But nothing is showing yet.
EDIT
Problem is that angular expects an object to be passed into the attribute, not a string literal. If you create an object person = { name: 'John' }, pass the person in, then write {{ person.name }} ( assuming we named the attribute person + scope var person too ).
During normalization, Angular converts - delimited name to camelCase.
So use camelCase while specifying the directive inside JS:
.directive('myDirective', function () {
Fiddle
I'm sure you've figured this out already, but if you change your scope definition for name to be
scope: {
name: '#'
}
you will then be able to pass a string. The '#' interpolates the attribute while the '=' binds it. Additionally, you don't need to include an attribute name if it is the same as the scope variable.
The problem appears to be in the directive definition. You note in your question that Angular expects an object; this is true for the "=" scope, but not for the "#" scope. In the "#" scope, Angular expects a string only. I have created a snippet below.
Too many modules
Unless you are reusing the directive in multiple applications, do not create a new module for it. Add the directive definition to the module that you created for the application. In my example below, I called the module back by using "angular.module( moduleName )"... When only one argument is used, Angular returns the existing object rather than creating a new one. This is how we can separate the code into many files.
Things to Note
You will notice the following:
You do not need to load the module into the app variable. Calling the Singleton each time is actually safer and easier on memory management.
The directive is in camel case, as you have already noted.
I am setting the name attribute to a string value and not an object; this works because of the "#" scope setting.
The div is set to ng-app='MyApp'. This usually is set to the html element, but I did not want to mess with the DOM on Stack Exchange. The ng-app directive can be set on any element, and the directives associated with that module will be applied on all elements that are within that element's scope. Without the ng-app directive, Angular does not know which module to run on the page.
//app.js - this defines the module, it uses two parameters to tell the injector what to do.
angular.module('MyApp',[]);
//directive.js stored elsewhere
//this calls back the module that has been created. It uses one parameter because the injector is no longer needed.
angular.module('MyApp').directive('myDirective', function () {
return {
restrict: 'AE',
scope: {
name: '#'
},
template: '<h1>{{name}}</h1>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="test"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
</div>
Using Injection
When you have a different module for each and every directive or controller, each one must be injected into the application's module definition. This leaves a lot of room for error. As a best practice, only create a new module when necessary, and make the module a container for a group of related functionality and not a single item.
The code below demonstrates proper injection.
angular.module( "MyApp", ['ReusableDirectives'] );
angular.module( "MyApp" ).directive( "myDirective", function(){
return {
restrict: "AE",
scope: { name: "#" },
template: "<p>This is the directive I defined in the example above. It uses <u>the same module</u> as my main application, because it is not going to be reused over and over again. In fact, I will need it just for this application, so I don't need to complicate things with a new module. This directive takes an attribute called 'name' and if it is a string allows me to manipulate the string within my templates scope to do things like this: {{'hello ' + name + '!'}}</p>"
};
} );
angular.module( "ReusableDirectives", [] );
angular.module( "ReusableDirectives" ).directive("reusableDirective", function(){
return {
restrict: "E",
template: "<p>This is a directive that I intend to use in many, many applications. Because I will reuse it so much, I am putting it in a separate module from my main application, and I will inject this directive. This is the only reason that this directive is not in the same module as the one I defined above.</p>"
};
} ).directive("reusableDirective2", function(){
return {
restrict: "E",
template: "<p>This is a second directive that I intend to use in multiple applications. I have stored it in a module with the first directive so that I can freely inject it into as many apps as I like.</p>"
};
} )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="Johnny"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
<h3>Injected Directives</h3>
<reusable-directive></reusable-directive>
<reusable-directive2></reusable-directive2>
</div>
Keep it simple. Define your directives on a single module for your application. Once you have that done and working, if you need the directives again in another application, refactor and experiment with injections at that time after you have some more Angular practice under your belt.
You have a bright future with Angular, keep up the good work!
Your directive must be camel-cased
.directive('myDirective', function () {
then in your html, your are free whether to call it my-directive or myDirective
Both are valid
<my-directive name="test"></my-directive>
<myDirective name="test"></myDirective>
Just to follow up on this, I had to use the following way to get my directive to work.
<my-directive name="test"></my-directive>

Categories

Resources