Angularjs directives call function from Parent's controller - javascript

Since my issue was just a missing word in the code, I'm going to write the way to do it.
I think It may helps other learners.
List of files :
app.js
mycontroller.js
page.html
mydirective.html
mydirectives.js
Your app.js is using route Provider to display your view. In this example you only have one view, which is page.html. This page is controlled by mycontroller.js.
You want your page.html to display few directives, each one is very different in terms of design, so you have putted the template in mydirectives.html.
The file that is managing your directives is mydirectives.js.
Our example will be to call a function which is declared in mycontroller.js from mydirective.html. The function will simply send an alert.
app.controller('mycontroller', function ($scope) {
$scope.sendAlert = function () {
alert("It works !");
}
});
Now you are defining your directives mydirective.html. We will just add a simple button for the example.
... your design
<button ng-click="callFunction()" type="button">Call the function</button>
... end of your design
Now that you have your directive set up, you need to manage it using your file mydirectives.js, which can contain multiples directives.
app.directive('test', function() {
return {
restrict: 'E',
templateUrl: 'mydirectives.html',
scope : {
callFunction: "=call",
}
};
});
Now you can add the directive in your page.html. It is defined. Don't forget to add the function ;).
<test call="callFunction"></test>
Don't forget to add include all the files in your project, and now it's working !

Did you forget to pass the scope test="test"?
<dir1 test="test" ng-show="showdir1">
</dir1>
<dir2 test="test" ng-show="showdir2">
</dir2>

Related

$(document).ready alternative for AngularJS

I'm using a template called Gentelella and I'm trying to implement AngularJS into it. However, I'm having an issue with a certain Javascript file. In the end of this file, a $(document).ready function is called which initialises Javascript code that makes some changes in the HTML code. The issue is that the $(document).ready function is called too early, before the HTML is fully loaded.
This issue occurs probably because I'm using ngRoute and this injects the template html file into the ng-view of the index.html. When this happens, the DOM probably already announces a document ready before AngularJS has injected the template (=HTML).
So basically, I just need to find a way to call some code in a Javascript file once AngularJS has injected the template.
I attached some code to gain some insight into the issue:
Snippet of the custom.min.js
$(document).ready(function () {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
});
Snippet of the main.js:
.config(function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
templateUrl : 'dash.html',
controller : 'dash',
controllerAs: 'controller'
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation',
controllerAs: 'controller'
}).when('/plain_page', {
templateUrl : 'plain_page.html',
controller : 'dash',
controllerAs: 'controller'
}).otherwise('/');
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
})
Thanks in advance!
Many jQuery plugins depend on a workflow of 1. draw the DOM. 2. run an init() function to set up code against those DOM elements.
That workflow fares poorly in Angular, because the DOM isn't static: Angular sets up and destroys DOM nodes on its own lifecycle, which can overwrite event bindings or DOM changes made outside Angular. Document ready isn't particularly useful when you're using Angular, because all it indicates is that Angular itself is ready to start running.
To use Angular effectively, you have to get into the habit of initing code only when it's actually needed. So instead of a big bucket of init_foo(); init_bar(); on document.ready, you should have a Foo directive with its own init code, and a Bar directive with its own specific init code, and so on. Each of those directives should only modify the DOM created by that specific directive. This is the only safe way to ensure that the DOM elements you need to modify actually exist, and that you're not creating conflicts or unexpected interdependencies between directives.
To take one example: I'm guessing your init_flot_chart() crawls down through the DOM looking for a particular element inside of which it'll draw a flot chart. Instead of that top-down approach, create a directive:
angular.module('yourApp')
.directive('flotWrapper', function () {
return {
template: "<div></div>",
scope: {
data: '#'
},
link: function(scope, elem, attrs) {
var options = {}; // or could pass this in as an attribute if desired
$.plot(elem, scope.data, options); // <-- this calls flot on the directive's element; no DOM crawling necessary
}
};
});
which you use like this:
<flot-wrapper data="{{theChartData}}"></flot-wrapper>
...where theChartData is an object containing whatever data is to be drawn in the chart. (You can add other attributes to pass in whatever other parameters you like, such as the flot options, a title, etc.)
When Angular draws that flotWrapper directive, it first creates the DOM element(s) in the directive template, and then runs whatever is in its link function against the template's root element. (The flot library itself can be included via a plain old <script> tag so its plot function is available when the directive needs it.)
(Note that this wouldn't update automatically if the contents of theChartData change; a more elaborate example which also watches for changes and responds appropriately can be seen here.)
As you are using ngRoute, your controller will run when the page is loaded.
You can call an init() method when the controller starts to do whatever you want.
function MyCtrl($scope) {
function init() {
console.log('controller started!');
}
init();
}
As a side note, it is a best practice recommanded in John Papa Angular's guide.
Another possibily is to use the ng-init directive, for example:
<div ng-controller="MyCtrl" ng-init="myFunction()">...</div>
There is a lifecycle hooks named $postLink() in recent version of angular.js, Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation.Check the guide
This is related to angular digest cycle, it's about how angular works underneath the hood, data binding etc. There are great tutorials explaining this.
To solve your problem, use $timeout, it will make the code execute on the next cycle:
app.controller('Controller', function ($scope, $timeout) {
$scope.$on('$viewContentLoaded', function(event) {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
},0);
});
});
$document.ready(function() {
$scope.$on('$viewContentLoaded', function() {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
})
})
})
update Notice: sry guys, this is not a rigorous solution, plz see another answer I wrote

Angular directive inside ng-template with modal

I am curious about the way angular works with preloading directives since I have a problem with a directive that resides in a <script> tag and ng-template.
When I pause execution in chrome dev-tools, during the initial document load, I can clearly see that the code inside the directive's controller does not get called if my directive lies in some arbitrary template. Here is the example code, when e.g. myDirective is included in index.html as a part of myModule.js module, also included both in index and in the main app module:
This is some other directive's html containing the problematic myDirective
<script type="text/ng-template" id="callThis">
<myDirective></myDirective>
</script>`
and I call it on click with ngDialog like this
ngDialog.open({
template: 'callThis',
scope: $scope
});
and it can't run the directive since it doesn't have any html to work with (thats the error, about some html element missing).
Finally here is the code for the module which holds myDirective
angular.module('myModule', ['myDirectiveTemplates', 'myDirectiveDirective'])
angular.module('myDirectiveTemplates', []).run(["$templateCache", function($templateCache) {$templateCache.put("myDirectiveTemplate.html","<h1></h1>");}]);
angular.module('myDirectiveDirective', []).directive('myDirective', function ($window, $templateCache) {
return {
restrict: 'E',
template: $templateCache.get('myDirectiveTemplate.html'),
controller: function($scope) {
//some arbitrary code
}
};
})
Interestingly if i put <my-directive></my-directive> right in index.html file it works ok, and the code inside the controller gets loaded on startup. I'm unsure how to solve this.
From what I understand of this problem you need to use the $compile service. There is a tutorial here that might help : $compile
The reason given in the tutorial is:
"The newly minted template has not been endued with AngularJS powers
yet. This is where we use the $compile service..."
And quoted from the Angular Docs:
Compiles a piece of HTML string or DOM into a template and produces a
template function, which can then be used to link scope and the
template together.
Here is a brief code example as per the tutorial :
app.directive('contentItem', function ($compile) {
/* EDITED FOR BREVITY */
var linker = function(scope, element, attrs) {
scope.rootDirectory = 'images/';
element.html(getTemplate(scope.content.content_type)).show();
$compile(element.contents())(scope);
}
/* EDITED FOR BREVITY */
});

AngularJS call method from an ancestor scope inside directive

I have an Angular app where I'm using ui-grid. I want to have a custom action on a cell of the grid that calls a method from my app. So basically, this means calling a method that's somewhere up in the parent hierarchy, from a directive.
This would be achieved by calling something like: $scope.$parent.$parent.$parent.$parent.foo(). But that doesn't seem too nice.
One option would be to create a recursive function that goes up the ancestry of the $scope. That's nicer, but still seems a bit weird.
Also... Is it good practice to try to achieve something like this?
You're correct that $parent.$parent.$parent is definitely not a good practice.
If the method you're calling is another directive, you can require that directive in your child directive and then, the parentDirective's controller function will be injected as the fourth parameter to your link function:
In your DDO:
return {
require : '^parentDirective',
restrict : 'E',
link : function (scope, elem, attrs, parentDirectiveController) {}
}
If what you're trying to call is on a factory/service, you can inject that factory/service into your directive, although this sometimes is a code smell, depending on what you're trying to inject.
Finally, another way to do it is to use event propagation. From your directive, you can use $scope.$emit to send information up to parent controllers:
From the directive:
$scope.$emit('directiveDidStuff', {
data : 'blah'
});
In the parent controller:
$scope.$on('directiveDidStuff', function (evt, params) {
this.data = params.data; // equals blah
});
You can achieve the same by using "&" through one of the scope variable in directive.Like this, you can bind your event to the controller method and from the method, you could do your desired things or if the original business logic which you wants to achieve on onClick of the grid is used across many modules than you can bisect it in service and make it reusable and call the service from the event method. Let me know if you do have any doubts with the approach.
Key Code of example:
Html
<my-component attribute-foo="{{foo}}" binding-foo="foo" isolated-expression- foo="updateFoo(newFoo)" >
Directive
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})

Template not loading

I have a problem that occurs sometimes, when the template entry is not defined in the template cache, it happens every once in a while after reloading the page.
The related code:
index.html
<div ng-include src="'Views/templates.html'"></div>
Views/templates.html
<script type="text/ng-template" id="customTemplate/spare-request-item.html">
<div>..</div>
</script>
directive.js
.directive("spare", function ($templateCache) {
console.log($templateCache.info());, //return length 30 when it fails, 31 otherwise
console.log($templateCache.get('customTemplate/spare-request-item.html'));//return undefined when it fails
return {
..
templateUrl: 'customTemplate/spare-request-item.html',
Anyone else has experienced this or know how to assure that the template is loaded and parsed before the directive runs? It is a angular bug?
Angular compiles the directives as it goes over DOM. You can't have it "wait" on a directive. What you could do, is not to show the directive until templates have loaded.
I don't think this is a good approach since it requires "knowledge" on the part of the user of the directive:
<div ng-include="'Views/templates.html'"
onload="$root.templatesLoaded = true"></div>
<my-directive ng-if="templatesLoaded"></my-directive>
Another approach would be to manually load all the templates and the directive's specific template. Here's a conceptual way of how to do this:
app.directive("foo", function($templateRequest, $templateCache, $compile) {
return {
link: function(scope, element) {
$templateRequest("templates.html")
.then(function(templateContainer) {
$compile(templateContainer);
return undefined; // just to show that no need to return anything
})
.then(function() {
var template = $templateCache.get("foo.html");
if (template) {
element.html(template);
$compile(element.contents())(scope);
}
});
}
}
});
plunker
$templateRequest is used here since it caches the templates.html template (to prevent double-requests), but it is a "lazy" use, since you would not actually need the template with id === "templates.html".
You can abstract it into a service, of course, to make it nicer.
Typically, however, I have seen directive developers embed the template in the .js file of the directive. This frees the user of the directive from loading separate .html files to make the directives work. I suppose you could load themes this way.

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