Access $error in Angular directive - javascript

I have an input wrapped in a directive with a dynamic name like this:
<input class="input-field" name="{{name}}" type="number" ... />
Now I want to access the $error variable of the form IN the directive.
Something like form.{{name}}.$error.number.
Is there a way to do this?

If you want to access the form (that is on the parent scope) you have to pass the form to your directive. To do this, you have to specify that you want two way binding (using =) when you define your directive.
Have a look at https://docs.angularjs.org/guide/directive, more specifically the part about isolating the scope can probably help you.

Maybe it's enough to get the $error of the specific ngModelController?
return {
template: '<input ng-model="value" type="number" /><span ng-bind="error | json"></span>',
scope : {},
link: function(scope, elem, attr) {
scope.error = elem.find('input').controller('ngModel').$error;
}
}
http://plnkr.co/edit/wzuWT1lVevCLHkLLBIAT?p=preview

Related

AngularJS Directive Isolated scope inside nested ng-transclude

I've been looking all over the internet for something like this and I still can't find the answer.
I have a directive that is reused throughout my application, it allows the user to sort and search through lists of items. I have multiple kinds of items that can be passed in to the directive (html templates that I pass in) as well as multiple uses for those templates. I.e, sometimes I want a certain button on that template, but sometimes I want another. (This will make more sense in a minute).
Therefore I have created multiple directives with transclusion in order to achieve this. However, I'm having serious issues with scoping and I can't seem to figure out exactly how to pass the isolated scope to the child directive.
Below is my code:
Item List Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function (scope, element, attributes, ctrl, transclude) {
//Things that modify the scope here. This scope is what I want to pass down to the child directives
//NOTE: I do not currently have a working transclude function which is why I didn't include it here because I have no idea what to do with it
scope.pagedItems = groupItemsToPages(items);
scope.currentPage = 0;
};
return {
restrict: 'E',
replace: 'true',
transclude: true,
templateUrl: 'partials/directives/item-list.html',
link: linkFunction,
scope: {
searchPlaceholder: "#",
items: "=",
control: "="
}
};
});
item-list.html
<div class="form-group">
<!-- I won't put all of the html here, just some to show you what i'm going for -->
<div class="search-field">
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}/>
</div>
<table class="table table-hover">
<tbody>
<tr ng-repeat="item in pagedItems[currentPage]">
<td ng-transclude></td>
</tr>
</tbody>
</table>
</div>
Here's the directive that simply returns the URL of whatever template is passed to it. This is so that I can add in an extra html through further nested transclusions.
item-template.js
var app = angular.module('main');
app.directive('itemTemplate', function() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: function(tElement, tAttrs){
return tAttrs.templateUrl;
}
};
});
Here's an example template (extremely simplified again, just to show you the layout)
profile-template.html
<div>
<p>item.name</p>
<p>item.description</p>
</div>
<div ng-transclude></div>
Here's an example of the HTML that calls this code
tab.html
<div class="tab">
<div class="available-items">
<item-list control="profileControl" search-placeholder="Search Profiles" items="profileControl.profiles">
<item-template template-url="partials/profile-template.html">
<button type="button" ng-click="launchProfile(item.id)">Launch Profile</button>
</item-template>
</item-list>
</div>
</div>
So now that you've seen the code. The issue I'm having is that my profile-template.html inclusion isn't getting the scope from the directive above it even though I've tried cloning the scope to it. All the examples I've seen require you to remove the template key from the directive and assume you're only returning the code you have in your transclusion. In my case, I have other html that I want to display in the template.
Any help would be appreciated
Instead of trying to pass the scope between your directives, you can make use of the $parent attribute and get access to higher scopes.
For instance, in your item-template controller you could gain access to the higher scope from item-list with a code like this:
if ($parent.searchPlaceholder === '') {
...
}

Dynamically add, compile and link ng-class attribute from another directive

In order give feedback on the validity in my input forms I'm using ng-class. These statements will look something like:
<div ng-class="{ 'has-error': !frmSomeName.vcHeader.$valid && frmSomeName.vcHeader.$dirty }">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
I dislike the lengthiness of the statement, and would like to replace it with something alike:
<div validation-state="frmSomeName.vcHeader">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
In order to avoid having to duplicate ngClass' behavior I'd like the the directive to add the ng-class directive.
This plnkr demonstrates my attempt at adding the attribute, and although it works in the simplest scenario, it is faulty and will not function with transclusion (or other more complex directives).
I know it doesn't work because of the misuse of the compile and link stages, however I'm not sure on how to actually make it work properly. Therefore my question: How do I add different directive-attribute from a directive-attribute?
The elements within the transclude directive aren't receiving the same scope. If you use the angualr $compile method and apply the scope of the transclude directive to the scope of the child directives it should work. The following should be added to your simpleTransclude directive:
link: function(scope, element) {
$compile( element.contents() )( scope )
}
remember to pass $compile into the directive.
I've forked your plnkr and applied the simpleTransclude scope to it's contents.

Add angularjs directive to a native html element (input text, div, span etc)?

I have something like this -
<input type="text" value="" title="Title"> ...
How can I add my custom directive as attribute to the existing html element on the page, let's say - directive as categoryLookup -
<input type="text" value="" title="Title" category-lookup> ...
I need to do this dynamically on page load, i.e make the input text behave per directive logic.
Thanks in advance.
You can do this with leveraging $compile after targeting your element. I added an id and used vanilla JS in this example, but you may have more at your disposal e.g. jQuery. After you have your element, just $compile it in your associated controller. Observe the following example...
<div ng-app="app" ng-controller="ctrl">
<input id="myInput" type="text" title="Title">
</div>
angular.module('app', [])
.controller('ctrl', function($scope, $compile) {
$compile(angular.element(document.getElementById('myInput')).attr('category-lookup', 'category-lookup'))($scope)
})
.directive('categoryLookup', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
console.log('directive registered')
}
}
});
JSFiddle Link - demo
Also, "dynamically on page load" can mean a few things. I assumed you are in a fully fleshed out AngularJS ecosystem, but there does exist the notion that you do not even have a controller and need to somehow cherrypick this directive and compile it on "load". Here is a nitty gritty example how you can do so, though, it's generally regarded as a bad practice, and instead leveraging the above logic in an associated controller is preferable. Observe the following...
<div ng-app="app">
<input id="myInput" type="text" value="" title="Title">
</div>
angular.element(document).ready(function () {
var $injector = angular.injector(['ng', 'app']);
$injector.invoke(function($rootScope, $compile) {
$compile(angular.element(document.getElementById('myInput')).attr('category-lookup', 'category-lookup'))($rootScope)
});
});
JSFiddle Link - demo - no controller

Polymer-AngularJS two-way data binding

I have some custom element created with Polymer. Let's call it x-input, and it looks like this:
<polymer-element name="x-input" attributes="name">
<template>
<input type="text" value={{name}}> <span>{{name}}</span>
<br />
<input type="range" value={{age}} > <span>{{age}}</span>
</template>
</polymer-element>
And I have this html I use Angular:
<html ng-app="testApp">
<body ng-controller="AppCtrl">
<input id="outer_input" type="text" ng-model="kids[0].name" value={{kids[0].name}} /> <br />
<span>name: {{kids[0].name}} age: {{kids[0].age}}</span><br />
<x-input ng-repeat="kid in kids" name={{kid.name}} age={{kid.age}}>
</x-input>
</body>
</html>
Here is the JS:
var testApp = angular.module('testApp', []);
testApp.controller('AppCtrl', function ($scope, $http)
{
$scope.kids = [{"name": "Din", "age": 11}, {"name": "Dina", "age": 17}];
}
The problem is with the two-ways data binding. When I change the #outer_input input value the x-input inner values (name and age) are changed.
But when I change the custom element input only inner binded variable are changed.
How can I change value of binded variable within the polymer element and it will change the model and all outer bound UI and data (two-way binding)?
Thanks
If you tell it to, Polymer will reflect model changes back out to the published property (its attribute), but issue is that Angular doesn't observer bindings to attributes.
There's a patch that makes this work like you want: https://github.com/eee-c/angular-bind-polymer
More info here: http://blog.sethladd.com/2014/02/angular-and-polymer-data-binding.html
I started the ng-polymer-elements project which lets you have two-way binding between web components and Angular in an Angular-like way:
<input ng-model="model"/>
<paper-input ng-model="model"></paper-elements>
It comes with support for Polymer core and paper elements, and can be configured for any web component.
I belive this is what youre looking for simple and transparent 2 way data binding and capability to expand to more custom elements and for javascript not dart
NG Polymer Elements
This is my working solution, ng-polymer-elements doesn't work for me ($dirty, $pristine, etc. not working). This is very straighforward IMO
angular.module 'tinizen.admin.ui'
.directive 'paperInput', ->
restrict: 'E'
require: 'ngModel'
link: (scope, elem, attrs, ctrl)->
watcher = ->
if ctrl.$dirty then ctrl.$invalid else false
scope.$watch watcher, (invalid)->
elem[0].invalid = invalid
updateModel = (inputValue)-> ctrl.$setViewValue inputValue
## attrs.$observe 'inputValue', updateModel not working
## so I have to use on 'input'
elem.on 'input', ->
scope.$apply ->
updateModel elem.prop('inputValue')
updateModel()
ctrl.$render = ->
elem.prop 'inputValue', ctrl.$viewValue
according to their documentation, when binding to native elements, you have to add an extra binding notation
https://www.polymer-project.org/1.0/docs/devguide/data-binding.html#two-way-native
Here {{name}} will update on input events, the {{age}} only on the change event
<polymer-element name="x-input" attributes="name">
<template>
<input type="text" value={{name::input}}> <span>{{name}}</span>
<br />
<input type="range" value={{age::change}} > <span>{{age}}</span>
</template>
</polymer-element>

Two way binding of form input directive with isolated scope

I am leaning AngularJS, specifically working on learning directives. Would like to have a form input directive that I can re-use on all my forms to encapsulate all the boiler plate markup. Though I am having trouble getting two way binding to work in my directive. It is using an isolated scope with its own internal property to store the value of the input field. I've setup a watch on that internal property that is correctly pushing the value from within the isolate scope up to the controllers scope. What I am trying to figure out is how to take an initial value from the controllers scope and set it as the initial value in my directive.
Plunker Link: http://embed.plnkr.co/TbVB0q9DHhBCVLQ4U64W/script.js
Typing in the first input box changes the controller scopes property, but not the directives value. Typing in the second input box changes the directive and the controller property.
I know this is possible using an attribute to pass the initial value. However I am hoping to be able to extract the value from the controller scopes property via the ngModel reference in my directive.
EDIT AFTER ANSWER:
For those not sure about why you want to go through the effort of learning directives. Especially when Angular is so powerful even without using directive. This is one good reason why.
Input fields on my form without directive:
<div class="form-group">
<label for="firstName" class="col-sm-6 col-md-4 control-label">First Name</label>
<div class="col-sm-6 col-md-8" ng-class="{'has-error': userForm.firstName.$invalid}">
<input type="text" id="firstName" name="firstName" placeholder="First Name" ng-model="muState.currentUser.firstName" class="form-control" required
popover="Cannot be blank" popover-trigger="{{{true: 'mouseenter', false: 'never'}[userForm.firstName.$invalid]}}" />
</div>
</div>
After using my directive:
<ws-form-input input-name="firstName" input-label="First Name" input-placeholder="First Name"
ng-model="muState.currentUser.firstName"
required input-error="Cannot be blank"></ws-form-input>
Go through the effort. You'll be trading the headaches of learning versus a maintenance nightmare later.
I think you can simplify your directive altogether by using the isolated scope's '=' attribute notation.
Something like this:
JAVASCRIPT
app.directive('inputDirective',function(){
return {
restrict: 'E',
replace: true,
scope: {ngModel: '='},
templateUrl: "directiveTemplate.html",
require: '^form',
link: function(scope, elem, attrs, ctrl){
scope.form = ctrl;
scope.required = false;
// If attribute required exists
if (attrs.required !== undefined) {
// ng-required takes a boolean which is read from this scope variable
scope.required = true;
}
}
};
});
HTML DIRECTIVE
<div>
<input type="text" id="directiveInput"
ng-model="ngModel" class="form-control" ng-required="required"/>
<br/>
Isolated Scope value of the input box: {{ngModel}}
</div>
See UPDATED PLUNKER
You have to implement ngModel.$render, see ref here.
The following code in your directive will suffice:
scope.ngModel.$render = function() {
scope.inputBinding = scope.ngModel.$viewValue;
};

Categories