$compile doesn't work inside Angular directive - javascript

with this Angular directive every time my model changes, new HTML item appended to the page:
app.directive('helloWorld', function($compile) {
return {
restrict: 'AE',
replace: true,
scope:{
arrayItem: '=ngModel'
},
link: function ($scope, ele, attrs) {
$scope.$watch( 'ngModel' , function(){
ele.html('<div ng-click="sendLike({{arrayItem.data.timeline_content}})" class="timeline-item"> Hello {{arrayItem2.data.timeline_content}} </div>');
$compile(ele.contents())($scope);
});
}
};
});
And this is HTML view:
<hello-world ng-repeat="arrayItem in arr" ng-model="arrayItem"></hello-world>
But ng-click inside dynamically generated HTML doesn't work. actually recompiling of this new added section does not works.
UPDATE:
this is what i want to achieve:
in fact i want to create a chat Application. messages stored inside an Array and i have to bind that array to the HTML view. if i click on every message, i need to an alert() fired inside the controller. my controller is like this:
app.controller("timelineCtrl", function ($scope) {
$scope.arr={};
$scope.sendLike = function (id) {
alert(id);
};
.
.
.
}
in the jQuery way, i simply use DOM manipulation methods and add new tags for each message but in Angular way i have to bind that array as a ng-model or something like that.
at first glance, i realize that designing a directive should be a good idea and inside module i can access to main scope and do needed thing with that directive and i expect that changes inside that directive should projected to HTML view but it fails and things like ng-click doesn't work for dynamically created tags.

There are two ways that you could accomplish this, with or without a directive.
Let's start without a directive; we'll assume that you have an array in the controller.
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<div ng-click="sendAlert(post)">
<span class="postnumber">{{::post.lineNumber}}:</span>
<span class="message">{{::post.message}}</span>
</div>
</div>
</div>
whenever an object is added to $scope.timeline, it can be assigned a lineNumber, and we can use angular OrderBy to sort the direction in reverse lineNumber order (using -). The $scope.sendAlert(post) will send the specific post to the function. in our bindings, we use :: to indicate that these are one time bindings, i.e. not values that need to be monitored independently of the array. This can improve performance on large lists.
using a Directive, we can accomplish this in a very similar manner, by making a Directive that renders a specific post, and passing the post in as a property.
app.directive('timelinePost', function() {
return {
restrict: 'AE',
scope:{
post: '='
},
template: '<div ng-click="postCtrl.sendAlert()">
<span class="postnumber">{{::postCtrl.post.lineNumber}}:</span>
<span class="message">{{::postCtrl.post.message}}</span>
</div>',
controller: 'postController',
controllerAs: 'postCtrl',
bindToController: true
};
app.controller("postController", function(){
var self = this; //save reference to this
self.sendAlert = function(){
//the specific post is available here as self.post, due to bindToController
};
};
//usage in HTML:
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<timeline-post post='post'></timeline-post>
</div>
</div>
you could further wrap this timeline in a directive in a similar manner, if you so desired. Either of these will accomplish the same task, of looping through your data, ordering it so that the newest post is at the top, and updating whenever the array changes. In the non-directive method, the timelineCtrl handles the $scope.sendAlert function; in the directive method, it is handled by the directive controller postController.
Please note, this is a rough draft based on what you have described and the information from various posts over the last 2 days. I haven't created a dataset to iterate through to test thoroughly, but the logic here should get you started.

Related

Directives in Angular

I have this directive view here in my code:
<div class="busy-indicator angular-animate" ng-show="busy"></div>
<div class="breadcrumblist" ng-class="atTopLevel ? ['at-top-level'] : null">
<div class="breadcrumblist-display angular-animate">
<label id="searchBox">Search</label><input class="c-context-menu-container t-menu-background c-context-menu-text" type="text" autocomplete="off" id="IdSearch" ng-model = "searchText.Name">
<div class="breadcrumblist-parents">
<div ng-show="::parents && parents.length >= 1"
ng-repeat="parentLink in parents"
class="breadcrumblist-parent-link t-breadcrumb--parent-bgcolor t-border--bottom-grey48"
ng-click="navUpToParent(parentLink)"
ng-class="{'selected': isSelected(parentLink.object), 'draggable': isDraggable(parentLink.object)}"
data-index="{{$index}}">
</div>
But, the searchBox is appearing for all places on my app but I want to make it appear just for one directive in particular. What should I do? I tought about make a scope variable to just "ng-show" this particular searchbox with a condition, but I don't know exactly how to do that, can you help me?
Until you give more information to your specific problem, here are some possibly relevant things you can do to debug your problem and find a solution
the searchBox is appearing for all places on my app but I want to make it appear just for one directive in particular
The combination of the name of the directive + its restriction might cause the directive to appear in unwanted locations.
More information on restrict if needed.
So for example, if you have a directive called 'breadcrumblistParentLink', which is restricted to C (class) - you might find it in undesired locations since you're also using this class to style some elements on your page.
If that's the case, you might find it helpful restricting directives to attributes and elements and giving some unique names.
I would also like to refer to your question
just "ng-show" this particular searchbox with a condition, but I don't know exactly how to do that
If you want a specific behavior for one instance of a directive, there are multiple ways to do that.
Most common is passing an attribute. For example, this is how you use it
<div my-awesome-directive show-searchbox="true"></div>
And this is how you'd put show on the directive scope
angular.module('myApp',[]);
angular.module('myApp').directive('myAwesomeDirective', function(){
return {
template: 'this is my template <span ng-show="showSearchBox">This is search box</span>',
restrict: 'A',
scope: {
showSearchBox: '<'
},
link: function(scope, element, attrs){
console.log(scope.showSearchBox);
}
}
})
You can play around with it here: https://plnkr.co/edit/4MdNeEafbZjq2kEFKYAl?p=preview
You can also look directive at attrs variable (attrs.showSearchBox) and spare the binding in some cases.
For example:
angular.module('myApp',[]);
angular.module('myApp').directive('myAwesomeDirective', function(){
return {
template: 'this is my template <span ng-show="showSearchBox()">This is search box</span>',
restrict: 'A',
scope: {
},
link: function(scope, element, attrs){
scope.showSearchBox = function(){
return attrs.showSearchBox;
}
}
}
})
Note I am using a function on the scope.
This function can also refer to the DOM and do something like $(element).is('[show-search-box]') if you are more comfortable with jquery - but it is highly recommended to stick to angular way of doing things.

Angular js : Unable to create directive

I am trying to create directive but my directive function is not getting called.
index.html
<div ng-repeat="que in questions.Cars">
<question-dir>print from direcetive</question-dir>
</div>
ConytrolDirective.js
(function () {
"use strict";
angular
.module("autoQuote")
.directive('questionDir',["questionDir"] );
function questionDir()
{
return {
template : "<h1>Made by a directive!</h1>"
};
}
}());
There are several mistakes in your code.
You should have function name instead of "questionDir"
.directive('questionDir',[questionDir] );
Use kebab case(- separated words) while using directive name
`<question-dir>print from direcetive</question-dir>`
Additionally you need to refer <script src="controlDirectives.js"></script> on your index.html page.
Demo here
I don't see many mistakes in your code but it would have helped many to stop assuming only if you had posted your code sample link too.
But one primary change you have to make is : Change your directive definition function invoking from
.directive('questionDir', ["questionDir"])
to
.directive('questionDir', questionDir)
See this working fiddle which has just added questions.Cars into $rootScope to make answer look more relevant to your query.
To preserve content of your directive element :
What i see in your question is : Element <question-dir> has child content / inner text print from direcetive in it and it gets overridden by Directive Element <h1>Made by a directive!</h1> completely.
That's the default nature of Angular : Element's (to which the directive is being applied) children will be lost to Directive Element. If you wanted to perserve the element's original content/children you would have to translude it.
Use transclude : true in Directive Definition Object and add <div ng-transclude></div> where you want directive Content / text print from direcetive to be included.
Code snippet :
function questionDir() {
return {
transclude : true,
template: "<div ng-transclude></div><h1>Made by a directive!</h1>"
};
}
Check this one for transclude changes.
Check these 2 links for more information on Transclude :
Understanding the transclude option of directive definition
Understanding transclude with replace
You had a few things wrong in you code:
In your IIFE closure you need to pass in angular
You need to normalize the directive name to kabab case in your html element name: `'
You need to include the reference to the directory factory in your directive registration: .directive('questionDir', questionDir);
Missing module dependencies array: .module("autoQuote", []) -- you should only have this defined once with the dependancies, [] so ignore this if you already have it elsewhere in your code.
Missing restrict in the Directive Definition Object: restrict: 'E',
If you click on Run code snippet you will see this directive now works.
(function (angular) {
"use strict";
angular
.module("autoQuote", [])
.directive('questionDir', questionDir);
function questionDir()
{
return {
restrict: 'E',
template: "<h1>Made by a directive!</h1>"
};
}
}(angular));
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="autoQuote">
<question-dir>print from direcetive</question-dir>
</div>
Finally, if you are using AngularJS 1.5 or greater you should consider using the component syntax for this.

how to call the directive with different templateurl and conroller from another controller function?

I am completely new to AngularJS.
I am having the below HTML code
<div id="products" ng-contoller="productController" listProducts></div>
This will execute when angular js runs and I am getting all the products in the attribute. My directive is like below
learnApp.directive('listproducts',function(){
return{
restrict: 'A',
templateUrl:"products.php",
controller:"productController"
};
});
But I am having the shopping cart with selected product ids
so when the cart is clicked, I want to replace the directive with the selected products details.
<span class='glyphicon glyphicon-shopping-cart' ng-click='productDetails();'>{{totalProductList.totalProducts}}</span>
like below
the productDetails function should call the directive and replace it with productDetails.php
learnApp.directive('listproducts',function(){
return{
restrict: 'A',
if(shoppingCartImageclicked)
{
templateUrl:"productDetailsForSelectedItems.php",
controller:"productDetailControllerForSelectedItems"
}
else // initial load
{
templateUrl:"productDetails.php",
controller:"productDetailController"
}
};
});
this seems like a very bad idea to be honest. that is not how directives are meant to be used.
one directive should have one task. your directive has two tasks, hence should be two directives.
you will have to define a second directive and solve the change of view programatically (at best with javascript) for example by using a different route, or at least ng-show/ng-if.

AngularJS- How to call controller's function on click of checkbox which is created in directive template

I am new to angularJS. I want to create checkboxes dynamically using templates in directives. I created controller and directives in separate files. I am creating checkbox in template in directive and want to invoke controller's function on ng-click of check box but I am unable to do so.
Here is my code sample.
Controller:
var app=angular.module('abc',[]);
app.controller('DemoCtrl', function($scope) {
$scope.ctrlFn = function(test) {
alert("hi "+test);
console.log(test);
}
});
I referred the https://github.com/iVantage/angular-ivh-treeview to create checkboxes tree view. I inlcuded all the css and js files in my sample. From the link I got the following js file which is creating the checkboxes in template in directive as shown below:
ivh-treeview.min.js:
angular.module("ivh.treeview",[]),
angular.module("ivh.treeview").directive("ivhTreeviewCheckbox",[function(){
"use strict";
return{restrict:"A",
scope:{node:"=ivhTreeviewCheckbox"},
require:"^ivhTreeview",
link:function(a,b,c,d){
var e=a.node,f=d.opts(),g=f.indeterminateAttribute,h=f.selectedAttribute;
a.isSelected=e[h],
a.ctrl=d,
a.$watch(function(){return e[h]},function(b){a.isSelected=b}),
a.$watch(function(){return e[g]},function(a){b.find("input").prop("indeterminate",a)})},
template:['<input type="checkbox"','ng-model="isSelected"','ng-change="ctrl.select(node, isSelected)" />'].join("\n")}
}]);
View:
<div class="col-sm-8" ng-controller="DemoCtrl as demo">
<div ivh-treeview="demo.bag"
ivh-treeview-selected-attribute="'isSelected'"
ivh-treeview-id-attribute="'uuid'"
ivh-treeview-expand-to-depth="0">
</div>
</div>
I want to call ctrlFn() on click of checkbox created in directive template. Please suggest a way to do the same.
Thanks In Advance
Sounds like you are looking for a two-way binding between directive and parent $scope.
// parent controller scope
$scope.person = { name:'coldstar', class:'sexy beast' };
// directive declaration
<div a-person='person'></div>
// directive code
scope: {
// the = sign is the key. could also be a function instead of object
innerPerson: "=aPerson"
},
link: function (scope, elm, attr){
// now this change will be reflect in the parent controller also
scope.innerPerson.name = "not coldstar anymore";
}
Edit:
I also noticed "'isSelected'" which should be "isSelected" if isSelected is a $scope.* entity

{{$index}} of ng-repeat computed after linker function of angular directive. $compile it?

html
<div repeater ng-repeat='item in items' class='first' id = '{{$index}}' > {{item}} </div>
angularjs directive:-
angular.module('time', [])
.directive('repeater', function() {
var linkFn = function(scope, element, attrs){
var id = $(element).attr('id');
alert(id); // {{$index}}
} ...
dynamic id created inside ng-repeat, when evoked inside directive displays as {{$index}} instead of value = 0, 1, 2 ...
How do you ensure when Linker function in directive executes the dynamic ids are used? I think it can be done using $compile inside the directive. But i can't quite figure how?
$compile(element)(scope)
is the syntax. But obviously the wrong order.
If you really need the IDs to be already populated you can run the relevant code inside scope.$evalAsync or $timeout to make sure the binding has been updated first. I would suggest trying hard to avoid needing to inspect the DOM and relying instead on your model for any of this information, using scope.$index.
Your directive is already in the process of being compiled when and linked when your link function is reached so I think recompiling wouldn't help. It is just a case of waiting long enough to finish the linking and start a $digest where the attributes will actually be updated. This is exactly what $evalAsync will do for you.
Example here: http://plnkr.co/edit/n77x4C?p=preview
I hope this will help you
directive
angular.module('time', []).directive('repeater', function() {
return{
restrict:"E",
scope:{
itemarray: "=itemarray"
}
template:"<div ng-repeat='item in itemarray' class='first' id = '{{$index}}' > {{item}} </div>",
link:linkFn
}
var linkFn = function(scope, element, attrs){
var id = $(element).attr('id');
alert(id); // {{$index}}
} ...
html
<repeater itemarray='itemarray"></repeater>

Categories

Resources