How to access AngularJS directive Form fields into parent controller - angularjs

I created following form as a angular directive and try to access the firstname from parent controller.
Please help me or guide me how to access directive form fields from inside MainCtrl.
HTML
<pre>
{{filter-frm| json}}
</pre>
<div ng-controller="MainCtrl">
<userform></userform>
</div>
JS
.controller('MainCtrl', ['$scope', function($scope){
console.log($scope.filterForm.firstname) //How to get this ?
};
.directive('userform', [function () {
return {
restrict: 'E',
scope: { formCtrl: '=' }
template: '<div>'
+ '<form id="filterForm" ng-submit="login()">'
+ '<input name="firstname" ng-model="user.firstName">'
+ '</form> '
+ '</div>',
link: function (scope, element, iAttrs) {
var form = element.find('form');
scope.formCtrl = form.controller('form');
}
};
}]);

If you add a name attribute to your form in your directive template, you will be able to access it via scope.formName. It should also bind the input fields values via scope.formName.fooInput.
In your case, adding name="filterForm" should do the trick:
template: '<div>'
+ '<form id="filterForm" name="filterForm" ng-submit="login()">'
+ '<input name="firstname" ng-model="user.firstName">'
+ '</form> '
+ '</div>',
Now you should be able to access scope.filterForm in the parent controller.
Param: name (optional); Type: string; Details: Name of the form. If specified, the form controller will be published into related scope, under this name.
from ngForm doc

You have to bind the values of the field to variables of your controller's model.
To do that, you have to expose the form fields as attributes of your directive.
See https://docs.angularjs.org/#!/guide/directive and how to use the scope field.

Related

Dynamic add attributes on component in Angular pre compile

I have component that is used multiple times, 58 times to be exakt. The only thing that differs between them is unique attributes to add validations. What I want to do is to add a array of attributes to my template before is compiled. Is this possible to achieve when working with a Angular component?
component.js
(function () {
'use strict';
angular
.module('upaApp')
.component('component', {
bindings: {
inputAttributes: '#',
},
controller: controller,
restrict: 'E',
templateUrl: 'app/component/component.html'
});
function controller() {
var $ctrl = this;
}
})();
component.html
<input {{ $ctrl.inputAttributes }}
class="form-control"
type="text" />
When I use the component <component input-attributes="directive1, directive2"></component> it doesn't render out my string and even if it did I would not be sure that it would work. So is there a way to dynamically be able to set the attributes in AngularJS?
Is this angular 1 or 2?
Ill assume the former.
I dont know of a way to place a string as an attribute. What you could do as a workaround is conditionally insert attributes with the ng-attr- attribute. This will insert the attribute if the variable is not undefined.
maybe something like this:
$scope.ctrl.inputAttributes = {
directive1:undefined, //this one wont show
directive2:"this one will show"// as directive2="this one will show"
}
then in your markup:
<input ng-attr-directive1="ctrl.inputAttributes.directive1"
ng-attr-directive2="ctrl.inputAttributes.directive2"
class="form-control"
type="text" />
https://www.thinkingmedia.ca/2015/03/conditionally-add-a-html-element-attribute-value-in-angularjs/
EDIT: it may not be clean, but you could create a directive that compiles html.
app.directive('dynamicAttributes', function ($compile) {
return {
restrict: 'E',
scope: {
attributes: '#'
},
link: function (scope, elem, attrs) {
var h = '<input '+scope.attributes + ' class="form-control" type="text" />';
elem.replaceWith($compile(h)(scope));
}
}
});
then in your DOM
<dynamic-attributes attributes="1 2 3"></dynamic-attributes>
fiddle: http://jsfiddle.net/brhardwick/nx16zdrL/1/
There was actually a very simple solution on my problem. You could use ng-model to send the value to the component. And when I placed my directives on the component it validates accordingly since it can access the value from ng-model.

Angular directive injecting DOM element with click event

Content edited
The goal is to create a directive that can be attached to a textbox that, when the textbox has focus, an image/button will appear after the textbox and the image/button click event will fire a function contained within the directive. The goal is for this functionality to be entirely self-contained in the directive so it can be easily deployed in many pages or apps.
The image/button appears after the textbox with no problem but the click event of the button does not fire the function. I have created a plunkr with the example code.
In the plunk, line 15 defines a function called 'search,' which does nothing more than fire an alert. When the textbox has focus, the button appears as expected and line 34 calls the search function successfully, which means the function itself is working. However, the button's click event doesn't fire the search function.
Original post
I'm trying to recreate some functionality in our apps that is currently being accomplished with jQuery. The functionality involves attaching a pseudo-class to a textbox which is then picked up by jQuery and an image of a magnifying glass is injected into the DOM immediately after the textbox. Clicking on the image causes a dialog box to pop open.
What I've accomplished so far is a simple html page, a simple controller, and a simple directive. When the textbox has focus, the image appears as expected. However, the ng-click directive does not fire.
Here's the html:
<input
id="txtAlias"
type="text"
ng-model="pc.results"
user-search />
</div>
Here is the controller:
angular
.module('app')
.controller('PeopleController', PeopleController);
PeopleController.$inject = ['$http'];
function PeopleController() {
var pc = this;
pc.results = '';
pc.search = function () {
alert('test');
};
}
And this is the directive:
angular
.module('app')
.directive('userSearch', userSearch);
function userSearch($compile) {
return {
restrict: 'EAC',
require: 'ngModel',
//transclude: true,
scope: {
//search : function(callerid){
// alert(callerid);
//}
},
template: "The user's alias is: <b><span ng-bind='pc.results'></span>.",
//controller: UserSearchController,
link: function (scope, element, attrs) {
element.bind('focus', function () {
//alert(attrs.id + ' || ' + attrs.userSearch);
var nextElement = element.parent().find('.openuserdialog').length;
if (nextElement == 0) {
var magnifyingglass = $compile('<img src="' + homePath + 'Images/zoomHS.png" ' +
'alt="User Search" ' +
'ng-click="pc.search("' + attrs.id + '")" ' +
'class="openuserdialog">')(scope);
element.after(magnifyingglass);
}
});
}
};
};
For the time being, I'd be happy to get an alert to fire by either hitting pc.search in the controller or by search in the isolated scope. So far, neither has worked. I'm sure it's something simple that's missing but I can't figure out what.
Solution
Thanks to a user over at the Google forum for showing me the controllerAs property for directives. This version now works perfectly:
angular
.module('app')
.directive('userSearch', userSearch);
function userSearch($compile){
return {
controller: function ()
{
this.search = function () {
alert('Test');
};
},
link: function (scope, element, attrs) {
element.bind('focus', function () {
var nextElement = element.parent().find('.openuserdialog').length;
if (nextElement === 0) {
var btn = '<img src="' + homePath + 'Images/zoomHS.png" ' +
'ng-click="userSearch.search()" ' +
'class="openuserdialog" />';
element.after($compile(btn)(scope));
}
});
},
controllerAs: 'userSearch'
};
};
You are using isolated scope in your directive which means it don't have access to its parent scope. So in this case you need to pass your method reference explicitly to directive. Passed method reference to your directive scope by new variable inside a isolated scope of directive.
Markup
<input id="txtAlias"
type="text" ng-model="pc.results"
user-search search="search(id)" />
scope: {
search: '&'
}
As you don't have access to parent scope, you can't use controller alias over there like you are using pc.. Simply do use following without alias. So directive will bind those variables from directive scope directly.
template: "The user's alias is: <b><span ng-bind='results'></span>.",
Also change compile template to
if (nextElement == 0) {
var magnifyingglass = $compile('<img src="' + homePath + 'Images/zoomHS.png" ' +
'alt="User Search" ' +
'ng-click="search({id: ' + attrs.id + '})' +
'class="openuserdialog">')(scope);
element.after(magnifyingglass);
}
Rather I'd love to have the compiled template as part of template of directive function only. And I'll show and hide it based on ng-if="expression" directive.
Relative answer
Rather than trying to inject into the DOM, and then trying to hook up to that thing you just injected, wrap both the input and the search button/icon in a directive. You can use an isolated scope and two-way binding to hook up both the input and the button:
HTML:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<search-box data="name" search-function="search"></search-box>
</body>
Here's both a controller and a directive that demonstrate this. Note the "=" in the isolated scope, creating a two-way binding to the corresponding attributes when the directive is used in a template.
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.search= function() { alert('search clicked'); }
});
app.directive('searchBox', function() {
return {
restrict: 'E',
scope: {
searchFunction: '=',
data: '=',
},
template: '<input ng-model="data" /><button ng-click="searchFunction()">Search</button>'
}
})
You should be able to easily replace the button element with an img or whatever else your heart desires.
Here's a plunk with an alert() for the search box, and where typing in the text box in the directive affects the corresponding property of the controller scope:
http://plnkr.co/edit/0lj4AmjOgwNZ2DJMSHDj

AngularJS nested directive $pristine and $dirty settings

I'm having issues with a directive I am writing.
Within the directive's template there is also another element directive.
Essentially the outer directive is a decorator for the inner, adding more functionality..
The issue that I am having is that the $pristine and $dirty values are not being set as I would have expected.
I have amended the fiddle below to demonstrate a similar scenario..
(Code follows:)
HTML
<body ng-app="demo" ng-controller="DemoController">
<h3>rn-stepper demo (3/5)</h3>
Model value : {{ rating }}<br>
<hr>
<div ng-model="rating" rn-stepper></div>
</body>
JS
angular.module('demo', [])
.controller('DemoController', function($scope) {
$scope.rating = 42;
})
.directive('test', function() {
return {
restrict: 'E',
scope: {
ngModel: '=ngModel'
},
template: '<input type="text" ng-model="ngModel"></input>'
};
})
.directive('rnStepper', function() {
return {
restrict: 'AE',
scope: {
value: '=ngModel'
},
template: '<button ng-click="decrement()">-</button>' +
'<div>{{ value }}</div>' +
'<button ng-click="increment()">+</button>' +
'<test ng-model="value"></test>',
link: function(scope, iElement, iAttrs) {
scope.increment = function() {
scope.value++;
}
scope.decrement = function() {
scope.value--;
}
}
};
});
http://jsfiddle.net/qqqspj7o/
The model is shared as expected and when I change the value in either the text input or using the slider, the binding works - however if I update the value in the text input, only the text input is marked as ng-dirty - the element directive itself remains as ng-pristine as does the outer div.
I don't understand why this is and the values are not propagated to the element? Is that expected behaviour - if so, how do I propagate the ng-dirty etc values to the element directive and the outer div..
Note: I can only use Angular v 1.2.x as the code needs to be compatible with IE8.
Thanks in advance..
Generally in directives you should avoid =value binding, and work directly with ngModelController.
This topic is a bit complicated for discussion here, but there are many great tutorias on the web I point you to this one:
using ngModelController it explains basics of working with ngModel and also tells bit about decorators.
When you work directly with ngModel you can set validity and state (dirty/touched/pristine) directly in your code, you can also set model value via $setViewValue().

validation of a form added with a directive

I would like to access the content of the validation variables provided from AngularJS for the forms.
I need to add forms using a directive like in the code, but if I do that I can't access the $dirty,$error,$invalid variables anymore and I need to access them.
JS:
myApp.directive('test', function() {
return {
scope : {
nameform: "=",
nameinput: "=",
type: "="
},
template: '<form name=nameform></form>',
restrict: 'E',
compile: function (elem,attr){
var form = elem.find("form");
if(attr.type == "text")
form.html('<input type="text" name="nameinput" ng-model="data.text" placeholder="type here..." required />');
}
};
});
HTML:
<test nameform="valform" nameinput="valinput" type="text"/>
{{valform.valinput.$invalid}}
I think that you can't. Because you are using isolated scope for building your directive, so you don't have acces to the information. You can try to build you directive using share scope, and I think that you would be able to access this information.
Change your directive to be ng-form instead of form (because ng-form is nestable). Then wrap your directive inside another form element and give that new form element a name. The outer form element will bind to your outer scope and you can access the properties that way.
Directive template:
"<ng-form name="valform"></ng-form>"
HTML:
<body ng-view>
<div>Valid: {{outerform.$valid}}</div>
<div>Errors: {{outerform.$error}}</div>
<form id="outerform" name="outerform">
<test type="text"/>
</form>
</body>
Side note, form names don't play nice with dynamic names. The plunkr below uses static names so I could help you with your current problem. I found a solution to the dynamic name problem on a different SO post...I think this one...
Dynamic validation and name in a form with AngularJS
Here's a plnkr with your working form...
http://plnkr.co/edit/RFrRXp2kWkP1Mefwl3Kn?p=preview
When you build your HTML for your input controls, make sure you append the name attribute correctly based on 'nameinput' passed in as an attribute:
myApp.directive('test', function() {
return {
scope : {
nameform: "=",
nameinput: "=",
type: "="
},
template: '<form name=nameform></form>',
restrict: 'E',
compile: function (elem,attr){
var form = elem.find("form");
if(attr.type == "text")
form.html('<input type="text" name="' + attr.nameinput +'" ng-model="data.text" placeholder="type here..." required />');
}
};
});

Binding data in a custom directive - AngularJS

I have a custom directive, and its purpose is to present a widget and bind it to a variable.
Every variable has different data type, so different widgets will be presented depending on the data type.
My problem is that I can pass the data of the variable, but I can't manage to bind the widget to it.
To simplify the problem, my widget is just a simple text input.
When I try to $compile the widget, Angular uses the value of the variable instead of binding to it.
HTML:
<body ng-app="app" ng-controller="myCtrl">
<input type="text" ng-model="resource.name"></div>
<div custom-widget widget-type="widget" bind-to="resource"></div>
</body>
Javascript:
angular.module('app', [])
.directive('customWidget', function($compile) {
return {
replace: true,
template: '<div></div>',
controller: function($scope) {
},
scope: {
bindTo: "=bindTo",
widgetType: "=widgetType"
},
link: function(scope, iElem, iAttrs) {
var html = '<div>' + scope.widgetType.label + ':<input ng-bind="' + scope.bindTo[scope.widgetType.id] + '" /></div>';
iElem.replaceWith($compile(html)(scope));
}
};
})
.controller('myCtrl', function($scope) {
$scope.widget = {
id: 'name',
label: 'Text input',
type: 'text'
};
$scope.resource = {
name: 'John'
};
});
Plunker demo: http://plnkr.co/edit/qhUdNhjSN7NlP4xRVcEA?p=preview
I'm still new to AngularJS and my approach may not be the best, so any different ideas are of course appreciated!
Since you're using an isolate scope one issue is that resource is on the parents scope and not visible within the directive. And I think you're looking for ng-model rather than ng-bind.
Also, since you want to bind to namein resource, we need to tie that in somehow.
So here's one approach to your template html (note the addition of $parent to get around the scope issue and the addition of .name(which you could add programatically using a variable if you preferred, or specify it as part of the attribute))
var html = '<div>' + scope.widgetType.label + ':<input ng-model="' + '$parent.' + iAttrs.bindTo +'.name'+ '" /></div>';
Updated plunker
Well, when you have a isolated scope within your directive and use the "=" operator you already have two-way data binding.
My suggestion would be to use the "template" more like a view so the operations are clearer.
I would change your directive to the following:
Using ng-model instead of ng-bing mainly because as the Documentation reveals:
The ngModel directive binds an input,select, textarea (or custom form control) to a property on the scope using NgModelController, which is created and exposed by this directive. [...]
Changed directive:
angular.module('app', [])
.directive('customWidget', function($compile) {
return {
replace: true,
template: '<div> {{widgetType.label}} <input ng-model="bindTo[widgetType.id]" /></div>',
scope: {
bindTo: "=bindTo",
widgetType: "=widgetType"
}
};
});
EDIT:
Ops forgot the Updated Plunker

Resources