I have a directive that receives a data object and a function to it's isolate scope. In link function I'm declaring a scope method to trigger in a certain event (button click).
Problem is the value passed to the above mentioned method is available inside it, but the scope variable is still undefined.
Directive:
commentsModule.directive('commentsDirective', [ function() {
return {
restrict: 'E',
templateUrl: '/alarm-viewer-comments-template.html',
scope: {
alarmComments: "=value",
sendNewComment: "&sendNewComment"
},
link: function(scope, elems, attrs, ngModelCtrl) {
scope.sendComment = function(data) {
console.log(scope.newComment);//this newComment variable is undefined
scope.sendNewComment(data);//data is correct
scope.newComment = '';
};
}
}
}
]);
Here inside link function, data passed into scope.sendComment is available but yet scope.newComment gets undefined.
Template:
<h4>Comments</h4>
<div ng-repeat="comment in alarmComments.comments">
<p>{{comment.timestamp}} | <strong>{{comment.user}}</strong>: {{comment.commentType}} {{comment.comment}}</p>
</div>
<div ng-if="alarmComments.editPermission && alarmComments.isActiveAlarm">
<form name="commentsForm" role="form" track-form>
<input type="text" ng-model="newComment" pattern="/.{1,}" maxlength="4" required ng-enter="sendComment(newComment)"/>
<button type="button" class="btn btn-primary" ng-disabled="commentsForm.$invalid" ng-click="sendComment(newComment)">Send</button>
</form>
</div>
UI:
<comments-directive value="alarmComments" send-new-comment="addNewComment(comment)"></comments-directive>
Can someone help me out...?
edit: what I want is to clear the input text field after entering a comment.
Within directive scope you should map the newComment property also along with alarmComments. like below -
scope: {
alarmComments: "=value",
newComment: "=newComment",
sendNewComment: "&sendNewComment"
},
A good way to debug problems like this is render scope ids (scope.$id) and verify they are of the same id.
Could you verify during link, what is the scope.$id and render it in your template?
<h4>Comments</h4>
<div ng-repeat="comment in alarmComments.comments">
<p>{{comment.timestamp}} | <strong>{{comment.user}}</strong>: {{comment.commentType}} {{comment.comment}}</p>
</div>
<div ng-if="alarmComments.editPermission && alarmComments.isActiveAlarm">
<form name="commentsForm" role="form" track-form>
<input type="text" ng-model="newComment" pattern="/.{1,}" maxlength="4" required ng-enter="sendComment(newComment)"/>
<button type="button" class="btn btn-primary" ng-disabled="commentsForm.$invalid" ng-click="sendComment(newComment)">Send</button>
{{$id}}
</form>
</div>
Sometimes templates create its own scope and you might have to use $parent.newComment in your templates.
Related
I have a strange situation in which $scope variables binding do not appear to be work as expected.
Here is the HTML:
<div class="input-group" style="width:100px">
<input type="number"
class="form-control"
id="Sampling_Request_for_Current_Sampling_INPUT"
ng-model="aabbcc"
style="width:125px;text-align:center">
<span class="input-group-btn">
<button class="btn btn-default" ng-disabled="Cannot_Allocate_Yet" ng-click="Get_Sampling_Request_Details()" type="button">{{All_Labels.Common.Display}}</button>
</span>
</div>
and here is the scope function invoked upon clicking on the button:
$scope.Get_Sampling_Request_Details = function () {
console.log("$scope.aabbcc: " + $scope.aabbcc) ;
}
The variable $scope.aabbcc is initialized to 0 upon controller's loading.
Regardless what I type into the input element, I always get 0 in the console.
This scenario generally happens, If you have wrapped your HTML inside ng-if, ng-switch ng-repeat.. or some other directive that creates new child scope.
See this fiddle.
So it's a best practice to wrap your scope in some model to leverage protypical inheritance and correctly bind data to $scope.
Like : $scope.data.aabbcc = 0 and use it like ng-model ='data.aabbcc'.
See this for few minutes and Read this for complete understanding.
check this working example
<div ng-controller="MyCtrl">
Hello, {{name}}!
<input type="number" ng-model="name"/>
<button class="btn btn-default" ng-disabled="Cannot_Allocate_Yet" ng-
click="Get_Sampling_Request_Details()" type="button">test</button>
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = 0;
$scope.Get_Sampling_Request_Details = function () {
console.log("$scope.aabbcc: " + $scope.name) ;
}
}
AngularJS controllers control the data. The scope is the binding part between the HTML (view) and the JavaScript (controller). You must define the ng-model inside a ng-controller within which its scope lies. Try this out.
<div ng-controller="myCtrl">
<div class="input-group" style="width:100px">
<input type="number"
class="form-control"
id="Sampling_Request_for_Current_Sampling_INPUT"
ng-model="aabbcc"
style="width:125px;text-align:center">
<span class="input-group-btn">
<button class="btn btn-default" ng-disabled="Cannot_Allocate_Yet" ng-click="Get_Sampling_Request_Details()" type="button">{{All_Labels.Common.Display}}</button>
</span>
</div>
</div>
<script>
var app = angular.module('Myapp', []);
app.controller('myCtrl', function($scope) {
$scope.Get_Sampling_Request_Details = function () {
console.log("$scope.aabbcc: " + $scope.aabbcc) ;
}
});
</script>
Declare an empty object in your controller section .
eg: $scope.obj = {};
And use like ng-model="obj.key_name" in your html. It will work.
I'm trying to create a input directive element with validation.
I'd like to manage error status in this directive.
There are 3 files
Index.html: uses this directive
textValid.js: contains directive code
textValid.html: contains the directive template
I create this directive
textValid.js
App.directive("textValid", function() {
return {
restrict: "E",
templateUrl: "tpl/textValid.html",
require: "?ngModel",
scope: {
name: "#",
element: "=",
model: "="
}
};
});
index.html
<form name="edit_form_ctrl.contract_edit_form" action="#" novalidate >
<div class="row">
<text-valid name="ncontract"model="edit_form_ctrl.contract.ncontract"
element="edit_form_ctrl.contract_edit_form.ncontract">
</text-valid>
</div>
</form>
and template textValid.html
<input type="text" name="name" ng-model="model" class="form-control" ng-required="true" value="{{model}}" />
<div>pristine: {{element.$pristine}}</div> <!--is always undefined-->
<div>Invalid: {{element.$error}}</div> <!--is always undefined-->
<span class="color-red" ng-if="element.$error.required &&!element.$pristine">
{{curLang.field_mandatory}}
</span>
I'm trying to get input control to check $error and $pristine value, but I cannot to achieve it.
I read all documentation and the book too, but with any results.
Does someone try to do that?
Thanks in advance
If you add {{double-curlies}} around the name attribute of the input element like so:
<input type="text" name="{{name}}" ng-model="model" class="form-control" ng-required="true" />
You should be able to access the ngModelController for it from inside text-valid with $scope.form[$scope.name]
A couple other notes:
using a value attribute on and element with ng-model attribute will not have an effect.
you have require: '?ngModel' in your directive definition, but there is no ng-model attribute on the text-valid element. This is ok, but you won't get any ngModelController being injected unless the text-valid element has an ng-model attribute.
Edit:
Because text-valid has isolate scope, to access the FormController, you would need to require it and bind it:
App.directive("textValid", function() {
return {
restrict: "E",
templateUrl: "tpl/textValid.html",
require: "^^form",
scope: {
name: "#",
element: "=",
model: "="
},
link: function($scope, elem, attr, FormCtrl){
$scope.form = FormCtrl;
// now you should be able to access $scope.form[$scope.name]
// (you *might* need to put any initialization that accesses it inside a $timeout to wait for it to be rendered/bound)
}
};
});
In template, you should then be able to access it like:
<div>pristine: {{form[name].$pristine}}</div>
<div>Invalid: {{form[name].$error}}</div>
Try this:
<input type="text" name="name" ng-model="model" class="form-control" ng-required="true" value="{{model}}" />
<div>pristine: {{form.name.$pristine}}</div> <!--is always undefined-->
<div>Invalid: {{form.name.$error}}</div> <!--is always undefined-->
<span class="color-red" ng-if="element.$error.required &&!element.$pristine">
{{curLang.field_mandatory}}
</span>
I have the following template:
<dynamic-field name="Name" type="input" ng-model="temp.product.name"></dynamic-field>
<dynamic-field name="Price" type="input" ng-model="temp.product.price"></dynamic-field>
<dynamic-field name="Qty" type="input" ng-model="temp.product.qty"></dynamic-field>
Custom directive code:
app.directive('dynamicField', function() {
return {
restrict: 'E',
templateUrl: getTemplate('templates/single-field.html'),
scope: {
ngModel: '='
},
link: function($scope, $element, $attrs) {
// console.log($scope);
}
};
});
In directive template, the input field is displayed:
<div class="field">
<input type="text" ng-model="ngModel" /> <!-- if $temp.product.post_text in parent scope is set to "Test", it's displayed -->
</div>
The problem is, when I modify something inside the input (isolated scope) changes are not applied to the parent scope. I think the problem is that I use primitive here:
<input type="text" ng-model="ngModel" />
... but I'm not sure how to resolve this. Any suggestions?
You must be doing something wrong. If it's two-way bound, it's two-way bound.
function MainController() {
this.name = "test";
this.logCtrlName = function() {
alert(this.name);
}
}
function dynamicField() {
return {
restrict: 'E',
template: `
<div class="field">
<input type="text" ng-model="ngModel" />
</div>
`,
scope: {
ngModel: '='
}
};
}
angular.module('app', []);
angular.module('app')
.controller('MainController', MainController)
.directive('dynamicField', dynamicField);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="MainController as ctrl">
<dynamic-field name="Name" type="input" ng-model="ctrl.name"></dynamic-field>
<a href ng-click="ctrl.logCtrlName();">Name inside parent controller</a>: {{ ctrl.name | json }}
</div>
</div>
Like you said since your model is primitive value, so whenever you change the value inside isolated scope will create its copy of scope variable.
A simple and quick way to fix that is create a function to update parent scope value $scope.$parent.ngModel directly then add ng-change to input element and call that function whenever input value is changed.
I have the following code:
<div class="col-md-10" data-ng-controller="type-controller">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-success" ng-model="typeId" data-btn-radio="'1'">
Option 1
</label>
<label class="btn btn-success" ng-model="typeId" data-btn-radio="'2'">
Option 2
</label>
</div>
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="2" />
</div>
My type-controller is empty so I'm omitting it - but I want to get the value of the attribute data-start from the last input inside the type-controller.
I'm not using jQuery.
IF the attribute data-start is significant because it is being used by some other 3rd party library, then you might consider simply using ng-init when you create this on the server:
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="2"
ng-init='start = 2' />
This will essentially run any code you need, and doesn't involve you having to parse out data attributes from the DOM.
You could write a pretty trivial directive to pull in the value and publish using an expression. This will essentially accomplish the same thing, but is more difficult in my opinion:
angular.module('data-pluck', [])
.controller('fooController', function() {
this.name = 'Foo Controller';
})
.directive('pluckData', ['$parse',
function($parse) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var expression = function() {};
expression.assign = function() {};
scope.$watch(attrs.placeData, function() {
expression = $parse(attrs.placeData);
});
scope.$watch(attrs.pluckData, function() {
expression.assign(scope, attrs[attrs.pluckData]);
});
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='data-pluck' ng-controller='fooController as ctrl'>
<h1>{{ctrl.name}}</h1>
<div data-my-val="I'm value one" pluck-data='myVal' place-data='ctrl.valueOne'>
<p>I'm a regular old <code><p></code> tag</p>
<input type='hidden' data-my-val="I'm the second value" pluck-data='myVal' place-data='ctrl.valueTwo' />
</div>
<h3>These Values Populated Dynamically</h3>
<ul>
<li>ctrl.valueOne = {{ctrl.valueOne}}</li>
<li>ctrl.valueTwo = {{ctrl.valueTwo}}</li>
</ul>
</div>
Angular comes with jqLite built in, which still has the attr() function. But it's not the Angular "way" to be manually fiddling around in the DOM from a controller. Your scope should be the interface between them.
I'm curious as to why you have a value in an attribute in your UI that isn't defined first in your model / scope? How does this value get changed? Is there a reason why you can't set it in the controller:
$scope.start = 2;
and then:
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="{{start}}" />
Can you explain a little about what data-start is meant to do?
I have a pattern related question. I build a directive to "load" content into a bootstrap popover.
HTML:
<span data-ng-show="user.provider === 'local'" class="label label-warning" data-popover="" data-placement="bottom" data-content-selector="#passwordchangeform">change password</span>
<div id="passwordchangeform" style="display:none;">
<h3>Change password</h3>
<h4>(Not finished yet!)</h4>
<form>
Old password:
<input type="text" ng-model="username" name="text"/><br/>
New password:
<input type="text" ng-model="password1" name="text"/><br/>
New password again:
<input type="text" ng-model="password2" name="text"/><br/>
<input type="button" data-ng-click="login()" value="login"/>
</form>
</div>
Directive:
angular.module('app').directive('popover', function ($compile) {
return {
restrict: 'A',
link: function (scope, el, attrs) {
var content;
if (attrs.content) {
content = attrs.content;
} else {
content = $compile("<div>" + $(attrs.contentSelector).html() + "</div>")(scope);
}
$(el).popover({
trigger: 'click',
html: true,
content: content,
placement: attrs.placement
});
}
};
});
In my controller, I implemented the function login()
Let's say in the controller login() function I get a success response to a HTTP/POST
What would be the best-practice then on actually closing the popover?? I can think of "dirty hacks" to force the closure, but I guess it could be done in a more appropriate way...
The "problem" I may have maybe related to the fact I'm using $compile in the directive to tie the content to the scope of the controller so I'm unable to close it from the link function.
Thanks,
Sander