Get Rendered Form into $StateChangeStart - angularjs

Is there any way that I can get the rendered form into
$rootScope.on("$stateChangeStart", function (){
})
I tried two things.
First: Using $template Request I got the template using templateURL and compiled that but it renders predefined template not the DOM's rendered.
See the code
if (fromState.name.length > 0) {
$templateRequest(fromState.templateUrl)
.then(function (html) {
var compiledElement = $compile(html)($rootScope);
var compliedForm = compiledElement.find('form');
}
}
then Secondly, I tried using
angular.element('document').find('form');
But it gives me list of attribute and all. But how to get check form is valid or not.
Document

I think what you are trying to achieve, is to block a state change when a form in the current view is not valid. I would make a directive for this, something like:
app.directive("formValidStateCheck", function($rootScope) {
return {
restrict: "A",
require: "ngForm",
link: function(scope, element, attrs, ngFormCtrl) {
$rootScope.$on("$stateChangeStart", function(event) {
if (ngFormCtrl.$invalid) {
// prevent routing
if (!confirm("Are you sure"))
event.preventDefault();
}
}
});
}
}
});
Than put the directive on your forms:
<form ng-form="myForm" form-valid-state-check>
</form>

.find() method will not work with selectors and tag names. you need to get it by form id(for this have a id to the form).
Then use angular.element(document.getElementById("#form_id"));

Related

Angularjs controller function vs directive function

Lately I've been building some modules and in some of them I only used controllers (controller is set within an existing directive I already need to use to load template) to have this comunnication between services and the view, for example:
$scope.callFunction = function(data) {
factRequest = saveData(data);
};
I also noticed I could do this from within a directive, like this:
link:function(scope) {
scope.callFunction = function(data) {
factRequest.saveData(data);
}
}
//or..
link:function(scope, element, attr) {
attrValue = attr.myValue;
element.bind('click', function(attrValue) {
factRequest.saveData(attrValue);
});
}
//or even..
link:function(scope, element, attr) {
attrValue = attr.myValue;
element.bind('click', function(attrValue) {
factRequest.saveData(attrValue);
});
var elButton = element.fin('span'); //for example
elButton.bind('click', function(attrValue) {
factRequest.saveData(attrValue);
});
}
Considering a scenario where this a reusable object, for example, a product where it display on multiple pages and have a commom function, such as addFavorite, addCart, addWishList, etc.. And also considering performance.
What is the difference between those call methods? And what is the best option to use as a call Function?
To restate, you are calling a service method on a click event and want to know where the best place to put that logic is.
Let's look at each of your examples:
Controller
angular.module('myApp').controller('MyController', function($scope, factRequest) {
$scope.callFunction = function(data) {
factRequest.saveData(data);
};
});
First of all, whenever I find myself injecting $scope into a controller I question my approach. This is because adding variables to the current scope creates hidden dependencies if you are relying using those variables in a child controller -- and is unnecessary if you are not.
Instead, you should be using the controllerAs syntax and adding the function to the controller itself. Something like this:
angular.module('myApp').controller('MyController', function(factRequest) {
var vm = this;
vm.callFunction = function(data) {
factRequest.saveData(data);
};
});
...and you would access it in your template like this:
<div ng-controller="MyController as vm">
<input ng-model="vm.data">
<button ng-click="vm.callFunction(vm.data)">
Click Me!
</button>
</div>
This is a perfectly good approach utilizing native Angular directives.
Directive w/ Link Function
angular.module('myApp').directive('myDirective', function(factRequest) {
return {
link: function(scope) {
scope.callFunction = function(data) {
factRequest.saveData(data);
}
}
};
});
Again, I don't like this because you are adding the function to scope. If you have a directive and want to expose some functionality to the template, you should use a controller. For example:
angular.module('myApp').directive('myDirective', function() {
return {
controller: 'MyDirectiveController',
controllerAs: 'myDir',
template: '<input ng-model="myDir.data">' +
'<button ng-click="myDir.callFunction(myDir.data)">' +
'Click Me!' +
'</button>'
};
}).controller('MyDirectiveController', function(factRequest) {
var myDir = this;
myDir.callFunction = function(data) {
factRequest.saveData(data);
}
});
This is essentially the same as the first example, except that it is now a reusable component.
Directive w/ Click Event Handler
angular.module('myApp').directive('myDirective', function(factRequest) {
return {
link: function(scope, element, attr) {
element.on('click', function() {
factRequest.saveData(scope.$eval(attr.myValue));
});
}
};
});
Notice I took a few liberties here. For one thing, an event handler function gets the event object as its first argument, so trying to pass attr.myValue wouldn't work. Also, I call scope.$eval(), which is a best practice that enables the use of Angular expressions in the myValue attribute.
I like this approach best, because it doesn't rely on the use of other directives like ng-click. In other words, this directive is more self-contained.
One thing I should add is that Angular will not remove this event listener when the element is removed from the DOM. It is a best practice to clean up after your directive, like this:
angular.module('myApp').directive('myDirective', function(factRequest) {
return {
link: function(scope, element, attr) {
function onClick() {
factRequest.saveData(scope.$eval(attr.myValue));
}
element.on('click', onClick);
scope.$on('$destroy', function() {
element.off('click', onClick);
});
}
};
});
Conclusion
From a performance perspective, all of these approaches are roughly equivalent. The first two don't add any watchers themselves, but ng-click and ng-model do so its six of one, half a dozen of the other.

angularjs have a directive only apply for select controllers

I'm using a directive to implement scrolling on a page from an a to a div. Meaning that by clicking on an a looks like:
<a data-scroll-on-click="" href="#projects">Projects</a>
The page smoothly scrolls to:
<div id="projects">
To make this happen, I am using an attribute scroll-on-click via the following directive:
consortiumApp.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var idToScroll = attrs.href;
element.on('click', function(event) {
event.preventDefault();
var $target;
if (idToScroll) {
$target = $(idToScroll);
} else {
$target = element;
}
$("body").animate({scrollTop: $target.offset().top}, 1500, 'easeInOut
});
}
}
});
The preventDefault stops the router from kicking in.
The a with the data-scroll-on-click attribute is part of a navbar that I would like to include on other pages via ng-include. However, this means that the a element will have the scroll-on-click attribute on pages where scrolling does not make sense. Meaning that when the navbar is on other pages besides the main page, I want the anchors in it to function like links back to the main page and not to trigger scrolling.
I'm not sure what a good solution is: I'm not sure if it is possible to have scroll-on-click appear only when there is a certain active controller (a sort of conditional attribute)? Or if it is possible to indicate the current active controller in a directive?
Worst comes to worst, I will just write two navbars - one for the main page that implements scrolling via a directive and one for subsidiary pages that implements linking via the router, but I have a feeling there is a more concise way to do this.
I think you can pass boolean value along with the data-scroll-on-click='true/false' and check this in the directive if its true than proceed for scroll otherwise ignore it.
<a data-scroll-on-click="" href="#projects">Projects</a>
And
consortiumApp.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var idToScroll = attrs.href;
var scroll_needed = attrs.scrollOnClick;
if(scroll_needed === true){
element.on('click', function(event) {
event.preventDefault();
var $target;
if (idToScroll) {
$target = $(idToScroll);
} else {
$target = element;
}
$("body").animate({scrollTop: $target.offset().top}, 1500, 'easeInOut
});
}
}
}
});

How to use angular "element" parent or find methods to get access to the parent form?

In a angular js directive which is on the "form input leve" I want a reference to the parent form in order to access the "on submit" event.
Right now, I can query the form by Id and add the submit.
But, I want to achieve it with angula's "element" method.
But it just won't work.
angular.module('app.common').directive('validateField', ['$timeout', function ($timeout) {
return {
restrict: 'A',
require: '^form',
scope: {
error: '=validateField'
},
link: function(scope, el, attrs, form) {
if (document.getElementById('testing')){
var e = angular.element(document.getElementById('testing'))
e.on('submit', function() {
console.log('Works!');
})
};
el.parent('form').on('submit', function() {
console.log('How do i do this?');
})
}
};
}]);
Any ideas how?
UPDATE
I have found a solution, not sure it's an elegant one though:
// Validate field on "form submit".
angular.element(el[0].form).on('submit', function() {
validate();
});
Hmm, this looks 'wrong' to me. Your directive shouldn't need to know about some specific element outside of its own scope in this way. You would normally use some kind of service to synchronise data across components or provide the events/event handling that you need, or have your controller bring the two together.
Without seeing what you're trying to do in the event handler it's hard to know what you're trying to achieve, but it looks like you're just intending to do some validation. Can you not do this using some custom AngularJS validation?
https://docs.angularjs.org/guide/forms#custom-validation
You can use CSS selectors inside angular.element function,
angular.element('#myId'),
angular.element('.myClass')
With that said, you can abstract your directive even further when you are requiring the form controller, by using the $name property of it:
require: '^form',
link: function(scope,el,attrs,form) {
...
if(form.$name) {
var domForm = angular.element("[name=" + form.$name + "]")
domForm.addClass('whee');
}
}

Modifying a directive to 'watch' a ctrl property?

I have a directive which checks if they click submit on a form. If so, it adds the 'submitted' class. This allows me to style the form inputs as red only when they have submitted a form (I hate having it red in real-time as they're typing).
'use strict';
app.directive('formSubmitted', function() {
return {
restrict: 'A',
require: 'form',
link: function(scope, element, attrs, ctrl) {
ctrl.$submitted = false;
element.on('submit', function() {
scope.$apply(function() {
ctrl.$submitted = true;
element.addClass('submitted');
});
});
}
};
});
The problem is 'resetting' a form once it has been submitted successfully... I need to remove the submitted class from inside the controller. I have tried a lot of ways to do this without success... as in this pseudo-code...
angular.element($scope.myForm).removeClass('submitted');
What I am thinking is instead of doing that from the controller (which doesn't work anyway), that I try to make the 'submitted' class mirror the $submitted property on ctrl... This way I could do...
$scope.myForm.$submitted = false and the class would update appropriately.
I have no idea even where to begin with though, and googling isn't helping...
Thanks!
A simple approach I have used in situations like this is leveraging the Angular ngClass directive and binding to a property on the controller that maintains whether the state is submitted or not. Something like so:
<button ng-click="isSubmitted = !isSubmitted">Submit</button>
<form ng-class="{submitted: isSubmitted}">
</form>
You can use the ng-class directive:
<form name="myForm" ng-class="{submitted: $submitted}">
See the doc here: https://docs.angularjs.org/api/ng/directive/ngClass
Within the controller handling the form submission, you certainly have a submit function:
$scope.submit = function (form) {
$scope.$submitted = true;
if (form.$invalid) {
return;
}
// Actually send data to backend, eventually receiving a promise
promiseFormBackend = MyService.sendForm();
promiseFromBackend.then(function () {
$scope.$submitted = false: // resetting the form class
});
}

JQuery UI Spinner is not updating ng-model in angular

Angular's ng-model is not updating when using jquery-ui spinner.
Here is the jsfiddle http://jsfiddle.net/gCzg7/1/
<div ng-app>
<div ng-controller="SpinnerCtrl">
<input type="text" id="spinner" ng-model="spinner"/><br/>
Value: {{spinner}}
</div>
</div>
<script>
$('#spinner').spinner({});
</script>
If you update the text box by typing it works fine (you can see the text change). But if you use the up or down arrows the model does not change.
Late answer, but... there's a very simple and clean "Angular way" to make sure that the spinner's spin events handle the update against ngModel without resorting to $apply (and especially without resorting to $parse or an emulation thereof).
All you need to do is define a very small directive with two traits:
The directive is placed as an attribute on the input element you want to turn into a spinner; and
The directive configures the spinner such that the spin event listener calls the ngModel controller's $setViewValue method with the spin event value.
Here's the directive in all its clear, tiny glory:
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
Note that $setViewValue is intended for exactly this situation:
This method should be called when an input directive wants to change
the view value; typically, this is done from within a DOM event
handler.
Here's a link to a working demo.
If the demo link provided above dies for some reason, here's the full example script:
(function () {
'use strict';
angular.module('ExampleApp', [])
.controller('ExampleController', ExampleController)
.directive('jqSpinner', jqSpinner);
function ExampleController() {
var c = this;
c.exampleValue = 123;
};
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
})();
And the minimal example template:
<div ng-app="ExampleApp" ng-controller="ExampleController as c">
<input jq-spinner ng-model="c.exampleValue" />
<p>{{c.exampleValue}}</p>
</div>
Your fiddle is showing something else.
Besides this: Angular can not know about any changes that occur from outside its scope without being aknowledged.
If you change a variable of the angular-scope from OUTSIDE angular, you need to call the apply()-Method to make Angular recognize those changes. Despite that implementing a spinner can be easily achieved with angular itself, in your case you must:
1. Move the spinner inside the SpinnerCtrl
2. Add the following to the SpinnerCtrl:
$('#spinner').spinner({
change: function( event, ui ) {
$scope.apply();
}
}
If you really need or want the jQuery-Plugin, then its probably best to not even have it in the controller itself, but put it inside a directive, since all DOM-Manipulation is ment to happen within directives in angular. But this is something that the AngularJS-Tutorials will also tell you.
Charminbear is right about needing $scope.$apply(). Their were several problems with this approach however. The 'change' event only fires when the spinner's focus is removed. So you have to click the spinner then click somewhere else. The 'spin' event is fired on each click. In addition, the model needs to be updated before $scope.$apply() is called.
Here is a working jsfiddle http://jsfiddle.net/3PVdE/
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
var mdlAttr = $(this).attr('ng-model').split(".");
if (mdlAttr.length > 1) {
var objAttr = mdlAttr[mdlAttr.length - 1];
var s = $scope[mdlAttr[0]];
for (var i = 0; i < mdlAttr.length - 2; i++) {
s = s[mdlAttr[i]];
}
s[objAttr] = ui.value;
} else {
$scope[mdlAttr[0]] = ui.value;
}
$scope.$apply();
}
}, 0);
});
Here's a similar question and approach https://stackoverflow.com/a/12167566/584761
as #Charminbear said angular is not aware of the change.
However the problem is not angular is not aware of a change to the model rather that it is not aware to the change of the input.
here is a directive that fixes that:
directives.directive('numeric', function() {
return function(scope, element, attrs) {
$(element).spinner({
change: function(event, ui) {
$(element).change();
}
});
};
});
by running $(element).change() you inform angular that the input has changed and then angular updates the model and rebinds.
note change runs on blur of the input this might not be what you want.
I know I'm late to the party, but I do it by updating the model with the ui.value in the spin event. Here's the updated fiddle.
function SpinnerCtrl($scope, $timeout) {
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
$scope.spinner = ui.value;
$scope.$apply();
}
}, 0);
});
}
If this method is "wrong", any suggestions would be appreciated.
Here is a solution that updates the model like coder’s solution, but it uses $parse instead of parsing the ng-model parameter itself.
app.directive('spinner', function($parse) {
return function(scope, element, attrs) {
$(element).spinner({
spin: function(event, ui) {
setTimeout(function() {
scope.$apply(function() {
scope._spinnerVal = = element.val();
$parse(attrs.ngModel + "=_spinnerVal")(scope);
delete scope._spinnerVal;
});
}, 0);
}
});
};
});

Resources