Passing custom directive attribute to ng-model in template - javascript

I'm trying to pass in some attributes to my <custom-input> directive. Like so...
<custom-input type="text" name="first_name" title="First Name"></custom-input>
However, I'm getting a syntax error for the line where I pass the attribute to ng-model in the template.
I'm not sure what I'm doing wrong? Everything was working before I tried to move into a custom directive.
Directive
.directive('customInput', function() {
return {
restrict: 'E',
scope: {
type: '#type',
name: '#name',
title: '#title'
},
templateUrl: './assets/templates/custom-input.html',
controller: function() {
this.data = {}
this.focus = null;
},
controllerAs: 'input'
};
})
Template
<div class="Form__field">
<input
ng-model="input.data.{{name}}"
ng-class="{'Form__input--is-filled': input.data.{{name}}.length > 0}"
ng-focus="input.focus='{{name}}'"
ng-blur="input.focus=null"
class="Form__input"
type="{{type}}"
name="{{name}}"
placeholder="{{title}}"
/>
<label
ng-show="input.data.{{name}}.length > 0"
ng-class="{'Form__label--is-active': input.focus === '{{name}}'}"
class="Form__label"
for="{{name}}"
>{{title}}</label>
<div
class="Info Info--default"
ng-show="input.focus === '{{name}}'">
</div>
</div>
Error
Error: [$parse:syntax] Syntax Error: Token '{' is not a valid
identifier at column 12 of the expression [input.data.{{name}}]
starting at [{{name}}].

Before:
input.data.{{name}}
After:
input.data[name]

Your inner scope is getting type, name, and title attached directly to it. By defining the scope in the directive definition, you are declaring an isolate scope--one that no longer has access to the outer scope. You're also not passing in your input object.
What you have is the same as doing this inside the controller:
scope.name = 'first_name';
scope.title = 'First Name';
scope.type = 'text';
If you follow #bchemy's suggestion, you'll get a new property on your empty input.data object called first_name. And then the contents of the input would go into that. But there's no reason to expect that anything will come into it, because you didn't pass anything in that you're putting into that variable.

Related

AngularJs passing parameter values to controller

My Html:
<test-app email="hello#hotmail.com"></test-app>
My Directive:
.directive('testApp', function () {
return {
restrict: 'E',
scope: {
userEmail = '#userEmail'
},
templateUrl: 'Form.html'
};
})
My Form in another Page:
<label> Email </label>
<input type="text" id="email">
Issue: I need to get the parameter value , and print it out on the textbox itself.
What i have done: I research on a lot of articles which demonstrated on how to pass directive parameters to controller but they were very complicated.
What i think is the next step:
1) Pass the directive parameter to the controller and then bind the controller to the textbox and set the value of the textbox to be the parameter value.
2) Or is there another way i can do this?
I am stuck on how to proceed on.
You can use attribute string binding using #
In controller you initialize your userEmail variable
The directive has a user-email attribute which loads value into the directive's userEmail local scope variable.
Using userEmail:'#' allows for string binding from controller to the directive.
See demo below:
angular.module("app",[]).directive('application', function() {
return {
restrict: 'E',
scope: {
userEmail : '#'
},
templateUrl: 'Form.html'
};
}).controller("ctrl",function($scope){
$scope.userEmail="hello#hotmail.com";
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<application user-email="{{userEmail}}"></application>
<script type="text/ng-template" id="Form.html">
<label>Email</label>
<input type="text" id="email" ng-model="userEmail">
</script>
</div>

AngularJS template with brackets in it

Why AngularJS doesn't accept brackets inside a ng-template content? I need it to create an input that's going to be an array, but I get this error:
"Error: Syntax Error: Token ']' not a primary expression at column 15 of the expression [form.interval[]] starting at []]."
angular.module("main", []).controller("MyCtrl", function($scope) {
}).directive("ngPortlet", function ($compile) {
return {
template: '<input class="form-control" type="text" placeholder="Interval" ng-model="form.interval[]" />',
restrict: 'E',
link: function (scope, elm) {
scope.add = function(){
console.log(elm);
elm.after($compile('<ng-portlet></ng-portlet>')(scope));
}
}
};
});
<div ng-app="main">
<div ng-controller="MyCtrl">
<div id="container">
<button ng-click="add()" >Add</button>
<ng-portlet></ng-portlet>
</div>
</div>
</div>
jsfiddle:
http://jsfiddle.net/7kcrrapm/1/
EDIT:
Now that I better understand what you're trying to accomplish, here is a different approach:
angular.module("main", []).controller("MyCtrl", function($scope) {
}).directive("ngPortlet", function ($compile) {
return {
template: '<input class="form-control" type="text" placeholder="Interval" ng-model="interval" />',
restrict: 'E',
link: function (scope, elm) {
var intervals = [];
scope.add = function(){
intervals.push(parseInt(scope.interval, 10));
console.log(intervals);
}
}
};
});
Now you have access to an array (intervals) that contains a list of all intervals added.
ORIGINAL:
form.interval[] is not valid JavaScript and thus not a valid scope property. If you need the property to be an array you can simply declare it in your controller ("MyCtrl"):
$scope.form.interval = [];
If you don't create the scope property in the controller your self, it will be implicitly created by the ng-model directive. You can find more info in the docs. I might also suggest this great read about Scopes in the official Angular Wiki
From what I understand, what you really want is ng-repeat.
<span ng-repeat="hour in form.interval">
<input class="form-control" type="text" placeholder="Interval" ng-model="hour" />
</span>
Declare the variable inside the controller or directive:
$scope.form.interval = [];
When you do add() to get another input, add a blank entry to the array in the controller or directive:
$scope.form.interval.push('');
Call add() when you create the variable if you want to start with one empty input box.
The reason it's not working, is because [] is invalid JavaScript syntax on a variable reference.
interval = [1, 2, 3]; // Ok.
interval = []; // Also Ok.
var foo = interval[]; // This isn't valid!
Take those square brackets off, or if you're wanting to do a ng-repeat setup you might consider some of the other given answers.

Angular JS breaking two way binding on isolate scope when binding to a primitive and using ng-include to dynamically load a template

I have a bit of a strange scenario that is a little different to the other childscope and two way binding issues I have seen on Stackoverflow.
I have a field generation directive that receives a configuration object and some data and dynamically creates the correct type of field on screen and populates the data.
directive.js
.directive('myField', function () {
var stringTemplate = "scripts/directives/templates/my-string.tpl.html";
var textTemplate = "scripts/directives/templates/my-text.tpl.html";
var selectTemplate = "scripts/directives/templates/my-select.tpl.html";
var linker = function ($scope, elem, attrs) {
// Function to dynamically select the correct template
$scope.getTemplateUrl = function () {
var template = '';
if ($scope.options) {
if ($scope.options.optionList) {
template = selectTemplate;
} else {
switch ($scope.options.type) {
case 'String':
template = stringTemplate;
break;
case 'Text':
template = textTemplate;
break;
}
}
return template;
}
};
return {
restrict: 'E',
replace: true,
scope: {
options: '=',
data: '=',
fieldName: '#',
fieldWidth: '#',
labelWidth: '#',
},
link: linker,
template: '<ng-include src="getTemplateUrl()"/>'
}
});
I then have the corresponding template... I'm showing just the string template in this case.
my-string.tpl.html
<div class="form-group col-md-12">
<label for="{{fieldName}}" class="{{labelWidth}}">
{{options.label}}
</label>
<div class="{{fieldWidth}}">
<input type="text" class="form-control input-sm" id="{{fieldName}}" placeholder="{{options.watermark}}" ng-model="data" tooltip="{{options.tipText}}" ng-disabled="options.editable === false">
</div>
</div>
An example of how this might then be used would be
controller.js
$scope.person.firstName = "John";
$scope.person.lastName = "Doe";
$scope.options.person.firstName.type = "String";
index.html
<div class="row">
<my-field options="options.person.firstName" data="person.firstName" field-name="firstName" label-width="small" field-width="medium"></my-field>
The problem is the usual one, my-field directive has an isolated scope with a "data" property that is two-way bound to the controller. Because I am then using ng-include to dynamically load the correct template I am creating a further child scope that due to prototypical inheritance still populates correctly as it doesn't have its own data property so reaches to the parent. However when I modify the field, a shadow property is created on my child scope called data that doesn't propagate upwards the way that two way binding should.
I hope you are still with me
controller > my-field
ng-include causes the following scopes to exist
controller > my-field > ng-include
From reading around I understand that what I need to do to rectify this is pass an object rather than a primitive, however as there is effectively an intermediate layer between my controller and my final directive this is not straightforward.
I thought about changing the isolate scope in my-field to look like this
scope: {
....
data: {value: '=data'}
....
}
and then updating the template to refer to the object
my-string.tpl.html
<div class="form-group col-md-12">
<label for="{{fieldName}}" class="{{labelWidth}}">
{{options.label}}
</label>
<div class="{{fieldWidth}}">
<input type="text" class="form-control input-sm" id="{{fieldName}}" placeholder="{{options.watermark}}" **ng-model="data.value"** tooltip="{{options.tipText}}" ng-disabled="options.editable === false">
</div>
</div>
but this kills angular.
I have successfully got it to work by reaching back to the controller scope for binding by using
ng-model="$parent.$parent.data"
but I am not really happy with this as a solution as A it is ugly and B it involves knowing the depth of scope you are at which could vary.
Really stumped with this. Any help would be appreciated.

watch ng-model inside directive

I have the following directive:
directive('myInput', function() {
return {
restrict: 'AE',
scope: {
id: '#',
label: '#',
type: '#',
value: '='
},
templateUrl: 'directives/dc-input.html',
link: function(scope, element, attrs) {
scope.disabled = attrs.hasOwnProperty('disabled');
scope.required = attrs.hasOwnProperty('required');
scope.pattern = attrs.pattern || '.*';
}
};
});
with the following template:
<div class="form-group">
<label for="input-{{id}}" class="col-sm-2 control-label">{{label}}</label>
<div class="col-sm-10" ng-switch on="type">
<textarea ng-switch-when="textarea" ng-model="value" class="form-control" id="input-{{id}}" ng-disabled="disabled" ng-required="required"></textarea>
<input ng-switch-default type="{{type}}" ng-model="value" class="form-control" id="input-{{id}}" ng-disabled="disabled" ng-required="required" pattern="{{pattern}}"/>
</div>
</div>
It is used by this form:
<form ng-controller="UserDetailsCtrl" role="form" class="form-horizontal">
<div ng-show="saved" class="alert alert-success">
The user has been updated.
</div>
<my-input label="First name" value="user.firstName" id="firstName"></my-input>
<my-input label="Last name" value="user.lastName" id="lastName"></my-input>
<my-input label="Email" value="user.email" id="email" type="email" disabled></my-input>
<my-input label="Password" value="user.password" id="password" type="password"></my-input>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button ng-click="update()" class="btn btn-default">Save</button>
</div>
</div>
</form>
Which has this controller:
controller('UserDetailsCtrl', function($scope, $stateParams, User) {
$scope.user = User.get({userId: $stateParams.id});
/**
* Update the current user in this scope.
*/
$scope.update = function() {
console.log($scope.user);
$scope.user.$update({userId: $scope.user.id}).then(function(results) {
$scope.saved = true;
});
};
}).
The form is rendered fine, but when I click the Save button, the user values are never updated.
How can I use the updated values from within the myInput directive in the controller scope?
Here's the basic problem. Your ng-model is a primitive and is only being bound in one direction...it will update if parent object is changed, but since it is primitive it does not carry reference to parent object...just value. Thus updating the primitive does not update parent object that it's original value came from
Cardinal rule in angular...always have a dot in ng-model
Here's a solution that will pass the main user object to directive scope, as well as the property of that object to use for each input
<my-input id="firstName" model="user" field="firstName" label="First name"></my-input>
Now need to pass the object from controller into the directive scope:
app.directive('myInput', function() {
return {
scope: {
/* other props*/
field: '#',
model:'='/* now have reference to parent object in scope*/
},
......
};
});
Then in markup for an input will use [] notation in order to get our dot in:
<input ng-model="model[field]".../>
DEMO
In order to use angular validation you will likely have to require the ngModel controller in your directive or use nested form
Your problem is the ng-switch.
ng-switch like ng-repeat creates a new scope that inherits from the parent.
That means that if you have let's say:
$scope.foo = "hello";
And then you have something like:
<input type="text" ng-model="foo">
Inside a ng-switch. When you update foo it is going to create its own foo that hides/shadows the parent foo.
In other words, the input will show hello but when you modify it, a new foo is created hiding the parent one. That means that your parent one won't get updated (your problem).
That is not Angular.js issue, that is how Javascript works.
Normally you want to do a:
<input type="text" ng-model="foo.bar">
That way, you can play with the inheritance and instead of creating a new foo it will just update the bar on the parent.
Since that is not something you can do every time and maybe in your concrete use case you can't, the easy way is just to use $parent:
<input type="text" ng-model="$parent.value">
That way inside your ng-switch you will use directly the parent value.
I highly recommend you to read this ASAP: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Example: http://plnkr.co/edit/z4D6Gk5fK7qdoh1mndzo?p=preview
Cheers.

Problems with angular validation in directive

I am fighting with the validation in an angular directive without success.
The form.name.$error object seems to be undefined, when I submit the name property to the directive template. If i use a fixed name-attribute inside the template, the $error object is fine, but of course identical for all elements.
The html is:
<form name="form" novalidate>
<p>
<testvalidation2 name="field1" form="form" field="testfield4" required="true">
</testvalidation2>
</p>
</form>
The directive looks like this:
app.directive('testvalidation2', function(){
return {
restrict: 'E',
scope: {
ngModel: '=',
newfield: '=field',
required: '=required',
form: '='
},
templateUrl: 'template2.html',
link: function(scope, element, attr){
scope.pattern = /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/;
scope.name = attr.name;
}
} // return
});`
and finally the template:
<div>
<input name="{{name}}" type="text" ng-model="newfield" ng-required="required" ng-pattern="pattern"> {{FIELD}}</input>
<span ng-show="form.name.$error.required">Required</span>
<span ng-show="form.name.$error.pattern"> Invalid </span>
<p>Output {{form.name.$error | json}}</p>
</div>
I have created a plunker for my Angular Validation Problem
and would be happy, if someone would help me to win the fight.
Michael
I don't have a fix for this but I can tell you what the problem is.
Firstly in your html form="form" should have name of the form form="form2".
Secondly Since you are creating a new scope in the directive, the scope created is a isolated scope which does not inherit from parent, which means that the the template input control that you add would not get added to the parent scope form2.
The only way out currently i can think of is to not use isolated scope.

Categories

Resources