like the title says, I'm attempting to make an attribute directive that wraps its parent and allows me to toggle between editing and showing the actual model value..
In short:
<input ng-model="model" allow-edit="editing" />
Would end up looking like:
<div>
<div ng-hide="editing">{{model}}</div>
<input ng-show="editing" ng-model="model"></input>
</div>
If everything went right.
However, I keep on getting something more along the lines of:
<input ng-model="model">
<!-- html from allow-edit directive's template --!>
</input>
I've used input as an example here, but I'd like to be able to wrap arbitrary content (select, etc) as well...
Has anyone been able to make a directive that wraps other content on the same element? Is there a better way to do this that I'm not considering?
Thanks for your help!
I hope this answers your question:
In directive options:
set replace: true
set transclude: "element"
use ng-transclude where ever you want to put the original element within the wrapper template.
plunker link
example:
js:
var app = angular.module("test", []);
app.directive("myCustomInput", function($rootScope){
return{
restrict: "A",
replace: true,
transclude: "element",
template: "<div class='input-wrap'>"+
"<div ng-transclude></div>"+
"<i class='glyphicon glyphicon-chevron-down'></i>"+
"</div>"
}
});
HTML:
<input my-custom-input type="text" />
What you want to do is replace:true but maintain "=ngModel"
replace:true,
scope:{
mymodel:"=ngModel",
editing:"=allowEdit"
}
Heres a Plunker
I think that using replace : true should enable you to replace the original content. Take a look at this StackOverflow answer:
Here
If you had a bit more of your directive, I could have a go at getting it to work, but hopefully you can use the plnkr of the other answer to work it out.
Related
I am facing problems with two way data binding in angular js. Here is the sample code.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body ng-app="">
<div>
<p>Input something in the input box:</p>
<p>Name: <input type="text" ng-model="name"></p>
<div id="jack">
</div>
<script>
$("document").ready(function(){
$("#jack").append("<p ng-bind='name'></p>");
});
</script>
</body>
</html>
Over here I am dynamically adding a paragraph with ng-bind to a div called jack using jQuery
For some reason when I type something in input box it is not reflecting in paragraph with ng-bind property.
I am a novice in angular js and would request you to provide me a simple solution to tackle this issue.
You cannot use jQuery to modify DOM outside Angular this way. Angular does not know about that binding as it was not compiled by Angular.
To solve this particular sample, simply remove the jQuery script and change the HTML to this:
<p>Input something in the input box:</p>
<p>Name: <input type="text" ng-model="name"></p>
<p ng-bind="name"></p>
The example above will work, but I imagine this was not a real-world example. If you post a more specific scenario, I can update my answer to help you solve that.
Edit
Based on your comment, I would create a simple directive to which you could pass your template, it would compile the template and inject the compiled template in the DOM.
Directive:
function myTemplateCompile($compile) {
return {
restrict: 'E',
link: link
}
function link(scope, elem, attrs) {
attrs.$observe('template', (template) => {
elem.html(template);
$compile(elem.contents())(scope);
});
}
}
HTML
<my-template-compile template="any HTML you might need including bindings"></my-template-compile>
You can then change the template attribute of the directive on the fly and it will re-compile and update the DOM based on the new value.
The example above should point you in the right direction. I just have to warn you, that this might be quite dangerous. If the content you are injecting is coming from some user input, this might have very severe security implications. Please make sure you are not exposing your application to attacks.
Well first we need to define the app and create custom directive.
var myApp=angular.module('myApp',[])
.controller('myCtrl',function($scope){
$scope.name="Your name";
})
myApp.directive('myDirective', function() {
return {
restrict: 'E',
template: '<span data-ng-bind="name"></span>'
};
});
After this you need to use above created directive as like below
<my-Directive></my-Directive>
I have a form on a page which is validated just fine if all the form elements are on the page, http://jsfiddle.net/nkanand4/6za8h8xg/1/.
<div ng-form="myform">
<div>
Name: <input type="text" ng-model="user.name" required name="input"/>
</div>
</div>
This, however, stops working if I am doing a wizard kind of form, where each step is populated using directive, http://jsfiddle.net/nkanand4/pe17afvq/2/.
<div ng-form="myform">
<form-element step="selectedStep"></form-element>
</div>
Any ideas on how to solve this will be appreciated. Thanks.
EDIT:
I initially had started with ng-include but dropped that approach because if I use it, the data is not persisted from step 1 to step 2 to back to step 1. Reason being a new scope is created when you move back and forth. Hence I needed a way to keep all the data under a scope property, like $scope.data.user.name, so that i can pass back $scope.data when its requested.
Don't compile HTML yourself, you can let Angular do it properly for you:
.directive('formElement', function($log, $templateCache, $compile) {
return {
template: '<div ng-include="\'step\' + selectedStep.step + \'.html\'">',
link: function(scope, elem, attrs) {
// ... nothing really here
}
};
});
Demo: http://jsfiddle.net/pe17afvq/4/
Finally got it sorted out. This was reported as a bug on angularJS issue tracker, https://github.com/angular/angular.js/issues/7519. The trick is to use the clone in the link function that is returned when you are using $compile.
$compile(html)(scope, function(clone) {
elem.empty().append(clone);
});
Updated my jsfiddle accordingly, and it works!
I have the following plunkr:
http://plnkr.co/edit/M1uwZxZP7sXp5sPw7pxf?p=preview
What I want to do is: I'd like to build an angular code to generate inputs automatically inside a form, given a json with it's description
EXAMPLE:
{'name': 'username', 'description': ['text', 'maxlength=16', 'required']}
To do so, I'm using a custom directive that appends input to the tag
<custominput></custominput>
Turns
<custominput>
<input type='text'/>
</custominput>
and THEN I add any other validation attributes, like minlength and maxlength.
In my plunkr, I can add attributes to the custominput tag, like that:
<custominput compiled="compiled" disabled="disabled"></custominput>
But HOW can I add these attributes to the input tag (that means, the child of custominput)??
UPDATE 1
This question can be summarized to:
How can I add an HTML element/attributes with angular directives FROM a directive
EXAMPLE: Turn this
<form name="form0">
<input custom-directive>
</form>
into this:
<form name="form0">
<input custom-directive type="text" ng-model="ctrl.username" ng-maxlength="15" ng-required="required">
</form>
from a directive
You would add them in the directive's template section. See code below:
html code
<form>
<input custom-directive>
</form>
directive code (im just writing this off the top of my head, it probably won't be a copy paste job for it to work, but it's definitely going in the right direction).
app.directive('customDirective', function() {
return {
restrict: 'A',
controller: function($scope, attrService) {
$scope.attributes = attrService.getAttrs;
},
link: function(scope, element, attrs) {
element.attr('name', scope.attributes.name);
// add more attributes
console.log(scope.attributes) // ensure attributes is being pushed through from directive controller.
}
}
});
To dynamically add attributes
What I tried to do the last days is something like that:
%myform(name='somename' ng-controller='whatever')
%myinput(ng-model='user.firstName' ...
controller has a user structure with firstName, lastname, ...
myform should just add some attributes to the <form>-tag, myinput should render a label, the input field and the errors when the somename-form-element is dirty and invalid. Pretty simple stuff.
As easy everything in AngularJS is, I had no chance. Had to move the ng-controller up to an extra div because nothing worked when the controller is defined in the myform tag (ng-click ignored, ...). Ugly but can live with that. No access to the scope in transcluded directives. Can be fixed with the link function and the append. Problem, the whole form validation stuff is not working when this fix is used. So I can have access to the form OR the scope.
What is the correct way to do this in AngularJS? I am really out of ideas and in despair after 4 days of trying and researching (learned the whole AngularJS in less than a day and not a single other problem).
Don't know if it makes sense to post ~ 30 different versions of trying to get this done. Maybe someone can provide a clean solution that is working and following the ideas behind the AngularJS framework (paypal beer thank you included).
Thank you very much in advance!
Anton
scope-fix-solutions:
http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
Issue with transcoded directives: https://github.com/angular/angular.js/issues/5489
... there are thousands of problems about directives and transcoding, seems to be the most ugly part in Angular. Wanted to include more links to solutions I tried, but I am only allowed to post 2.
If somebody needs the solution (small example) - whole example on Plunker - provided by Sander Elias, many thanks!
HTML:
<body ng-controller='AppController as appVm'>
<h1>Hello angular {{appVm.version}}</h1>
<my-form name="test">
<div class="input-group">
<span class="input-group-addon">#</span>
<input type="text" class="form-control" ng-model='appVm.user' required placeholder="Username" name='username' ng-minlength=5>
</div>
<div ng-hide="test.$pristine">
<div ng-show="test.username.$error.required" class="alert alert-danger" role="alert">this is a required field</div>
<div ng-show="test.username.$error.minlength" class="alert alert-danger" role="alert">At least 5 chars</div>
</div>
<button class="btn btn-primary" ng-show='test.$touched || test.$valid'>submit</button>
</my-form>
</body>
JavaScript:
angular.element(document).ready( function() {
// generate module
myModule = angular.module( 'myApp',[]);
// define a simple controller and put the user's name into the scope
myModule.controller('SampleController', ['$scope', function ($scope) {
$scope.user = {
name: 'Hugo'
};
}]);
// make the form directive (just put the two attributes in the form...)
myModule.directive('myform', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<form ng-attr-name="{{name}}" autocomplete="off" novalidate=true>' +
'<fix-transclude></fix-transclude>' +
'</form>',
scope: {
name: '#'
},
link: function (scope, elm, attr, contrl, transclFn) {
scope.$parent[scope.name] = scope[scope.name];
// attach the parent scope (originating one!) to the transcluded content!
transclFn(scope.$parent,function (clone) {
elm.find('fix-transclude').replaceWith(clone);
});
}
}
});
// bootstrap AngularJS
angular.bootstrap(document, ['myApp']);
});
Using AngularJS if I set a simple input text box value to something like "bob" below. The value does not display if the ng-model attribute is added.
<input type="text"
id="rootFolder"
ng-model="rootFolders"
disabled="disabled"
value="Bob"
size="40"/>
Anyone know of a simple work around to default this input to something and keep the ng-model? I tried to use a ng-bind with the default value but that seems not to work either.
That's desired behavior, you should define the model in the controller, not in the view.
<div ng-controller="Main">
<input type="text" ng-model="rootFolders">
</div>
function Main($scope) {
$scope.rootFolders = 'bob';
}
Vojta described the "Angular way", but if you really need to make this work, #urbanek recently posted a workaround using ng-init:
<input type="text" ng-model="rootFolders" ng-init="rootFolders='Bob'" value="Bob">
https://groups.google.com/d/msg/angular/Hn3eztNHFXw/wk3HyOl9fhcJ
Overriding the input directive does seem to do the job. I made some minor alterations to Dan Hunsaker's code:
Added a check for ngModel before trying to use $parse().assign() on fields without a ngModel attributes.
Corrected the assign() function param order.
app.directive('input', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if (attrs.ngModel && attrs.value) {
$parse(attrs.ngModel).assign(scope, attrs.value);
}
}
};
});
The Angular way
The correct Angular way to do this is to write a single page app, AJAX in the form template, then populate it dynamically from the model. The model is not populated from the form by default because the model is the single source of truth. Instead Angular will go the other way and try to populate the form from the model.
If however, you don't have time to start over from scratch
If you have an app written, this might involve some fairly hefty architectural changes. If you're trying to use Angular to enhance an existing form, rather than constructing an entire single page app from scratch, you can pull the value from the form and store it in the scope at link time using a directive. Angular will then bind the value in the scope back to the form and keep it in sync.
Using a directive
You can use a relatively simple directive to pull the value from the form and load it in to the current scope. Here I've defined an initFromForm directive.
var myApp = angular.module("myApp", ['initFromForm']);
angular.module('initFromForm', [])
.directive("initFromForm", function ($parse) {
return {
link: function (scope, element, attrs) {
var attr = attrs.initFromForm || attrs.ngModel || element.attrs('name'),
val = attrs.value;
if (attrs.type === "number") {val = parseInt(val)}
$parse(attr).assign(scope, val);
}
};
});
You can see I've defined a couple of fallbacks to get a model name. You can use this directive in conjunction with the ngModel directive, or bind to something other than $scope if you prefer.
Use it like this:
<input name="test" ng-model="toaster.test" value="hello" init-from-form />
{{toaster.test}}
Note this will also work with textareas, and select dropdowns.
<textarea name="test" ng-model="toaster.test" init-from-form>hello</textarea>
{{toaster.test}}
Update: My original answer involved having the controller contain DOM-aware code, which breaks Angular conventions in favor of HTML. #dmackerman mentioned directives in a comment on my answer, and I completely missed that until just now. With that input, here's the right way to do this without breaking Angular or HTML conventions:
There's also a way to get both - grab the value of the element and use that to update the model in a directive:
<div ng-controller="Main">
<input type="text" id="rootFolder" ng-model="rootFolders" disabled="disabled" value="Bob" size="40" />
</div>
and then:
app.directive('input', ['$parse', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if(attrs.value) {
$parse(attrs.ngModel).assign(scope, attrs.value);
}
}
};
}]);
You can of course modify the above directive to do more with the value attribute before setting the model to its value, including using $parse(attrs.value, scope) to treat the value attribute as an Angular expression (though I'd probably use a different [custom] attribute for that, personally, so the standard HTML attributes are consistently treated as constants).
Also, there is a similar question over at Making data templated in available to ng-model which may also be of interest.
If you use AngularJs ngModel directive, remember that the value of value attribute does not bind on ngModel field.You have to init it by yourself and the best way to do it,is
<input type="text"
id="rootFolder"
ng-init="rootFolders = 'Bob'"
ng-model="rootFolders"
disabled="disabled"
value="Bob"
size="40"/>
This is a slight modification to the earlier answers...
There is no need for $parse
angular.directive('input', [function () {
'use strict';
var directiveDefinitionObject = {
restrict: 'E',
require: '?ngModel',
link: function postLink(scope, iElement, iAttrs, ngModelController) {
if (iAttrs.value && ngModelController) {
ngModelController.$setViewValue(iAttrs.value);
}
}
};
return directiveDefinitionObject;
}]);
Hi you can try below methods with initialize of model.
Here you can initialize ng-model of textbox two way
- With use of ng-init
- With use of $scope in js
<!doctype html>
<html >
<head>
<title>Angular js initalize with ng-init and scope</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
</head>
<body ng-app="app" >
<h3>Initialize value with ng-init</h3>
<!-- Initlialize model values with ng-init -->
<div ng-init="user={fullname:'Bhaskar Bhatt',email:'bhatt.bhaskar88#gmail.com',address:'Ahmedabad'};">
Name : <input type="text" ng-model="user.fullname" /><br/>
Email : <input type="text" ng-model="user.email" /><br/>
Address:<input type="text" ng-model="user.address" /><br/>
</div>
<!-- initialize with js controller scope -->
<h3>Initialize with js controller</h3>
<div ng-controller="alpha">
Age:<input type="text" name="age" ng-model="user.age" /><br/>
Experience : <input type="text" name="experience" ng-model="user.exp" /><br/>
Skills : <input type="text" name="skills" ng-model="user.skills" /><br/>
</div>
</body>
<script type="text/javascript">
angular.module("app",[])
.controller("alpha",function($scope){
$scope.user={};
$scope.user.age=27;
$scope.user.exp="4+ years";
$scope.user.skills="Php,javascript,Jquery,Ajax,Mysql";
});
</script>
</html>
The issue is that you have to set the ng-model to the parent element to where you want to set the ng-value/value .
As mentioned by Angular:
It is mainly used on input[radio] and option elements, so that when the element is selected, the ngModel of that element (or its select parent element) is set to the bound value.
Eg:This is an executed code :
<div class="col-xs-12 select-checkbox" >
<label style="width: 18em;" ng-model="vm.settingsObj.MarketPeers">
<input name="radioClick" type="radio" ng-click="vm.setPeerGrp('market');"
ng-value="vm.settingsObj.MarketPeers"
style="position:absolute;margin-left: 9px;">
<div style="margin-left: 35px;color: #717171e8;border-bottom: 0.5px solid #e2e2e2;padding-bottom: 2%;">Hello World</div>
</label>
</div>
Note: In this above case I alreday had the JSON response to the ng-model and the value, I am just adding another property to the JS object as "MarketPeers". So the model and value may depend according to the need, but I think this process will help, to have both ng-model and value but not having them on the same element.
I had similar issue. I was not able to use value="something" to display and edit.
I had to use the below command inside my <input>along withe ng model being declared.
[(ngModel)]=userDataToPass.pinCode
Where I have the list of data in the object userDataToPass and the item that I need to display and edit is pinCode.
For the same , I referred to this YouTube video