Ways to access element controllers (eg. ngModel & form) - angularjs

I have a directive that is always placed inside <form> on elements that wrap form elements (input/select/etc), let's assume directive is called wrapper:
<form>
<something>
<wrapper>
<input />
</wrapper>
</something>
</form>
And from within this directive I need to access both formController and ngModelController. I know I can require: ^form in wrapper's directive definition object, but I still need access to ngModelController. I found two ways to achieve it:
childFormControl.controller('ngModel')
childFormControl.data('$ngModelController')
// also for controller I could use this, instead of require
childFormControl.closest('form').controller('form')
childFormControl.closest('form').data('$formController')
My question is : is it considered a hack to use controller or data methods to access the controller or is it safe and considered as usage of public Angular API? I did not find any examples on Angular documentation with this approach.

<body ng-controller="ngModelController ">
<form>
<something>
<wrapper>
<input />
</wrapper>
</something>
</form>
</body>
Js file
(function(){
var app = angular.module('ngModelController ', function(){
//Your main controller
});
app.directive('wrapper', function(){
return {
restrict : 'E',
controller : function(){
//Your form controller actions
},
controllerAs : 'formController'
};
});
})();

Related

Directive doesn't work when I which the version of Angular to 1.0.1 to 1.2.27

The following could be run in demo here.
this is html:
<div ng-controller="MyCtrl">
<h2>Parent Scope</h2>
<input ng-model="foo"> <i>// Update to see how parent scope interacts with component scope</i>
<br><br>
<!-- attribute-foo binds to a DOM attribute which is always
a string. That is why we are wrapping it in curly braces so
that it can be interpolated.
-->
<my-component attribute-foo="{{foo}}" binding-foo="foo"
isolated-expression-foo="updateFoo(newFoo)" >
<h2>Attribute</h2>
<div>
<strong>get:</strong> {{isolatedAttributeFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedAttributeFoo">
<i>// This does not update the parent scope.</i>
</div>
<h2>Binding</h2>
<div>
<strong>get:</strong> {{isolatedBindingFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedBindingFoo">
<i>// This does update the parent scope.</i>
</div>
<h2>Expression</h2>
<div>
<input ng-model="isolatedFoo">
<button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>
<i>// And this calls a function on the parent scope.</i>
</div>
</my-component>
</div>
And this is js:
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.foo = 'Hello!';
$scope.updateFoo = function (newFoo) {
$scope.foo = newFoo;
}
}]);
This should be a good example for three kinds of scope binding in directives.However, it just doesn't work when I try to switch a higher angular version - (1.2.27). I suspect the shadow of the inherited scope within the directive, but I'm not sure of it.
This isn't going to work the way you expect. Isolated Scopes are created and provided to the Link, Compile, and Template portions of a Directive. However, the HTML within the Element itself is not actually part of the Directive. Those HTML portions are still bound to the parent $scope. If you have a tendancy to name your isolated scope objects the same, you may have just been working against the $scope unintentionally and not noticed any ill effect. If your HTML was in a Template rather than inside the Element, it would access the isolate scope.
As an example, in the HTML that is inline in the Element, you can call updateFoo(), but that would not be possible from inside a Template

Angular JS scope not updating from included template

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.

Exposing AngularJS directive property value to parent controller

I am working on an AngularJS app. I am trying to write a reusable component to use throughout my app. For the sake of demonstration, we'll just use a text box. I've created a fiddle of my attempt here. Basically, I'm trying to use a control like this:
<div ng-app="myApp">
<div ng-controller="myController">
<my-control textValue="{{controlValue}}"></my-control>
<br />
You entered: {{controlValue}}
</div>
</div>
Unfortunately, I cannot figure out how to bind a property between the scope of the controller and the scope of the directive. I'm not sure what I'm doing wrong in my fiddle. Can someone please tell me what I'm doing wrong?
Thank you!
You have created directive with isolated scope and you are trying to print value from isolate scope. It isn't right, you can write your directive like this, without isolated scope:
return {
restrict: 'EAC',
template: '<input type="text" ng-model="controlValue"></input>'
};
if you want to setup directive with isolated scope, you should isolate your scope then use $watch for do changes
You were not so far, but you need to be carefull when using {{}}
Remove braces and don't use camelNotation for text-value attribute :
<div ng-app="myApp">
<div ng-controller="myController">
<my-control text-value="controlValue"></my-control>
<br />
You entered: {{controlValue}}
</div>
</div>
Use ng-model attribute :
angular.module('ui.directives', []).directive('myControl',
function() {
return {
restrict: 'EAC',
scope: {
textValue: '='
},
template: '<input type="text" ng-model="textValue"></input>'
};
}
);

Angular directive transclude create new scope?

I am trying to create directive component for modal windows, which take care about modal behaviour such as opening, closing, taking care of zIndex etc.
Content of modal components is controlled by Controller.
So far idea is good, but when i try to have
<modal ng-controller="MyController">
Some content which will be transcluded with dynamic {{value}} from MyController
</modal>
It failed and does not render {{value}}
I have to wrap modal into controller
<div ng-controller="MyController">
<modal>
Some content which will be transcluded with dynamic {{value}} from MyController
</modal>
</div>
Is there any way, how to make first example works, or it is impossible and why angular do it that way?
There is full example with plunker at the end.
var app = angular.module('plunker', []);
app.directive("modal", function() {
return {
restrict:'E',
replace:true,
transclude:true,
scope: false,
template:'<div class="modal">Modal scope {{$id}}<div ng-transclude></div></div>',
link: function($scope) {
console.log("directive scope ", $scope.$id)
}
}
})
app.controller('DetailControl', function($scope, $location) {
console.log("controller scope ", $scope.$id)
$scope.name = 'World';
});
and this HTML
<body>
main scope {{$id}}
Controller on same element as modal<br>
<modal ng-controller="DetailControl">
<div>
content scope (transclude) {{$id}}<br>
Some content of modal window. The name is {{name || '-unknown-'}}
</div>
</modal>
Controller outside modal
<div ng-controller="DetailControl">
Controller scope {{$id}}
<modal>
<div>
content scope (transclude) {{$id}}<br>
Some content of modal window. The name is {{name || '-unknown-'}}
</div>
</modal>
</div>
<body>
here is plunker http://plnkr.co/edit/WOgZKB3e0bQUASMhFVOp?p=preview
The issue is the ngController directive creates it's own scope. When you do <modal ng-controller="MyController"> the ngController scope is a sibling to modal so modal can't see over (sideways in a sense) into that controller.
It works when ngController is a parent because you're using scope: false which causes your directive to inherit it's scope from its parent.
Rather than use a separate ngController directive you can attach a controller to your directive:
app.directive("modal", function() {
return {
controller: function($scope, $location) {
console.log("controller scope ", $scope.$id)
$scope.name = 'World';
}
}
This approach will give your directive good encapsulation as it no longer will depend on an external controller- which is good. One plus is you no longer need to coordinate multiple scopes.
If you need multiple directives to communicate you can use require to allow multiple directives to all share access to one parent directive's controller. This is the approach Angular internally takes (for instance in 'ng-switch`)
try use transclude with passing scope :
http://docs.angularjs.org/api/ng.$compile
transcludeFn -> scope
"transcludeFn - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument. This is the same as the $transclude parameter of directive controllers. function([scope], cloneLinkingFn)."

Avoiding too many Angularjs Directives

I have the following directive:
.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
});
in my template (someTemplate.html) I have the following:
<div>
<div>Some div</div>
<input type="button" value="button" />
</div>
I want to add behavior to the button and div. I can go the route of adding directives like so:
<div>
<div div-click>Some div</div>
<input type="button" value="button" button-click />
</div>
and adding more directives and binding click events via element.bind(... but is there a best practice? Should I be adding behavior in the 'myDirective' containing those elements? via jQuery or jQlite . The clickable elements inside the template are not meant to be resuable..so should I just use jQuery to find those elements and bind event listeners to them? I can see how their can be a directives explosion by constantly using the directive route, what is the best practice?
The question for me would be on what exactly the directives should be for.
It sounds to me, as if you are trying to wrap functionality that you know from other frameworks like jQuery into directives. this leads to stuff like:
var app = angular.module("module.directives", []);
app.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
link: function(scope, el) {
el.on("click", function() { console.log(42); });
}
});
While certainly possible, this is (at least for me) considered "bad" style.
The difference with Angular is, that it does not use the DOM as the "Model" part of the framework, like jQuery or Prototype do. Coming from these libraries this is something to wrap your head around, but actually, for starters, it boils down to this:
Work with the scope and let the changes to the scope be reflected in the DOM.
The reflection part is actually the short and easy one: Angular does this out of the box (i.e. "most of the time").
Reconsidering your example with the click - Angular provides excellent event handlers in the form of directives. ng-click is a very good example for this:
<div>
<div ng-click="method()">Some div</div>
<input type="button" value="button" ng-click="method2()" />
</div>
This directive takes an expression - it looks a bit like the old days, where you would bind javascript directly to elements, like this:
here
It's way different though - Angular will look for the names method and method2 on the current scope you are in. Which scope you are currently in depends on the circumstances (I heavily suggest the docs at this point)
For all of our intents and purposes, lets say, you configure a controller inside your directive from earlier:
var app = angular.module("module.directives", []);
app.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
controller: ['$scope', function(scope) {
scope.active = false;
scope.method = function() { console.log(42); };
scope.method2 = function() { scope.active = !scope.active };
}]
});
You can define this in many places, even as late as during the link phase of a directive. You can also create an extra controller in a separate module. But let's just stick with this for a moment:
In the template - when you click on your div the scope's method will be called. Nothing fancy, just console output. method2 is a little bit more interesting - it changes the active variable on the scope. And you can use this to your advantage:
<div>
<div ng-click="method()">Some div</div>
<input type="button" value="button" ng-click="method2()" />
<span ng-show="active">Active</span>
</div>
When you click on your button, the span will be turned on and of - the ng-show directive handles this for you.
This has gotten a bit longer than expected - I hope though, that this sheds some light on the "best practises" (which are quite dependent on what you actually want to accomplish).

Resources