Angular directive transclude create new scope? - angularjs

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)."

Related

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

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'
};
});
})();

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

How to access a form in a directive from the controller?

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>

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.

How to use an AngularJS controller that already exists on route change?

I have this html:
<div ng-controller="MyCtrl">
<div ng-view></div>
</div>
<script type="text/ng-template" id="/a">
// SomeHtml with Angular templates
</script>
<script type="text/ng-template" id="/b">
// SomeHtml with Angular templates
</script>
And:
angular.module('ngView', [], function($routeProvider, $locationProvider) {
$routeProvider.when('/a', {
templateUrl: '/a',
controller: MyCtrl
});
$routeProvider.when('/b', {
templateUrl: '/b',
controller: MyCtrl
});
});
The controller "MyCtrl" has some bootstrap code that is invoked when the html is first loaded, this bootstrap code sets up some state that should be used by both "/a" and "/b" template. Templates "/a" and "/b" will present the data obtained during the bootstrap to render in different ways.
I'd like to not have a controller and still be able to access MyCtrl scope from my templates.
I would remove the wrapping controller, and have my routes each have their own controller. If these controllers need shared data then I would add a dedicated object that holds these data to the controllers' dependency lists. Here is an example: https://stackoverflow.com/a/9407953/410102
Beside the Angular website says that you should point some controller you are not required to do it, and if the tag with the ng-view attribute is wrapped into another tag that has a ng-controller then the template rendered will be able to access the parent scope as usual.
Your template controller will have a parent controller (your so called wrapping controller), which it inherits. So you can execute functions and access properties from your wrapping controller.
function TemplateAController($scope) {
...
}
function WrappingController($scope) {
$scope.execute = function() {
...
}
...
}
In your template:
<a ng-click="execute()">Execute</a>

Resources