How to wrap an element using an angular directive? - javascript

I'm trying to create an angular component that adds a prefix to an <input> element. Something like this:
The idea would be to use it like this:
<input type="text name="url" input-prefix="http://">
For this I need to wrap the <input> around a <div> container which will also include a <span> with the prefix and add some custom CSS.
I believe I need to use the directive compile function to achieve this because of the DOM manipulation, but I don't understand very well how it works and I haven't found much documentation.
The issue I found so far is that after manipulating the DOM on the compile function, the <input> appears to be completely unusable, I cannot even type in it. Here's my fiddle:
http://codepen.io/anon/pen/vokxy - Can't type on the <input> inside the green wrapper.

You don't manipulate the DOM in the compile phase, you do it in link phase (postLink actually), since the DOM isn't actually set up correctly until then. There's an example in the Developer Guide.

Moved your code to link method, here's a working example.
Example:
var app = angular.module('my-app', []);
app.controller("myController", function($scope) {
$scope.ctrl = this;
$scope.name = "w";
$scope.minLength = 3;
});
app.directive("inputPrefix", function() {
return {
restrict: 'A',
link: function(scope,element, attrs) {
var wrapper = angular.element(
'<div class="prefixed-input">');
element.after(wrapper);
element.removeAttr("input-prefix");
wrapper.append(element);
}
}
});
Live example: http://jsfiddle.net/choroshin/cnc5m/

Related

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.

ng-bind-html with UI Bootstrap directives

I don't think this is directly an issue but I don't know how to do this. I'm trying to dynamically load content that uses UI Bootstrap directives but when the content is loaded UI Bootsrap components don't work. Being more specific, tooltips don't work. Here is the important code:
<div ng-bind-html="trustSnippet(f.field.contentAfter)"></div>
The javascript
$scope.trustSnippet = function(snippet) {
return $sce.trustAsHtml(snippet);
};
The HTML I'm trying to inject is:
<i class="fa fa-exclamation-circle" tooltip-placement="right" tooltip="On the Right!"></i>
Any clues?
TY
This is because ng-bind-html doesn't compile the inserted elements, and so the UI Bootstrap directives - or any other directive or expression, for that matter, would not work.
If you are getting the HTML from a particular location, you could simply use ng-include.
For static location:
<div ng-include="'path/to/html'"></div>
Or, if the location is dynamic and stored in a scope-exposed variable: $scope.path = "path/to/html";:
<div ng-include="path"></div>
Otherwise, if the HTML itself with Angular expressions/directives is dynamically generated or imported (a rare case, which should make you re-examine your design to make sure that you are not offending any best practices), you would need to compile it using $compile service, and it is better done using a directive:
app.directive("ngBindHtmlCompile", function($compile, $sce){
return {
restrict: "A",
link: function(scope, element, attrs){
scope.$watch($sce.parseAsHtml(attrs.ngBindHtmlCompile), function(html){
var el = angular.element("<div>").html(html);
element.empty();
element.append(el.children());
$compile(element.contents())(scope);
})
}
};
});
Don't forget to add "ngSanitize" as a dependency. The usage is:
<div ng-bind-html-compile="html"></div>
I was facing the same problem. The following method worked for me.
In HTML,
<div ng-bind-html="f.field.contentAfter | unsafe"></div>
In Javascript,
app.filter('unsafe', function($sce) {
return function(val) {
return $sce.trustAsHtml(val);
};
});

Use ng-show and etc. in directive "A"

I saw related asks, but dont understand anyway.
If I have directive: http://pastebin.com/QtAzGv62
And I need to add "ng-show" (or any other standart angular directive) functional to this directive (for related DOM element, that is ), that must depends on AuthService option (named "logged").
How?! :)
Since you are not binding to a particular 'element' you just need to make the methods available to your directive scope so change
elem.bind('click', function() {
AuthService.tryLogin();
});
to
scope.loggedIn = false;
scope.tryLogin = function(){
AuthService.tryLogin();
scope.loggedIn = true;
}
Then you can do in your directive html
<div ng-show="loggedIn">You dun logged in man!</div>

Conditional "multiple" attribute in <input type="file"> with AngularJS

I need an upload form field that may or may not allow the user to select more than one file.
I know I can do something like:
<input type="file" multiple ng-if="allow_multiple">
<input type="file" ng-if="!allow_multiple">
But, we know that is not ideal.
I tried
<input type="file" ng-multiple="allow_multiple">
But that doesn't work.
It seems that AngularJS has no such ngMultiple directive, but everyone is using it anyway (or am I missing something?)
Anyway, what is the best way to accomplish that?
EDIT: From thw answers so far it really seems like there's no pretty way to do this.
I opened this issue on their tracker, let's see what we get :-)
https://github.com/angular/angular.js/issues/7714
The easiest way is to write your own ngMultiple directive.
HTML (relevant):
<label><input type="checkbox" ng-model="allowMultiple"> Allow multiple</label>
<hr>
<input
type="file"
class="hide"
accept="image/*"
ng-multiple="allowMultiple">
JS:
angular
.module('app', [])
.controller('appCtrl', function($scope) {
$scope.allowMultiple = false;
})
.directive('ngMultiple', function () {
return {
restrict: 'A',
scope: {
ngMultiple: '='
},
link: function (scope, element) {
var unwatch = scope.$watch('ngMultiple', function (newValue) {
if(newValue) {
element.attr('multiple', 'multiple');
} else {
element.removeAttr('multiple');
}
});
}
};
});
Plunker
I come to this page for same issue with Angular 2
And finally fixed like this:
<input type="file"
[accept]="extAccepts"
[multiple]="(maxFiles > 1)" />
Note: both file type (extAccepts) and maxFiles are reading from component as #input() from the user.
Hope it will help for someone!
Besides the difficulty of doing this there is also the issue that some browser will not evaluate multiple="false" (Safari 8 on file input for ex). So the multiple attribute needs to be conditionally written.
I would wrap your html in a directive and conditionally apply the attribute within the directive such as:
var input = elem.find('input');
if(condition)
input.attr('multiple', 'true');
Where the condition could be any directive attribute.
Try using ng-attr-
ng-attr-class="{{someBoolean && 'class-when-true' || 'class-when-false' }}"
If you prefix any attribute with ng-attr-, then the compiler will strip the prefix, and add the attribute with its value bound to the result of the angular expression from the original attribute value.
I am on mobile, sorry for the short answer.
I would hide two different file inputs, one with the multiple attribute and one without. You can use the ng-if directive to achieve that.
Edit: I'm so sorry, seems like you don't want to do it that way, even though it's completely valid.
You could write your own directive for it however, it's really simple.
You can use ngSwitch directive for this. Take a look in AngularJs documentation for more details.
https://docs.angularjs.org/api/ng/directive/ngSwitch
In this demo example the directive switch between input file with and without multiple based in a scope param passed for directive using ngSwitch directive.
Demo using ngSwitch
Another idea is to use ngShow/ngHide for this. In this demo example input file is show/hide based in param and have a directive for get input value(s) and set in a $scope param.
Demo using ngShow + directive
Solution is pretty simple since you are using directive or component - just manipulate DOM on the right moment. Take a glance here:
app.component('myComponent', {
templateUrl: 'tmpl.html',
bindings: {
str: '#'
},
controller: function ($element) {
this.$postLink = function () {
$element.find('input').attr('multiple', 'multiple');
}
}
}

create HTML element dynamically

I am very new to angular js. I want to create an input box on click of particular div. Here I need to create element on div which repeating.
<div><div ng-repeat ng-click="create();"></div><div>
What will be the best way to do so?
DOM manipulation in Angular is done via directives (There is paragraph on 'Creating a Directive that Manipulates the DOM' here)
First, read through this excellent article: How do i think in Angular if i have a jQuery background
The Angular Team also provides a pretty neat tutorial, which definetly is worth a look: http://docs.angularjs.org/tutorial
While Angular is pretty easy and fun to use once you have wrapped your head around the concepts, it can be quite overwhelming to dive into the cold. Start slow and do not try to use each and every feature from the beginning. Read a lot.
I strongly recommend egghead.io as a learning resource. The video-tutorials there are bite-sized and easy to watch and understand. A great place for both beginners and intermediates. Start from the bottom here.
Some folks have done great things with Angular. Take a look at http://builtwith.angularjs.org/ and check out some source code.
Use an array and ng-repeat to do that. Have a look at the following code.
I crated scope variable as an empty array. Then created a function to add values to that array.
app.controller('MainCtrl', function($scope) {
$scope.inputFields = [];
$scope.count = 0;
$scope.addField = function(){
$scope.inputFields.push({name:"inputText"+$scope.count++});
}
});
I used ng-repeat with this array. and called the function on the click event of a div.
<div ng-click="addField()">Click here to add</div>
<div ng-repeat="inputField in inputFields">
<input type="text" name="inputField.name">
</div>
Check this working link
Update - Show only one text box on click
I created addField() as follows.
$scope.addField = function(){
$scope.newTextField = "<input type='text' name='myTxt'>";
}
To render this html in my view file I created a new directive called compile as follows.
app.directive('compile', function($compile) {
// directive factory creates a link function
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
Then used this directive in my view.html file
<body ng-controller="MainCtrl">
<div ng-click="addField()">Click to Add</div>
<div compile="newTextField"></div>
</body>
click here to view the working link

Categories

Resources