Angularjs parsing function to templateUrl directive - angularjs

Is that possible to parsing function to angularjs directive that return a templateUrl? In my case, I have this directive
.directive('forumForm', function(){
return {
restrict : 'C',
scope : {
data : '=forum',
},
templateUrl : '/templates/forum_form.tpl.html'
}
});
This is my tempalteUrl
<input type="text" ng-model="data.Title" name="nameF" class="form-control" required="" ng-minlength="20" ng-maxlength="100">
<input type="" class="tagsinput" ng-model="data.tagIn" />
<button type="button" ng-click="fn(data)">Submit</button>
And, I call that via class like this
<div class="forumForm" forum="forum"></div>
Last, my controller have a function called fn
$scope.fn = function((){
alert('text')
})
You can see that I parsing a forum variable to my templateUrl via directive. My problem is, Is that possible to parsing a function in that directive? So if I create
<div class="forumForm" forum="forum" fn="action(forum)"></div>
And if I click the button (In my templateUrl), It's call a function that I have written in controller. Is that possible?

Yes, you can use & binding for this:
The & binding allows a directive to trigger evaluation of an
expression in the context of the original scope, at a specific time.
Any legal expression is allowed, including an expression which
contains a function call. Because of this, & bindings are ideal for
binding callback functions to directive behaviors.
Example:
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
var ctrl = this;
ctrl.forum = {}
ctrl.log = log;
function log(data){
console.log(data);
};
}])
.directive('forumForm', [function () {
var forumForm = {
restrict : 'EC',
scope : {
data : '=forum',
fn: '&'
},
templateUrl : 'forum_form.tpl.html'
}
return forumForm;
}]);
<script src="//code.angularjs.org/1.6.2/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl as $ctrl">
<forum-form forum="$ctrl.forum" fn="$ctrl.log(data)"></forum-form>
</div>
<script type="text/ng-template" id="forum_form.tpl.html">
<input type="text" ng-model="data.title" />
<input type="" class="tagsinput" ng-model="data.tagIn" />
<button type="button" ng-click="fn({data: data})">Submit</button>
</script>
</div>

Related

ng-repeat doesn't update when item added from component directive

I have problem of my code but couldn't find that,I updated an array but it doesn't update in my main view
here my code
app.controller("MainCtrl", function ($scope, $http) {
$scope.user = [];
$scope.adduser = function (fuser, luser) {
var name = { fname: fuser, lname: luser }
// debugger;
$scope.user.push(name);
};
<div ng-app="mainApp">
<div ng-controller="MainCtrl">
<h3>User Info</h3>
<userinfo></userinfo>
<div ng-repeat="name in user">
{{name.fname}}
</div>
</div>
<form name="myForm" ng-controller="MainCtrl">
First Name : <input type="text" ng-model="fname">
<br> Last Name : <input type="text" ng-model="lname">
<button type="button" class="btn btn-primary" ng-click="adduser(fname,lname)">Add</button>
</form>
You need to pass the variable in the directive,
<userinfo user='user'></userinfo>
and the directive as,
scope: {
user: '='
}
controllerAs syntax makes available your controller function this(context) accessible via its alias, here it is c. As you're using controllerAs syntax, Your binding values should be bounded to controller function context (this). You should be avoid using $scope in controller in controllerAs approach. Even while calling adduser method call it by controller alias c.adduser
Code
app.controller("MainCtrl", function ($http) {
var c= this;
c.user = [];
c.adduser = adduser;
function adduser(fuser, luser) {
var name = { fname: fuser, lname: luser }
// debugger;
c.user.push(name)
};
})

Angularjs calculate based on formula given

Im new in Angularjs
and I would like to do something like the following in directive
<!--get numb1&numb2 from user input-->
<div>
<input ng-model="numb1" type=number/>
</div>
<div>
<input ng-model="numb2" type=number/>
</div>
<!--result display on the following input box, not allow to edit-->
<div>
<input ng-model="result" formula="some formula here, can be anything" readonly/>
</div>
numb1 & numb2 can be change anytime, use $watch instead on ngChange.
Can anyone guide me on this?
You can achieve your requirement with the following code snippet.
The HTML
<div ng-app="demo">
<div ng-controller="DefaultController as vm">
<inputs formula="vm.formula"></inputs>
</div>
</div>
<script type="text/template" id="inputsTemplate">
<div>
<input type="number" ng-model="vm.a"/>
</div>
<div>
<input type="number" ng-model="vm.b"/>
</div>
<div>
<input type="number" ng-model="vm.result" readonly/>
</div>
</script>
The AngularJS code
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.controller('InputsController', InputsController)
.directive('inputs', inputs);
function DefaultController() {
var vm = this;
vm.formula = 'a + b';
}
function inputs() {
var directive = {
restrict: 'E',
scope: {
formula: '='
},
template: function () {
return angular.element(document.querySelector('#inputsTemplate')).html();
},
controller: InputsController,
controllerAs: 'vm',
bindToController: true
};
return directive;
}
InputsController.$inject = ['$scope', '$parse'];
function InputsController($scope, $parse) {
var vm = this;
var expressionFunc = $parse(vm.formula);
$scope.$watchGroup(['vm.a', 'vm.b'], function (newValue, oldValue, scope) {
if (angular.isDefined(vm.a) && angular.isDefined(vm.b)) {
var result = expressionFunc(vm);
vm.result = result;
}
});
}
The jsfiddle link here
The coding style is followed from angular-styleguide by John Papa and from Pro AngularJS by Adam Freeman
Explanation:
There is a main controller which is having a formula set on its scope such as a + b and which is passed to a directive called inputs through an attribute called formula, this an element directive meaning that it is restricted to be applied only as an individual html element.
The inputs directive gets its template from a script tag having html content
by using the template property of the directive definition. The inputs directive has its own controller bound using the controller aliasing syntax.
The inputs directive has three objects on its controller scope they're a, b and result and the magic happens in the InputsController using the $parse service which returns a function when an expression is parsed which can indeed be evaluated on an object to execute that expression and get the result i.e.
var expressionFunc = $parse(vm.formula);
var result = expressionFunc(vm);
vm.result = result;
Also as we need to watch both a and b objects on the scope, we can use the $watchGroup function on the $scope object to handle the change events and update the view accordingly.
Note: we are only parsing once var expressionFunc = $parse(vm.formula); and executing the expression many times var result = expressionFunc(vm); this is an important factor and should be considered for quality of the code and for performance.
You are in the same scope, so, you can use ng-value in order to set a new value given numb1 and numb2. For example, you formula is numb1 + numb2, just try something like this:
<input ng-model="result" ng-value="{{numb1 + numb2}}" readonly/>

$scope not getting any values

So this is how my index.html is structured.
<html ng-app='information'>
<body ng-controller="FirstController as first>
<div ng-controller="SecondController as second>
<div id="information">
<myOwnDirective ng-controller="ThirdController as thirdCtrl"></myOwnDirective>
</div>
This is my custom directive.
(function() {
var app = angular.module('information', ['ui.bootstrap']);
app.directive('myOwnDirective', function(){
return {
restrict: 'E',
templateUrl: 'my-own-directive.html',
};
});
This is a custom directive template. my-own-directive.html
<uib-accordion tag ng-repeat="info in first">
<form ng-submit="thirdCtrl.updateInformation()">
<div class="form-group">
<label for="someprop">Property</label> <input type="text" name="someprop"
class="form-control" ng-model="info.propValue"
ng-readonly="info.propValue">
</div>
<button type="submit" class="btn btn-success btn-lg btn-block">Click</button>
</form>
This is my script file: myScript.js
(function() {
angular.module('information').controller('ThirdController', [ '$scope', function($scope) {
console.log("In third controller"); // This prints
this.updateInformation = function() {
console.log("Inside updateInformation");
console.log("Inside scope is: "+ $scope) // It is undefined
};
}]);
})();
What I want to get is get value of info.propValue from the scope but I cant seem to get it. It always shows up as undefined. I have a very long form whose contents I want to read but I cant seem to get the values. Once I have them I will bemaking an Ajax call using $http. Also, if I try to invoke function updateInformation() by doing $scope.updateInformation(), it doesnt invoke, but it does get called if I do this.updateInformation(). Why ?? What am I missing or what am I doing wrong? Any help is appreciated. Have been stuck on it for quite a while. Thank you.

Why my angular controller donĀ“t find my form - $scope.form undefined

I am trying do understand why my form is not recognize inside the maincontroller, if I put another controller outside, my form is recognize.
config.js
var myApp = angular.module('Myapp', ['ui.router','oc.lazyLoad','ui.bootstrap','kendo.directives','ngStorage',]);
function config($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$urlRouterProvider.otherwise("/index/main");
$stateProvider
.state('testing', {
url: "/testing",
controller: 'MyController',
templateUrl: "testing.html"
});
}
angular
.module('Myapp')
.config(config)
.run(function($rootScope, $state) {
$rootScope.$state = $state;
});
MyController.js
function MyController($scope) {
//do something
$scope.test = {name: 'das'};
$scope.sendTest = function () {
console.log($scope.form.$valid);
console.log($scope.form.testNumber.$valid);
console.log($scope.form.testName.$valid);
};
};
angular
.module('Myapp')
.controller('MyController', ["$scope"]);
testing.html
<form name="form" novalidate>
<p>
<label>Number: </label>
<input type="number" min="0" max="10" ng-model="test.number" name="testNumber" required />
</p>
<p>
<label>Name: </label>
<input type="text" ng-model="test.name" name="testName" required />
</p>
<button ng-click="sendTest()">Submit</button>
</form>
Like this a have this error
TypeError: Cannot read property '$valid' of undefined
but if I create another controller inside MyController.js and i move the code inside like this
function ChildController($scope,$timeout) {
$scope.test = {
name: 'das'
};
$scope.sendTest = function () {
console.log($scope.form.$valid);
console.log($scope.form.testNumber.$valid);
console.log($scope.form.testName.$valid);
};
};
function MyController($scope) { //do other stuff ...};
angular
.module('Myapp')
.controller('ChildController', ["$scope", ChildController])
.controller('MyController', ["$scope"]);
and add the ng-controller to the form like this
<form name="form" novalidate ng-controller='ChildController'>
the form is reconize correctly and working.
Can anyone explain what I am missing here, i would like to understand better, I am a novice.
Thanks you for the help.
Best regards.
Jolynice
As seen in Brad Barber's comment:
The form is being created on the child scope and not the controller
scope.
A good solution like he suggested would be to add the bindToController and controllerAs syntax to your route object:
function config($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$urlRouterProvider.otherwise("/index/main");
$stateProvider
.state('testing', {
url: "/testing",
controller: 'MyController',
controllerAs: 'viewCtrl',
bindToController: 'true',
templateUrl: "testing.html"
});
}
Now you can bind the name of the form as viewCtrl.form:
<form name="viewCtrl.form" novalidate>
<p>
<label>Number: </label>
<input type="number" min="0" max="10" ng-model="viewCtrl.test.number" name="testNumber" required />
</p>
<p>
<label>Name: </label>
<input type="text" ng-model="viewCtrl.test.name" name="testName" required />
</p>
<button ng-click="viewCtrl.sendTest()">Submit</button>
</form>
You can then notate it in your controller using this:
function MyController($scope) {
// Adding variables functions available to ctrl scope
// same as vm = this;
angular.extend(this, {
test: {name: 'das'},
sendTest: function() {
console.log(this.form.$valid);
console.log(this.form.testNumber.$valid);
console.log(this.form.testName.$valid);
}
});
};
angular
.module('Myapp')
.controller('MyController', ["$scope", MyController]);
Here is a codepen that I hacked together for you:
Check whether if the form is inside any div which has ng-if condition
Like
<div ng-if="condition">
<form name="viewCtrl.form">
.
</form>
</div>
If this is so, the form would return undefined since there is new scope created for the DOM containing ng-if condition. Then use ng-show instead of ng-if that would not create new scope. I got this solution from this answer

Dynamic validation and name in a form with AngularJS

I have this form : http://jsfiddle.net/dfJeN/
As you can see the name value for the input is statically set :
name="username"
, the form validation works fine (add something and remove all text from the input, a text must appears).
Then I try to dynamically set the name value : http://jsfiddle.net/jNWB8/
name="{input.name}"
Then I apply this to my validation
login.{{input.name}}.$error.required
(this pattern will be used in an ng-repeat) but my form validation is broken. It is correctly interpreted in my browser (if I inspect the element I saw login.username.$error.required).
Any Idea ?
EDIT: After logging the scope in the console it appears that the
{{input.name}}
expression is not interpolate. My form as an {{input.name}} attribute but no username.
UPDATE: Since 1.3.0-rc.3 name="{{input.name}}" works as expected. Please see #1404
You can't do what you're trying to do that way.
Assuming what you're trying to do is you need to dynamically add elements to a form, with something like an ng-repeat, you need to use nested ng-form to allow validation of those individual items:
<form name="outerForm">
<div ng-repeat="item in items">
<ng-form name="innerForm">
<input type="text" name="foo" ng-model="item.foo" />
<span ng-show="innerForm.foo.$error.required">required</span>
</ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>
Sadly, it's just not a well-documented feature of Angular.
Using nested ngForm allows you to access the specific InputController from within the HTML template. However, if you wish to access it from another controller it does not help.
e.g.
<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';
$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // undefined
console.log($scope.formName.staticName); // InputController
}
}
</script>
<div controller='OuterController'>
<form name='myForm'>
<input name='{{ inputName }}' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>
I use this directive to help solve the problem:
angular.module('test').directive('dynamicName', function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-name'))(scope);
// $interpolate() will support things like 'skill'+skill.id where parse will not
elem.removeAttr('dynamic-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
});
Now you use dynamic names wherever is needed just the 'dynamic-name' attribute instead of the 'name' attribute.
e.g.
<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';
$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // InputController
console.log($scope.formName.staticName); // InputController
}
}
</script>
<div controller='OuterController'>
<form name='myForm'>
<input dynamic-name='inputName' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>
The problem should be fixed in AngularJS 1.3, according to this discussion on Github.
Meanwhile, here's a temporary solution created by #caitp and #Thinkscape:
// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
$provide.decorator('formDirective', function($delegate) {
var form = $delegate[0], controller = form.controller;
form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
}]);
Demo on JSFiddle.
Nice one by #EnISeeK.... but i got it to be more elegant and less obtrusive to other directives:
.directive("dynamicName",[function(){
return {
restrict:"A",
require: ['ngModel', '^form'],
link:function(scope,element,attrs,ctrls){
ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
ctrls[1].$addControl(ctrls[0]);
}
};
}])
Just a little improvement over EnlSeek solution
angular.module('test').directive('dynamicName', ["$parse", function($parse) {
return {
restrict: 'A',
priority: 10000,
controller : ["$scope", "$element", "$attrs",
function($scope, $element, $attrs){
var name = $parse($attrs.dynamicName)($scope);
delete($attrs['dynamicName']);
$element.removeAttr('data-dynamic-name');
$element.removeAttr('dynamic-name');
$attrs.$set("name", name);
}]
};
}]);
Here is a plunker trial. Here is detailed explantion
I expand the #caitp and #Thinkscape solution a bit, to allow dynamically created nested ng-forms, like this:
<div ng-controller="ctrl">
<ng-form name="form">
<input type="text" ng-model="static" name="static"/>
<div ng-repeat="df in dynamicForms">
<ng-form name="form{{df.id}}">
<input type="text" ng-model="df.sub" name="sub"/>
<div>Dirty: <span ng-bind="form{{df.id}}.$dirty"></span></div>
</ng-form>
</div>
<div><button ng-click="consoleLog()">Console Log</button></div>
<div>Dirty: <span ng-bind="form.$dirty"></span></div>
</ng-form>
</div>
Here is my demo on JSFiddle.
I used Ben Lesh's solution and it works well for me. But one problem I faced was that when I added an inner form using ng-form, all of the form states e.g. form.$valid, form.$error etc became undefined if I was using the ng-submit directive.
So if I had this for example:
<form novalidate ng-submit="saveRecord()" name="outerForm">
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit">Submit</button>
</form>
And in the my controller:
$scope.saveRecord = function() {
outerForm.$valid // this is undefined
}
So I had to go back to using a regular click event for submitting the form in which case it's necessary to pass the form object:
<form novalidate name="outerForm"> <!--remove the ng-submit directive-->
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit" ng-click="saveRecord(outerForm)">Submit</button>
</form>
And the revised controller method:
$scope.saveRecord = function(outerForm) {
outerForm.$valid // this works
}
I'm not quite sure why this is but hopefully it helps someone.
This issue has been fixed in Angular 1.3+
This is the correct syntax for what you are trying to do:
login[input.name].$invalid
if we set dynamic name for a input like the below
<input name="{{dynamicInputName}}" />
then we have use set validation for dynamic name like the below code.
<div ng-messages="login.dynamicInputName.$error">
<div ng-message="required">
</div>
</div>

Resources