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>
Related
I am binding an ng-model to an input, but the value of the variable it's bound to is not being updated outside of the div where that directive is declared:
<div input-field
ng-if="startTypes.selected.value == 'LocalDate' || startTypes.selected.value == 'LocalDateTime'">
<input id="date" type="text" ng-model="date" input-date>
<label for="date">Date</label>
Date inner scope: {{date}}
</div>
Date outer scope: {{date}}
When selecting a new date, ony the inner date is updated. The outer one remains with the old value (which might be either undefined or not depending if I declared it in the controller, it doesn't matter).
I am using angular-materialize, I am not sure if this is the source of the issue but it doesn't make sense because it is a specific framework for angular to work with the CSS framework materializecss.
This is the component I am using.
Edit:
I have tried declaring date in the controller as $scope.date = new Date() and indeed the current date is loaded in the date picker. However when a date is selected and the model changes, it's only updated locally (inner scope), while in the outer scope the old value remains.
As ng-if creates a child scope which is Prototypically inherited from its current scope while inserting inner template to DOM, hence in this case ng-model getting created inside ng-if's child scope. So what happening is while creating a child scope it carries the primitive datatype values & reference(object) datatypes values to child scope, thats why you can see the outer scope date is getting value inside ng-if date field(only first time). But when you update the value in date you will not see the value gets updated to outer scope. Because the way child scope has create primitive type value not carry their references, where as objects are carried with their references. So you can create a object like $scope.model = {} & then define a property into it, that will work. Because object are carried with their references to child scope, updating inner object would sync the outer object as well(they both are same). This rule is called as Dot Rule by which you can fix your issue.
$scope.model = {};
$scope.model.date = new Date();
More convenient way to avoid such kind of scope hierarchy is using controllerAs pattern while using controller on HTML. In this case you shouldn't be using $scope instead you will bind all the properties to controller function context (this). Thereafter when using controller you can use alias of controller to get the values of controller like ng-controller="myCtrl as vm"(here vm is alias of controller which has all information binding to this)
HTML
<div input-field
ng-if="vm.startTypes.selected.value == 'LocalDate' || vm.startTypes.selected.value == 'LocalDateTime'">
<input id="date" type="text" ng-model="vm.date" input-date>
<label for="date">Date</label>
Date inner scope: {{vm.date}}
</div>
Date outer scope: {{vm.date}}
You should use an object with a property when binding to ngModel.
$scope.form = {
date: new Date()
};
ngIf like ngRepeat directive creates its own $scope.
So you could use ng-show instead of ng-if in this specific case.
From docs:
The ngIf directive removes or recreates a portion of the DOM tree
based on an {expression}. If the expression assigned to ngIf evaluates
to a false value then the element is removed from the DOM, otherwise a
clone of the element is reinserted into the DOM.
Example:
angular.module('app', [])
.controller('mainCtrl', function($scope) {
$scope.testa = false;
$scope.testb = false;
$scope.testc = false;
$scope.testd = false;
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
Test A: {{testa}}
<br /> Test B: {{testb}}
<br /> Test C: {{testc}}
<br /> Test D: {{testd}}
<br />
<div>
testa (without ng-if):
<input type="checkbox" ng-model="testa" />
</div>
<div ng-if="!testa">
testb (with ng-if):
<!-- if you don't use $parent testb isn't updated -->
<input type="checkbox" ng-model="$parent.testb" />
</div>
<div ng-show="!testa">
testc (with ng-show):
<input type="checkbox" ng-model="testc" />
</div>
<div ng-hide="testa">
testd (with ng-hide):
<input type="checkbox" ng-model="testd" />
</div>
</body>
</html>
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.
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
I'm trying to build a WebComponent where you can edit items in an array, with the Polymer javascript framework. Model to DOM bindings work OK, but DOM to Model doesn't - simplified example:
<polymer-element name="rep-test">
<template>
<template repeat="{{item in items}}">
<input type="text" value="{{item}}" placeholder="changes don't work!">
</template>
<button on-click="{{add}}">Add</button>
{{items}}
</template><script>
Polymer({
ready: function() { this.items = [] },
add: function() { this.items.push('') },
itemsChanged: function() { console.log(this.items) } // debug
})
</script>
</polymer-element>
<rep-test></rep-test>
The items are correctly displayed in the input elements, but when I change the value inside an input, changes are not reflected to the model (items). The binding works only in one direction.
Is there any way to make the binding bidirectional, so that when a change occur in the DOM, it is copied in the model ?
I've seen this todo demo which achieves this effect, but it does so with custom events associated with items changes. This obviously works but I'm looking for a more declarative way of doing this with bindings.
Since changes in array’s elements are not reflected to itemsChanged, I would suggest you to listen on the input changes:
<!-- ⇓⇓⇓⇓⇓⇓⇓⇓⇓ -->
<input type="text" on-change="{{ itemChanged }}"
value="{{item}}" placeholder="changes don't work!">
[...]
<!-- inside script -->
itemChanged: function(e) {
console.log(e.path[0].value)
}
Below is the link to the working example: http://plnkr.co/edit/sZYHeMuAVB0G1muHhFNK?p=preview
Here is an example of bidirectional binding: as you change the values in the input fields model is updated:
Plunk
Follow data changes:
<br>
{{testData.employees[0].firstName}}
<br>
{{testData.employees[3].firstName}}
<br><br>
<template repeat="{{person in testData.employees}}">
{{person.firstName}}
<input type="text" value="{{person.firstName}}">
<br>
</template>
I'll reference this post because it explains how this works better then I can:
"...if you change the data values, the new values are NOT available to all other instances - because the instance variables are just copies of the referenced strings. By using an object with data properties, as in the edited version above, and only ever reading from and assigning to the data properties of that object rather than overwriting the object itself, changed values are shareable between instances."
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;
};