I have an Angular directive that contains a form with validation. I want to have a button in my view that gets disabled when this directive's form is $pristine, but the button exists in the view at the level of the controller, so I have no access to the child form inside the directive.
How can I access the form inside the directive from the parent controller without doing some weird hack?
Here is one fine way to do it. Expose a directive controller, like the form does by exposing the FormController object on the current scope. Since your directive creates isolated scope, the code will look like:
controller:function($scope, $element, $attrs) {
$scope.$parent[$attrs.myDirectiveName]=this; // Exposes the directive controller on the parent scope with name myDirectiveName
// Now you can define a function that tells state of the form. Or expose the form on the controller
this.isPristine=function() {
return $scope.formName.$pristine;
}
}
Once the directive controller is there, you attach the directive to a html element and the controller is available on the current scope.
<div my-directive='mydir'></div> // create a property $scope.mydir on current scope.
Now you can check the state using $scope.mydir.isPristine()
Why not just add to the button ng-disabled
Fiddle: http://jsfiddle.net/4vhsmdcn/
<div ng-controller="MyCtrl">
<form name="bob">
<input name="some" type="text" ng-model="pie"/>
<button ng-disabled="bob.$pristine">Submit</button>
</form>
</div>
Related
This is weird as it should be pretty straightforward. I will post my code first and then ask the question:
html -
<div ng-controller="myController" ng-switch on="addressCards">
<div>
{{addCustom}} // does not get changed
<div ng-if="addCustom === false">
{{addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
controller -
(function(){
'use strict';
angular.module('myApp')
.controller('myController',['$scope',myController]);
function myController($scope){
$scope.addCustom = false;
}
})();
So I simply introduced a scope variable - addCustom - in my controller and set it to false as default. This variable controls if a div is shown or not. I am also outputting the value of the scope on the html at 2 different locations. Please see above.
But when I change its value in an ng-click within this divs, its value is changing at the second location(within the div) but not the first one(outside the div). Because of this the div does not change state as well.
I am not able to figure what might be possibly wrong here. Can someone please help?
The thing happening is when you have ng-repeat,ng-switch and ng-if directive, angular creates child scope for those element wherever they are placed. Those newly created scope are prototypically inherited from there parent scope.
On contrast Prototypal Inheritance means?
If you have scope hierarchy, then parent scope property are accessible inside child scope, only if those property are object (originally object referenced is passed to child scope without creating its new reference). But primitive datatypes are not accessible inside child scope and if you looked at your code addCustom scope variable is of primitive dataType.
Lets discuss more about it.
Here you have myController controller which has addCustom scope variable of primitive type & as I said above ng-switch & ng-if directive are compiled they do create new child scope on that element. So in your current markup you have ng-switch on ng-controller="myController" div itself. For inner html it had created a child scope. If you wanted to access parent scope inside child(primitive type) you could use $parent notation before scope variable name. Now you can access the addCustom value by $parent.addCustom.
Here its not over when angular compiler comes to ng-if div, it does create new child scope again. Now inner container of ng-if will again have child scope which is prototypically inherited from parent. Unfortunately in your case you had primitive dataType variable so you need to use $parent notation again. So inside ng-if div you could access addCustom by doing $parent.$parent.addCustom. This $parent thing will solve your problem, but having it on HTML will make unreadable and tightly couple to its parent scope(suppose on UI you would have 5 child scope then it will look so horrible like $parent.$parent.$parent.$parent). So rather you should go for below approach.
Follow Dot rule while defining ng-model
So I'd say that you need to create some object like $scope.model = {} and add addCustom property to it. So that it will follow the prototypal inheritance principle and child scope will use same object which have been created by parent.
angular.module('myApp')
.controller('myController',['$scope',myController]);
function myController($scope){
$scope.model = { addCustom : false };
}
And on HTML you will use model.addCustom instead of addCustom
Markup
<div ng-controller="myController" ng-switch on="addressCards">
<div>
{{model.addCustom}} // does not get changed
<div ng-if="model.addCustom === false">
{{model.addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="model.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
Other best way to deal with such kind of issue is, use controllerAs pattern while using controller on HTML.
Markup
<div ng-controller="myController as myCtrl" ng-switch on="addressCards">
<div>
{{myCtrl.addCustom}} // does not get changed
<div ng-if="myCtrl.addCustom === false">
{{myCtrl.addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="myCtrl.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
From the Docs:
The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.
-- AngularJS ng-if directive API Reference
The rule of thumb is don't bind to a primitive, instead bind to an object.
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-if, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)1
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.1
Ng-if introduces a different scope. Try this as an attribute of your button:
ng-click="$parent.addCustom = false"
This will assure that you're accessing the same scope.
It's because of this that it's always good practice to use the ControllerAs syntax. All attributes are bound to the controller object and namespaced accordingly, meaning you never run in to this problem. I've updated your example using the ControllerAs syntax to demonstrate its use.
HTML
<div ng-controller="myController as vm" ng-switch on="addressCards">
<div>
{{vm.addCustom}}
<div ng-if="vm.addCustom === false">
{{vm.addCustom}}
<button type="button" class="btn btn-primary btn-icon-text" ng-click="vm.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
Controller
(function(){
'use strict';
angular.module('myApp')
.controller('myController', [ myController ]);
function myController () {
var vm = this;
vm.addCustom = false;
}
})();
Here is an excellent article providing more detail about ControllerAs and it's advantages.
Both Classic Controller and Controller As have $scope. That's super important to understand. You are not giving up any goodness with either approach. Really. Both have their uses.
Here's Plunker
I have an external template within in a controller with ng-include. It is shown and hidden based on click event of Button.It is working as required but with $parent in ng-include Template.Is there any other better way of doing this ?
Html
<body ng-controller="MainCtrl">
<div data-ng-include="'terms.html'" data-ng-show="otherContent"></div>
<div ng-show="mainPage">
<p>Hello {{name}}!</p>
<button data-ng-click="mainPage=false; otherContent=true">Link to some Content</button>
</div>
</body>
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.mainPage=true;
});
External Template
<p>Some content here </p>
<button data-ng-click="$parent.mainPage=true; $parent.otherContent=false">Back</button>
Option1 - Set property on an object in the scope
In the main controller add an object on the scope.
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.page={mainPage:true};
});
and in the ng-click do:-
<div data-ng-include="'terms.html'" data-ng-show="page.otherContent"></div>
<div ng-show="page.mainPage">
<button data-ng-click="page.mainPage=true; page.otherContent=false">Back</button>
<!-- -->
<button data-ng-click="page.mainPage=true; page.otherContent=false">Back</button>
Demo - setting property on an object in the scope
Option2 - Use function
Instead of setting properties on the view (Which is anyways a good idea to abstract out too much logic from the view), Do your set operations in the controller exposed as a function that can be invoked from the view, which also gives extensibility when you need to add more logic for that particular action. And in your case you could even use the same function and call it from both the button clicks and flipped based on a boolean argument.
Demo - with function
Option3 - Use Controller Alias
Using an alias for the controller, which is nothing but instance of the controller is set as a property on the scope with the property name same as the alias provided. This will make sure you are enforce to use dot notation in your bindings and makes sure the properties you access on the child scopes with the controller alias are inherited as object reference from its parent and changes made are reflected both ways. With angular 1.3, it is also possibly to set the isolate scoped directive properties are bound to the controller instance automatically by setting bindToController property in the directive configuration.
Demo - With Controller alias
ControllerAs is the recommend way of avoiding this problem.
Using controller as makes it obvious which controller you are accessing in the template when multiple controllers apply to an element.
If you are writing your controllers as classes you have easier access to the properties and methods, which will appear on the scope, from inside the controller code.
Since there is always a . in the bindings, you don't have to worry about prototypal inheritance masking primitives.
<body ng-controller="MainCtrl as main">
<div data-ng-include="'terms.html'" data-ng-show="main.otherContent"></div>
<div ng-show="mainPage">
<p>Hello {{main.name}}!</p>
<button data-ng-click="main.mainPage=false; main.otherContent=true">Link to some Content</button>
</div>
</body>
Here are some resources for controller as:
http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
http://toddmotto.com/digging-into-angulars-controller-as-syntax/
https://docs.angularjs.org/api/ng/directive/ngController#example
So I have below index.html:
<div ng-controller="UsersController">
<div ng-include='"assets/users/partials/template.html"'></div>
<a ng-click="get_data()">Get</a>
</div>
Template.html:
<input type="text" ng-model="SearchUser" name="SearchUser" />
My controller:
app.controller('UsersController', ['$scope', function($scope) {
$scope.get_data = function(){ console.log($scope.SearchUser); };
}
]);
So in above case on the click anchor, I am getting undefined in the $scope.SearchUser scope value.
But if I take that input out of the template and put inside main HTML it works.
I checked for multiple controller declaration and other stuffs but nothing worked for me.
I am using angular 1.2.25 version.
ng-include defines its own scope, which inherits from the controller scope. So SearchUser is set, but as an attribute of the child scope.
As always, the solution is to have a dot in your ng-model, and to define the outer object in the controller scope:
$scope.state = {};
and, in the HTML:
<input type="text" ng-model="state.SearchUser" name="SearchUser" />
That way, angular will get the state field from the child scope. Since the child scope prototypically extends the controller scope, it will find it in the controller scope, and it will write the SearchUser attribute of the state object.
In my view I have an input, a span and a button like so:
<script type="text/ng-template" id="myTemplate.html">
<input type="text" ng-model="phoneNumber">
<span>{{ phoneNumber}}</span>
<input type="button" ng-click="click()">
</script>
When typing in the textbox, the content of the span updates as expected reading. But when clicking the button, phoneNumber has not updated inside the controller:
app.controller('myPopopCtrl', ['$scope', '$modalInstance',
function ($scope, $modalInstance) {
$scope.phoneNumber= '';
$scope.click = function() {
alert($scope.phoneNumber); // alerts only ''
};
Is there some newbe mistake you can make in angular which makes stuff not updating on the $scope inside a controller?
Are there some $scope issues with the angular-ui modal I need to be aware of?
Edit:
It seems like phoneNumber gets created in 2 scopes. One time in the scope at the blue arrow which where phoneNumber: '' and once in the child scope at the red arrow. The view uses the phoneNumber in the child scope and the controller uses the phoneNumber in the parent scope...
Why are two scopes created?
ng-include creates a new scope. So pass a object instead of string
$scope.phone={number:null}
The template then looks like
<script type="text/ng-template" id="myTemplate.html">
<input type="text" ng-model="phone.number">
<span>{{ phone.number}}</span>
<input type="button" ng-click="click()">
</script>
Look at this wiki to understand the issues with prototypal inheritance.
Had the same problem and after a few experiments I've settled on writing
<input type="text" ng-model="$parent.phoneNumber">
This way input is bound to the blue scope, which is exactly what you wanted.
Other way is to initialise phoneNumber with a true-ish value. Try $scope.phoneNumber= '123'; - worked fine for me.
Angular seems to walk up the scope tree looking for an attribute to bind to. If nothing's found - it binds to the inner-most scope, red scope in your pic.
As other answer suggests - this red scope is created by ng-include, as a child of controller's $scope.
this fiddle represents what i am trying to do:
http://jsfiddle.net/d1001001/dwqw6/.
The grid directive needs to grab some data from controller, but since it's nested in the modal directive, which has isolated scope, it doesn't have access to controller's scope. If i
put it like this
<div ng-controller="MyCtrl">
<grid data="data" cols="cols"></grid>
</div>
it works.
Is there a solution to this? I don't feel like passing the data and cols variables to the modal directive as well. Thanks
Use
<div ng-transclude></div>
instead of
<ng-transclude></ng-transclude>
in the template of modal directive.