Directive Inside ngRepeat Doesn't Update - angularjs

I'm trying to get a custom directive to work inside of ngRepeat, but can't get the obvious to work. In this case I don't 'believe' I want to isolate scope. I suspect this is simply a matter of framework ignorance, but can't seem to figure it out. I have a plunk here to show: http://plnkr.co/edit/LNGJHtbh7Ay0CYzebcwr

The link function runs only once for each instance of the sel directive, so it renders the arr.name value one time. In order to make it aware of future changes, you can use a $watch:
link: function(scope, elm, attr){
scope.$watch('arr.name', function() {
elm.text(scope.arr.name)
});
}
Plunker here.
You can find more information on that in the $rootScope documentation.

Related

jQuery used inside Angular

I know this is a subject discussed too many times. I know that jQuery may create problems. I know most sites advise try to avoid the use of jQuery, however my question is:
How may I manipulate the DOM inside an Angular directive if not using jQuery? Even in Angular docs there is intensive use of jQuery lite inside directives. I 'd appreciate your opinions in that.
In link function you can actually manipulate DOM.
app.directive('sampleDirective', function() {
return {
...
link: function(scope, ele, attr, ctrl) {
//here actually you can manipulate DOM using ele.
}
}
})
Angular has bilt-in jqLite(subset of jQuery). Find it here.
Note: Do DOM manipulations only in directive.

Transclude partial content

I am trying to create a "sticky table header" component for which I need to copy parts of the transcluded content of my directive.
Depending on how I transclude the content, it works only partially at best: with $compile, expressions are updated when the underlying data changes, but ng-repeat does not seem to work at all. It does not even render the first time, let alone update later. Simply appending the partial content I found does not seem to work at all: element.append($(transcludedEl).find('.wrapper'));
To illustrate my point, I have created a plunkr using three versions of the same code: http://plnkr.co/edit/xkAkzl8ID3m5Ras3Ww31
The first is super-simple direct ng-repeat, which only serves to show what should happen.
The second uses a directive that transcludes its full content, which works but is not what I need.
The third (reproduced below) uses a directive to try and include only part of its content, which is what I need, but which does not work.
The interesting bit is this:
app.directive('stickyPartial', ['$compile', '$timeout', function($compile, $timeout) {
return {
restrict: 'E',
transclude: true,
template: '<div></div>',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(transcludedEl) {
// this is what i want to achieve - not working
// element.append($(transcludedEl).find('.wrapper'));
// neither is this, though it does support expressions
$compile($(transcludedEl).find('.wrapper'))(scope, function(clone) {
element.append(clone);
});
});
}
};
}]);
So far, I have tried several combinations of $compile, .clone() and .html(), but to no avail. I can neither get a working partial DOM tree from the compiled template, nor a useful partial HTML source with ng-repeat intact that I can then compile manually.
As a last resort, I might try copying the DOM after angular is done (which seemed to work, previously) and then manually repeat this process every time the relevant model data changes. If there is another way, thought, I'd very much like to avoid this.
Using https://stackoverflow.com/a/24729270/2029017 I found a solution with compile that does what I want: http://plnkr.co/edit/V4yUbiAD9EAaaJXihziv

AngularJS directive: Expression 'undefined' used with directive ... is non-assignable

I'm a 2 week old Angular noob, and I've been attempting my first ever directive for over a day now. :P I've read this and this, which seem good directives introductions and a bunch of Stackoverflow answers, but I can't get this working.
<div ng-app="App" ng-controller="Main">
<textarea caret caret-position="uiState.caretPosition"></textarea>
{{uiState.caretPosition}}
</div>
and
angular.module('App', [])
.controller('Main', ['$scope', function ($scope) {
$scope.uiState = {};
$scope.uiState.caretPosition = 0;
}])
.directive('caret', function() {
return {
restrict: 'A',
scope: {caretPosition: '='},
link: function(scope, element, attrs) {
var $el = angular.element(element);
$el.on('keyup', function() {
scope.caretPosition = $el.caret();
});
}
};
});
Fiddle is here. I'm basically trying to get the caret position within a textbox. I'm using this jQuery plugin in the fiddle (source of the .caret() method, which should just return a number).
My questions are
Do I even need a directive here? Ben Nadel says the best directives are the ones you don't have to write (amen to that!)
If yes, is this the right way to go about the directive? Ie isolated scope with two-way bound variable?
If yes, when I run this code locally, I keep getting the error Expression 'undefined' used with directive 'caret' is non-assignable!. I've read the doc and as far as I can tell I've followed instructions for how to fix to no avail.
BONUS: Why in jsfiddle do I get no such error. The fiddle just fails silently. What's with that?
Thanks!
My solution was harder to find out here, but easier to implement. I had to change it to the equivalent of;
scope: {caretPosition: '=?'},
(Note that the question mark makes the attribute optional. Prior to 1.5 this apparently wasn't required.)
You were close.. The main problem is changing a scope variable inside an event that angular doesn't know about. When that event occurs, you have to tell angular that something changed by using scope.$apply.
$el.on('keyup', function() {
scope.$apply( function() {
scope.caretPosition = $el.caret();
});
});
Working fiddle here
For the questions:
yes, I don't think there's a way around having to write a directive for this.
in this case, it seems fine.
not sure, there are no problems in the jsfiddle. It sounds like you're setting carat="something" somewhere? check to make sure the html is the same as what's in the fiddle.
same as 3
Also note that if you click and move the caret, it won't update because it's only listening for keyup.

AngularJS - add http prefix to url input field

Our app is being ported from jQuery to AngularJS with bootstrap (angular-ui bootstrap).
One handy feature that was covered by the following excellent post was to add "http://" prefix to a URL field if it did not already have a prefix: http://www.robsearles.com/2010/05/jquery-validate-url-adding-http/
I am trying to achieve the same in AngularJS via a directive, but cannot get the directive to alter the value of the ng-model as it is being typed.
I've started simple by trying to get a fiddle to add a "http://" prefix on EVERY change for now (I can add the logic later to only add it when needed). http://jsfiddle.net/LDeXb/9/
app.directive('httpPrefix', function() {
return {
restrict: 'E',
scope: {
ngModel: '='
},
link: function(scope, element, attrs, controller) {
element.bind('change', function() {
scope.$apply(function() {
scope.ngModel = 'http://' + scope.ngModel;
});
});
}
};
});
Can anyone please help me to get this to write back to the ngModel. Also, the field I need to apply this new directive to already has a directive on it with isolate scope so I'm assuming I can't have another one with isolate scope - if this is so can I achieve it without isolate scope?
A good way to do this is by using the parsers and formatters functionality of ng-model. Many people use use ng-model as just a binding on isolated scope, but actually it's a pretty powerful directive that seems to lack documentation in the right places to guide people on how to use it to its full potential.
All you need to do here is to require the controller from ng-model in your directive. Then you can push in a formatter that adds 'http://' to the view, and a parser that pushes it into the model when needed. All the binding work and interfacing with the input is done by ng-model.
Unless I can find a good blog on this (very much open to comments from anyone who finds them), an updated fiddle is probably the best way to describe this, this support for URL to be entered manually with 'http' or 'https', as well as auto-prefixing if none of them: http://jsfiddle.net/jrz7nxjg/
This also solves your second problem of not being able to have two isolated scopes on one element, as you no longer need to bind to anything.
The previous comment provided by Matt Byrne doesn't work for the https prefix. Checkout the updated version based on previous answers that works with **https prefix too!
This was missing there
/^(https?):\/\//i
http://jsfiddle.net/ZaeMS/13

using ng-show in directive template

I am trying to define a directive to show a checkmark when a question has been answered.
questModule.directive('showCheckmarkOnDone', function () {
return {
transclude: true,
template: "<div ng-transclude></div><img src='img/checkmark.svg' " +
"ng-show='scope.questions[type][number-1].done' " +
"class='checkmark' style='height: {{height}}px; width: {{height}}px;'>",
link: function (scope, element, attrs) {
scope.height = $(".app").height()/12;
scope.number = attrs.number;
scope.type = attrs.type;
}
};
});
The scope.questions[type][number-1].done exists in my controller for the page, and I can see that it is being updated correctly when I press the done button. However, the directive does not register the change. I tried putting a $watch in the link function - that didn't help either. I think I'm a bit confused about how to get my directive scope to play nicely with my controller scope - any thoughts on how I can give this directive access to an object that exists in an outside controller? (scope.questions)
This is not a valid way to define directive scope:
scope: '#'
You can either (a) not define it, (b) set it to true, or (c) set it to {} (an object). See the docs for more info: http://docs.angularjs.org/guide/directive (find the header: "Directive Definition Object")
In this case, I imagine if you remove it, you may be OK, because it will allow scope.questions to be visible from your directive. If you reproduce the issue in jsfiddle.net or plnkr.co, it would be much easier to assist you.
Edit:
Your directive generally should have 1 parent element
You should not use scope in your directive's HTML, it's implied
I think you said as much in your comment, but you should strive to make your directive more generic by passing in scope.questions[0][0].done instead of looking it up in your directive's HTML using attributes.
Here's a working example: http://jsfiddle.net/EZy2F/1/

Resources