Angular: How to force recompile directive? - javascript

HTML:
<div ng-repeat="obj in arr">
<custom-directive status-stored-in="obj"></custom-directive>
</div>
Problem:
I have page-turn built in for the large amount of objs. Which means that the value of arr, representing the current page of objs, will change. However, the obj in the status-stored-in="obj" part is not refreshed with the change.
Right now my solution is to add a ng-if in customDirective, flickering its value back and forth to have it force recompiled. Is there any other equivalent, neater way to deal with this?
Edit:
The start of the custom directive:
module.directive 'checkbox', (checkboxHooks) ->
restrict: 'E'
scope:
hook: '='
hookedTo: '='
statusStoredIn: '='
templateUrl: 'templates/checkbox.html'
link: (scope, element, attr) ->
The gist is that it needs to get hold of an object, for storing the checked status. The whole of it can be found here: [coffee/js].

Inside your directives link function you need to watch status-stored-in for changes and then recompile it e.g.:
link: function(scope, element) {
scope.$watch('statusStoredIn', function() {
element.html(statusStoredIn);
$compile(element.contents())(scope);
});
}

Related

How to deal with css and custom directive in angulajs

Consider a custom directive in angularjs:
.directive('myDir', function() {
return {
restrict: 'E',
template: "..."
}
})
As far as I see the tag <my-dir></myDir> has no default style associated (or, at least, it is not a block tag). Now I want to style it and place in the right point of my page. I have 2 alternatives:
1) Use this template:
.directive('myDir', function() {
return {
restrict: 'E',
template: "<div class="layout">...</div>",
}
})
And css:
.layout {
/* bla bla bla */
}
But this introduce a new unnecessary level in the DOM three, since if I wrote something like <my-dir class="layout"></my-dir> with the proper css attached it would have worked anyway BUT I'll have to remember to add the same css class every time I use <my-dir> inside my code (and this is not DRY).
2) This led me to add style inside post-link function:
.directive('myDir', function() {
return {
restrict: 'E',
template: "...",
link: function(scope, element, attrs) {
element.addClass('layout');
}
}
})
Which strategy is better? Are there pros or cons I can't see?
UPDATE:
Using replace: true in directive definition is not an option, since it has been deprecated and when using bootstrap things like <my-dir class="visible-xs"></my-dir> may be useful.
You could solve this problem by having replace: true option in your directive, which will basically replace your directive DOM with the template which you have given from your directive.
Directive
.directive('myDir', function() {
return {
restrict: 'E',
template: "<div class="layout">...</div>",
replace: true //added replace true property
}
})
Resultant HTML
<div class="layout">..Content..</div>
Like above I shown, you could get rid off the unnecessary HTML tag by simply using replace option of directive.
Demo Plunkr
Note
As per Angular Docs replace option has been deprecated in Angular2,
but in your case you can avoid that.
But if you really wanted to go avoid the use of replace option of directive then I'd suggest you to use your directive as an attribute instead of going for an element but then you need to take <div class="layout"> outside and then your could put your directive over that like <div class="layout" my-dir>. But if you again think over it in Angular2 perspective, you are changing your component structure to old angular way using attribute/class

$compile doesn't work inside Angular directive

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.

Transclude then replace with angularjs

I would like to create a directive that replaces :
<my-overlay class="someOverlay">
<h4>Coucouc</h4>
</my-map-overlay>
With :
<div class="someOverlay default-overlay">
<h4>Coucouc</h4>
</div>
The replace method is yet deprecated.
How to write a directive that manipules the DOM creating a div, adds the default-overlay class to the previous defined one, transcludes and replaces the <my-map> directive ?
Is it possible to divide the process as following : DOM manipulation in compile and transcluding in link?
This is the commit for 'replace' to be removed:
https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb
If you read some of the last comments it seems that replace may not be deprecated after all. However this could be a way to achieve what uou want:
.directive('myOverlay', function(){
return {
restrict: 'E',
template: '<div ng-transclude></div>',
transclude: true,
link: function (scope, element) {
element.children()[0].setAttribute('class', element.attr('class') + ' default-overlay');
element.replaceWith(element.children()[0]);
}
}
});
http://jsfiddle.net/b6ww0rx8/10/

How can I use a directive wrapper for ng-grid with the grid options determined by a directive attribute?

I am attempting to create a re-usable directive wrapper for ng-grid where I can apply the location of the ng-grid options through the use of an attribute.
Here is the skeleton of the code which gets very close to what I want:
angular.module('myApp').directive('grid', function() {
return {
restrict: 'E',
template: '<div class="gridStyle" ng-grid="someObject.gridOptions"></div>',
link: function(scope, element, attributes) {
// no code here necessary, to use the hard-coded ng-grid options
}
}
}]);
What I would like to do is supply the variable for ng-grid by using an attribute, similar to this:
<grid dataLocation="someObject.gridOptions"></grid>
I've tried using the compile and the link options with multiple methods, including reading the attributes, and then using the element.html() method to update the html to set the ng-grid attribute to "someObject.gridOptions", as well as using {{someScopeVariable}}, and setting scope.someScopeVariable to "someObject.gridOptions" in the linking function. I have verified using the chrome's html inspector that the div's attribute looks correct, but I have not been able to get the item to show up on my page.
I suspect I'm running into issues since the ng-grid is already a compiled directive. Is there any way I can pull this off? I've tried a large number of compile and linking methods with no success yet.
To access the original template's attributes, the template option of directive should be defined as a function as follows:
angular.module('myApp').directive('grid', function() {
return {
restrict: 'E',
template: function(element, attrs){
return '<div ng-grid="' + attrs.dataLocation + '"></div>';
}
};
});
Your HTML stays the same:
<grid dataLocation="someObject.gridOptions"></grid>

Angular directive $observe jquery widget initialization issue

I made a directive that wraps freebases jquery search widget. I want the user to be able to change the search language on the fly so I'm using $observe to watch the 'lang' attribute.
The issue is the plugin reinitialized every time the attribute changes but does not end the execution of the previous initialization. When the user selects terms, I'm adding it to an array. If the lang attribute has been modified, the selected term is added for every change to the attribute.
I could just filter the array and only allow items with the same ID to appear once, but I'm wondering if there is a better solution.
See plunker here.
The directive:
directive('suggest', function() {
return {
restrict: 'E',
template: "<input type='text'>",
replace:true,
scope:{onSelect:'&'},
link: function(scope, element, attrs) {
attrs.$observe('lang', function(value) {
$(element).suggest({
lang: value
})
.bind("fb-select", function(e, info) {
console.log(info);
scope.onSelect({data:info});
scope.$apply();
});
});
}
};
});

Categories

Resources