Directives - AngularJS - angularjs

I have 2 questions relating to directives. The first question relates to injecting a provider. I have used the compile directive example listed on the AngularJS web site. In that example it states to create a module and then create a directive from that module
// declare a new module, and inject the $compileProvider
angular.module('compile', [], function($compileProvider) {
// configure new 'compile' directive by passing a directive
// factory function. The factory function injects the '$compile'
$compileProvider.directive('compile', function($compile) {...
In my application in all I do is create the directive like so
myApp.directive('compile', function($compile) {...
I haven't referred to $compileProvider anywhere in my code, however my code still works and compiles templates quiet well. Why is that?
Also, although it works well when compiling templates they all seem to work except when I compile 'switch' statement. 'switch' statements do not seem to link the scope, all the other elements compile without a problem. Is this related to the fact that I haven't injected $compileProvider or is there something about switch statements that require an extra step when compiling?
Thanks
Frank

It works and compiles templates quite well because module.directive is simply shorthand for $compileProvider.directive. The docs for module.directive refer you to $compileProvider.directive.
As for using switch inside your directives, can you provide an example of how you're doing this? Depending on what you're switching on and where you're doing it, you may be defining your directive incorrectly. For example, if you're switching inside of directive callback, it's only going to be executed once, so only one of your case statements will win and create only 1 directive.

Related

Angularjs : How can i know when to use "Compile" when to use "Link" in directives?

I am very new to angularjs. I have little bit confusion with link and compile usage in directives. Can anyone please tell me in which scenarios we have to use link and compile.
What is the difference between compile and link function in angularjs
Already answered on stack overflow and has an excellent explanation
The compile phase
When the DOM is loaded Angular starts the compile phase, where it traverses the markup top-down, and calls compile on all directives. Graphically, we could express it like so:
An image illustrating the compilation loop for children
It is perhaps important to mention that at this stage, the templates the compile function gets are the source templates (not instance template).
The link phase
DOM instances are often simply the result of a source template being rendered to the DOM, but they may be created by ng-repeat, or introduced on the fly.
Whenever a new instance of an element with a directive is rendered to the DOM, the link phase starts.
In this phase, Angular calls controller, pre-link, iterates children, and call post-link on all directives, like so:
Below links will give you clear idea for compile vs link.
Angular directives - when and how to use compile, controller, pre-link and post-link
http://odetocode.com/blogs/scott/archive/2014/05/28/compile-pre-and-post-linking-in-angularjs.aspx

Difference in controller declaration in AngularJS

I have seen controller being declared in two ways as below. But what diff does this make?
appmodule.controller('Actrl',['$scope',function($scope) {}]);
appmodule.controller('Actrl',function($scope) {});
But, most of the times, the 1st doesn't work. Why?
Both the syntax are same but the first one is preferred (there is a typo, see below description) if you are minifying your code.
Angular resolves the dependency based on the name so when you write appmodule.controller('Actrl',function($scope) {}); syntax, Angular injects the dependency of $scope by reading the argument name $scope. But when your code is minified for production level use then your code will become like:
appmodule.controller('Actrl', function(a) {});
Now, the Angular will not be able to resolve the dependency with the name a. That is why the first approach is used i.e. appmodule.controller('Actrl',['$scope', function($scope) {}]);. Now when your code is minimized for production, your code will be like this:
appmodule.controller('Actrl',['$scope', function(a) {}]);
Now, Angular can match the index based position that a is $scope.
There is a typo in your code where the list should not be closed before the function declaration.
Read the Dependency Annotation for more information on this under the topic Inline Array Annotation.
Angular invokes certain functions (like service factories and
controllers) via the injector. You need to annotate these functions so
that the injector knows what services to inject into the function.
There are three ways of annotating your code with service name
information:
Using the inline array annotation (preferred)
Using the $inject property annotation
Implicitly from the function parameter names (has caveats)
Edit:
Another more detailed description over the two different style: a-note-on-minification:
Since Angular infers the controller's dependencies from the names of
arguments to the controller's constructor function, if you were to
minify the JavaScript code for PhoneListCtrl controller, all of its
function arguments would be minified as well, and the dependency
injector would not be able to identify services correctly.
We can overcome this problem by annotating the function with the names
of the dependencies, provided as strings, which will not get minified.
There are two ways to provide these injection annotations:
[EDIT]
For your first case, this isn't the right syntax. The right one would be to encapsulate in the same array your dependency injection and your controller like the following:
appmodule.controller('Actrl',['$scope', function($scope) {}]);
The difference between both of your definitions is that in the first case you're explicitly specifying your injection dependencies. This will avoid to rename variables name during minification which would break your code. Hence the name in quotes [i.e. those strings] will be used in the minified versions.
Both approach are doing the same thing but the second one is just a syntactic sugar of the first one.
These are just two ways that AngularJS does Dependancy Injection. But this version,
appmodule.controller('Actrl',['$scope',function($scope) {}]);
in particular has been written to handle code minification. It is recommended use this version whenever possible.
To get the difference clear, you must first understand how AngualarJS does dependancy injection. For more details you can refer to:
Understanding Dependency Injection
The "Magic" behind AngularJS Dependency Injection
AngularJS Dependency Injection - Demystified
But to cut the long story short, AngularJS loops through each items by their names in the parameter list, looks up against a list of known names of objects that can be injected and then injects the objects if there is a match.
Let's have a look at an example:
appmodule.controller('myController',function($scope, $log) {
$log.info($scope);
});
Here, since $scope and $log (the order that you specify them in the parameter list doesn't matter here) are known objects to AngularJS, it injects them into myController. But if you were to do:
appmodule.controller('myController',function(someVar) {
// ...
});
AngularJS doesn't know about the parameter someVar and it throws a dependancy error.
Now let's come back to your example. Let me modify your 2nd version a bit:
appmodule.controller('Actrl',function($scope, $log) {
$log.info($scope);
});
If we use a minifier, let's see how this piece of code gets minified. I am using an online minifier for this purpose . After minification, it becomes:
appmodule.controller("Actrl",function(o,l){l.info(o)});
This is because, minifiers usually shorten the variable names to smallest size to save space. Notice how our $scope got renamed to o and $log to l.
Now, if we run this code, AngularJS doesn't know about o and l and it is going to cry about missing dependencies as we understood earlier.
AngularJS deals with this problem using the 1st version of Dependency Injection in your example. If it was:
appmodule.controller('Actrl',['$scope','$log', function($scope, $log) {
$log.info($scope);
}]);
After minification it becomes:
appmodule.controller('Actrl',['$scope','$log',function(o,l){l.info(o)}]);
Here, even though $scope and $log parameters were renamed to o and l respectively, the minifier didn't touch the strings '$scope' and '$log' and their order in the array.
Now when AngularJS injector sees this version using array, it substitutes each item in the parameter list in the function with the corresponding objects in the array (provided the objects are known to AngularJS).
So in our example, even after minification, AngularJS knows that it needs to substitute o with $scope and l with $log. Thus the code runs without any Dependancy Injection errors.
But one important thing is to note here is that, when we use this version the order of the items specified in the array and the parameter list of the function of really matters. That is, if you were to do:
appmodule.controller('Actrl',['$scope','$log', function($log, $scope) {
$log.info($scope);
}]);
, it is going to blow everything up!

Angular Directive not executing on UI Bootstrap Modal open

I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.

AngularJS Multiple Directive Resource Contention Error

I am currently learning AngularJS on Udemy with the excellent AngularJS JumpStart with Dan Wahlin course. I am on Section 7 - Bonus: Getting Started Building Custom Directives.
I had defined a Hello World directive which was working fine; then I defined a new directive so that I could learn about shared vs isolate scope.
As soon as I include the second directive, I get the following error:
Error: [$compile:multidir] Multiple directives [helloWorld, helloWorld] asking for template on: <hello-world>
http://errors.angularjs.org/1.4.8/$compile/multidir?p0=helloWorld&p1=&p2=helloWorld&p3=&p4=template&p5=%3Chello-world%3E
Here's a Plunker.
If I comment out the script that defines either one of these, the other one works.
Why is this happening? I've clearly named both directives differently, so why is Angular throwing an error that basically says I've declared the same directive twice?
1) you forget to add "plain:true" in the directives ( if you put directly your html code in template you have to put "plain:true" otherwise use templateUrl to your html file)
2) the problem come from the fact that you create two times the same angular module :
var directives = angular.module('app').directives;
I update your plunker
http://plnkr.co/edit/cYkgdYcHXGlbtMRJHRw2?p=preview
I think the issue is defining directives variable and adding it each time to the directives.
The more popular way of defining directives is:
var app = angular.module('app');
app.directive('helloWorld', ....);
Or if you prefer it your way, either have all directives in the same file, or move var directives = angular.module('app').directives to app.js

why can't I Inject directive into my test cases

I could able to inject almost everything in the beforeEach(inject(beforeEach(inject(function(_$controller_,_mycustomService_,_$log_) in Jasmine
etc, But I can't inject a directive ?
I would get error like http://errors.angularjs.org/1.3.13/$injector/unpr?p0=myCustomDirectiveProvider%20%3C-%20myCustomDirective
Should this not possible with AngularJS ? Is this the reason why the unit testing of directive is little different style ( by that I meant the $compile option) ?
Directives aren't injectable anywhere in angular, only providers (services, factories, values, constants, etc.).
In order to test a directive, you need to ensure the module in which the directive is defined has been loaded with the module() function, (it'll also need to be referenced in your karma config), then you can compile a piece of DOM and ensure everything behaves as you expect.
Rather than go into a full example here, I'll advise you to do your own research and google "testing angular directives".

Resources