Calling methods conditionally inside Angularjs directives - angularjs

I'm a beginner to Angularjs, what I'm trying to do is call two different methods from a directive depending on the condition.
My work flow is
I have a form in a directive
When i try to add a new record via the form, function in the
directive should call the save() method
when I try to update a new record, function in the directive should
call the update method
This is my current code
#new html form (calling the form directive)
<recipe-form recipeForm="recipeFormData" > </recipe-form>
#edit html form (calling the form directive)
<recipe-form recipeForm="recipeFormData" > </recipe-form>
So in my directive I have the following method
#form directive
<form id="signup-form_id" ng-submit="$parent.Update()">
So what I want to do is, when the directive calls from the #new I want the method to be
<form id="signup-form_id" ng-submit="$parent.Create()">
So what I want to do is, when the directive calls from the #edit I want the method to be
<form id="signup-form_id" ng-submit="$parent.Update()">
I was trying to pass the method as a param, but for some reason it didn't work:
#update form
<recipe-form recipeForm="recipeFormData" update="Update()"> </recipe-form>

You didn't tell how you distinguish between the two actions, but you can always specify the method in a boolean variable (that you specify as a directive attribute, or a controller's scope variable, etc... let's call it updateMethod) and use it like this:
<form id="signup-form_id" ng-submit="updateMethod ? $parent.Update() : $parent.Create()">
Or even two separate forms (especially useful if the forms themselves are different):
<form id="signup-form_id" ng-submit="$parent.Update()" ng-if="updateMethod">
and:
<form id="signup-form_id" ng-submit="$parent.Create()" ng-if="!updateMethod">
The third option would be using a single submit function, but putting an if/else branching inside it.

You actually pass the update method result by writing the () brackets, not its reference.
I advice you to pass your update / create function in your directive attributes. This way is more parametrable, and doesn't refer to $parent wich is not well isolated.
HTML:
<recipe-form custom-data="recipeFormData" method="create"></recipe-form>
<recipe-form custom-data="recipeFormData" method="update"></recipe-form>
Controller :
$scope.recipeFormData = { foo: 'bar' };
$scope.create = function (data) {
// create something
}
$scope.update = function (data) {
// update something
}
Directive :
app.directive('recipeForm', function () {
return {
templateUrl: 'mySuperDirective.html',
// creates an isolated scope
scope: {
customData: '=',
method: '='
}
};
});
mySuperDirective.html :
<form ng-submit="method()">
<input type="text" ng-model="customData.foo">
<button type="submit">OK</button>
</form>

Related

Override controller functions in directive is a good idea?

I have a generic functionality implemented inside a controller. When i write a directive is it good idea to extend those controller functions inside the directive ?
Like in below implementation inside the link function.
var superCancel = scope.cancel;
// Overriding the cancel function from the controller
scope.cancel = function() {
if(element.hasClass('ng-dirty')){
element.removeClass("ng-dirty");
}
// Calling controller cancel
superCancel();
};
If your directive html is coming inside the controller in html then you can use $parent instead of rewriting
in directive:
$scope.$parent.cancel(); // only if controller coming as parent
If the controller is not coming as parent it's better to use a service or factory to implement that
Read here for more
It is better to have the directive use an attribute to set a parent scope value.
For example:
JS
app.directive("setModelApi", function() {
return {
require: "ngModel",
link: function(scope,elem,attrs, ngModelCtrl) {
scope.$eval(attrs.setModelApi, {$api: ngModelCtrl})
}
}
});
In the above example, the setModelApi directive evaluates the Angular Expression defined by the set-model-api attribute with $api exposing the ngModelController.
HTML
<input ng-model="x" set-model-api="xmodel=$api">
<button ng-click="xmodel.$setPristine()">Set Pristine</button>
The setModelApi directive sets the xmodel scope variable to the ng-model-controller API.
The button invokes the $setPristine method of the ng-model-controller API.
From the Docs:
$setPristine();
Sets the control to its pristine state.
This method can be called to remove the ng-dirty class and set the control to its pristine state (ng-pristine class). A model is considered to be pristine when the control has not been changed from when first compiled.
-- AngularJS ngModelController API Reference -- $setPristine
By using an HTML directive attribute to define the scope variable to which the API attaches, different inputs can use the directive and the connections are exposed in the HTML.
The DEMO on JSFiddle

How to call a custom directive's action in AngularJS?

In AngularJS you can make a button to call an action like this:
<div ng-controller="myController">
<button ng-click="onButtonClicked()">Click me</button>
</div>
So, I'm inserting a custom directive like this:
and in my-canvas.js directive file's link function I replace the tag with a KineticJS canvas. Then, User manipulate the canvas by dragging around Kinetic shapes and, finally, when User does with the shapes what he's required to do, I want the directive to call an action defined on myController. I'm thinking about something like this:
<div ng-controller="myController">
<my-canvas ng-success="onScenarioSuccess" />
</div>
but I can't figure out how the correct way to do it.
How can I make a directive to call it's action/event programmatically?
When you want your directive to expose an API for binding to behaviors you should use an isolate scope and use the & local scope property. This allows you to pass in a function that the directive can invoke. Here is a simple example:
.directive('testDirective', function () {
return {
restrict: 'E',
scope: {
action: '&'
},
template: '<button ng-click="action()">Test</button>'
};
});
And use it like:
<test-directive action="myControllerFunction()"></test-directive>
As per the documentation:
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.
There'are some more detail in the documentation.
If you want to expose a custom event like ng-success and want to call a function on the event.
You can either do what #Beyers has mentioned using isolated scope.
Or else look at the source code of ng-click, it just wraps the javascript event inside $scope.apply, using the $parse service to evaluate the expression passed to it. Something like this can be added in your link function
var fn = $parse(attr['ngSuccess']);
element.on('someEvent', function (event) {
var callback = function () {
fn(scope, {
$event: event
});
};
scope.$apply(callback);
});
The advantage of this mechanism is that isolated scope is not created.

handle submit event in directive as well

I’m quite new to Angular but I love it already!
I need to write a couple of reusable components for a wizard.
I would like to handle the submit event from the form in the directive as well, is this possible?
<form ng-submit="submit()" name="exampleForm">
<foo data="someData"></foo> <!-- I need to handle the submit event in directive as well-->
<input type="submit" id="submit" value="Submit"/>
</form>
If the user presses enter or clicks the button on the form the directive has to make a call to the backend and double check the data.
If the check is successful the form will be valid.
I built a simple example here
Thanks in advance!
Stefan
Yes it's possible.
The first issue is targeting the correct $scope. Right now your code targets the submit() function on the controller. The <form> can't see the $scope of the directive due to how the html elements are nested.
Since you want to submit() from the directive, then the directive template should also include the <form> element and the submit input button.
Invoking foo will look like this:
<foo data="someData" name="exampleForm"></foo>
If you would rather keep foo the way it is (and that's a totally legit way to go about it) then you'll need a new directive with the submit() function. So you'd have 2 directives working together (Very angular! Many wow!).
What you will want here is a custom directive for form validation. Custom form validators are added to the $validators object on the ngModelController.
For a simple example for an "integer" directive (found in the Angular docs here under "Custom Validation"), the directive would be defined like this:
var INTEGER_REGEXP = /^\-?\d+$/;
app.directive('integer', function() { return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.integer = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (INTEGER_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
} }; });
And it would be used in the form like this:
<form name="form" class="css-form" novalidate>
<input type="number" ng-model="size" name="size" min="0" max="10" integer />{{size}}<br />
</form>
There is also an option for asynchronous validation, adding the validator to the $asyncValidatorsobject. More info is found at the link mentioned above. The working example using both is found here
Here's a working example:
http://plnkr.co/edit/sckCOq3a50PBjat2uixk?p=preview
I've created another 2-way binding on the directive's scope (called "submit"), so you can specify the name of the submit function as it will be seen from ExampleController's scope (I used "directiveSubmit" here).
scope: {
data: '=',
submit: '='
},
.
<foo data="someData" submit="directiveSubmit"></foo>
The directive's controller then creates that function and assigns it to its own scope, and that also assigns it to ExampleController's scope via the magic of 2-way binding.
Next you can run that submit function from ExampleController's scope and it will actually be referring to the newly created function in the directive.
<form ng-submit="directiveSubmit(exampleForm)" name="exampleForm" novalidate>
Notice that I'm also passing the exampleForm into that function. Without that the directive will have a hard time getting access to it.
Finally, I've also added novalidate to the form because if you don't do that some browsers (i.e. Chrome) will handle form validation in a way that may (or may not) be undesirable. In Chrome try removing that and submitting, then you'll see what I mean (I also made the "name" field required).

AngularJs can't access form object in controller ($scope)

I am using bootstrap-ui more specifically modal windows. And I have a form in a modal, what I want is to instantiate form validation object. So basically I am doing this:
<form name="form">
<div class="form-group">
<label for="answer_rows">Answer rows:</label>
<textarea name="answer_rows" ng-model="question.answer_rows"></textarea>
</div>
</form>
<pre>
{{form | json}}
</pre
I can see form object in the html file without no problem, however if I want to access the form validation object from controller. It just outputs me empty object. Here is controller example:
.controller('EditQuestionCtrl', function ($scope, $modalInstance) {
$scope.question = {};
$scope.form = {};
$scope.update = function () {
console.log($scope.form); //empty object
console.log($scope.question); // can see form input
};
});
What might be the reasons that I can't access $scope.form from controller ?
Just for those who are not using $scope, but rather this, in their controller, you'll have to add the controller alias preceding the name of the form. For example:
<div ng-controller="ClientsController as clients">
<form name="clients.something">
</form>
</div>
and then on the controller:
app.controller('ClientsController', function() {
// setting $setPristine()
this.something.$setPristine();
};
Hope it also contributes to the overall set of answers.
The normal way if ng-controller is a parent of the form element:
please remove this line:
$scope.form = {};
If angular sets the form to your controllers $scope you overwrite it with an empty object.
As the OP stated that is not the case here. He is using $modal.open, so the controller is not the parent of the form. I don't know a nice solution. But this problem can be hacked:
<form name="form" ng-init="setFormScope(this)">
...
and in your controller:
$scope.setFormScope= function(scope){
this.formScope = scope;
}
and later in your update function:
$scope.update = function () {
console.log(this.formScope.form);
};
Look at the source code of the 'modal' of angular ui bootstrap, you will see the directive has
transclude: true
This means the modal window will create a new child scope whose parent here is the controller $scope, as the sibling of the directive scope. Then the 'form' can only be access by the newly created child scope.
One solution is define a var in the controller scope like
$scope.forms = {};
Then for the form name, we use something like forms.formName1. This way we could still access it from our controller by just call $scope.forms.formName1.
This works because the inheritance mechanism in JS is prototype chain. When child scope tries to create the forms.formName1, it first tries to find the forms object in its own scope which definitely does not have it since it is created on the fly. Then it will try to find it from the parent(up to the prototype chain) and here since we have it defined in the controller scope, it uses this 'forms' object we created to define the variable formName1. As a result we could still use it in our controller to do our stuff like:
if($scope.forms.formName1.$valid){
//if form is valid
}
More about transclusion, look at the below Misco's video from 45 min. (this is probably the most accurate explanation of what transcluded scopes are that I've ever found !!!)
www.youtube.com/watch?v=WqmeI5fZcho
No need for the ng-init trickery, because the issue is that $scope.form is not set when the controller code is run. Remove the form = {} initialization and get access to the form using a watch:
$scope.$watch('form', function(form) {
...
});
I use the documented approach.
https://docs.angularjs.org/guide/forms
so, user the form name, on "save" click for example just pass the formName as a parameter and hey presto form available in save method (where formScopeObject is greated based upon the ng-models specifications you set in your form OR if you are editing this would be the object storing the item being edited i.e. a user account)
<form name="formExample" novalidate>
<!-- some form stuff here -->
Name
<input type="text" name="aField" ng-model="aField" required="" />
<br /><br />
<input type="button" ng-click="Save(formExample,formScopeObject)" />
</form>
To expand on the answer by user1338062: A solution I have used multiple times to initialize something in my controller but had to wait until it was actually available to use:
var myVarWatch = $scope.$watch("myVar", function(){
if(myVar){
//do whatever init you need to
myVarWatch(); //make sure you call this to remove the watch
}
});
For those using Angular 1.5, my solution was $watching the form on the $postlink stage:
$postLink() {
this.$scope.$watch(() => this.$scope.form.$valid, () => {
});
}

Wrapping angular-ui-typeahead breaks the on-select callback

I'm trying to wrap AngularUI's typeahead directive with my own directive so I can package up some shared data/behavior for easier reuse in my app: Plunker
In the onSelect callback, why does the correct value only show up after the timeout?
For reference, this works correctly without the wrapping directive: Plunker
Use = inside an isolate scope directive so that the bound function is called only after the model is updated:
scope : {
selectedModel : "=",
onSelect : "="
}
Plunker
You have to use an ampersand in the directive and pass the item in the html. From the Angular Docs "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."
Isolated scope in directive:
scope: {
onSelect: '&'
}
Html
<input autocomplete="off"
placeholder="Enter “jo” and pick something"
ng-model="selectedModel"
typeahead="v as v.name for v in vendors | filter:$viewValue"
typeahead-on-select="onSelect({item: $item})" />
Callback function should take an item
function myCallback(item) {
console.log(item);
}

Resources