I'm trying to learn to build custom directives in AngularJS. Presently I am using AngularJS 1.5.8.
I am trying to create an example of a directive with an isolate scope where the controller scope is not visible, so that I can selectively expose things by adding them to 'scope: {}.' In this example, what I expect to get is 'Name: Street:' because ctrl.customer should be unavailable to the directive. Then later I would add "customer: '='" to "scope: {}" and I would get "Name:David Street:123 anywhere street". Unfortunately, I am getting "Name:David Street:123 anywhere street" from this directive as it is written.
things I have tried:
setting "bindToController: true"
removing "scope: {}" and setting "bindToController: {}" (so I add "customer: '='" to that)
setting "bindToController: false" (might as well, right?)
repeatedly hitting shift-F5 after loading the html so make sure that I just don't have the old file cached.
testScope.js:
var app = angular.module('scopeModule',[]);
app.controller('Controller',[function(){
var vm = this;
vm.customer = {
name: 'David',
street: '123 anywhere street'
};
}]);
app.directive('sharedScope', function() {
return{
scope:{},
template: 'Name:{{ctrl.customer.name}} Street:{{ctrl.customer.street}}',
controller: 'Controller',
controllerAs: 'ctrl',
bindToController: true
};
});
index2.html:
<!doctype html>
<html ng-app="scopeModule">
<body>
<shared-scope></shared-scope>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="testScope.js"></script>
</body>
</html>
Controller is the controller of sharedScope directive. It is a single entity. this in directive's controller is ctrl in directive's template, because both controller and template belong to a directive.
There's no reason to isolate directive's scope from itself.
Isolated scope is isolated from the scopes of other directives. From the reference:
{...} (an object hash): A new "isolate" scope is created for the
directive's element. The 'isolate' scope differs from normal scope in
that it does not prototypically inherit from its parent scope. This is
useful when creating reusable components, which should not
accidentally read or modify data in the parent scope.
This means that if sharedScope directive has no controller and parent directive has Controller controller, this
<div ng-controller="Controller as ctrl">
<shared-scope></shared-scope>
</div>
will result in
Name: Street:
output.
Related
I'm so confused about my situation. Let me briefly introduce my situation.
HTML structure. (It's just structure, full HTML is more than that but there is no any other controller except "pageController")
<body ng-app="app">
<div id="wrapper" ng-controller="pageController">
<div id="menu">
<a>.........</a>
</div>
<div id="mainView">
<ng-view></ng-view>
</div>
</div>
</body>
Of course, I properly set router.
var app = angular.module("app", ["ngRoute"])
.config(function($routeProvider){
$routeProvider
.when("/about", {
templateUrl: "about.html"
})
.when("/summary", {
templateUrl: "summary.html"
})
.when("/company", {
templateUrl: "company.html"
})
.when("/remote", {
templateUrl: "remote.html"
})
.when("/personal", {
templateUrl: "personal.html"
})
.when("/academic", {
templateUrl: "academic.html"
})
.otherwise({
redirectTo : "/about"
});
});
I didn't set any controller now. I put each controller as "pageController" before but I removed now because I think pageController will be automatically inherited as long as pageController is in parent element.
And I made custom directive.
app.directive('imageLoader', function(){
return{
restrict:'A',
link:function(scope, elem, attrs){
elem.bind('click', function(){
var fileName = "img/portfolioImages/" + attrs.ori;
scope.$parent.$parent.modalImgSrc = fileName;
scope.$parent.$parent.isModalOpen = true;
scope.$parent.$parent.$digest();
});
}
};
});
In injected html in ng-view, there is no any other controller, but it dynamically generate <img> tag like below
<div class="project-image-div">
<img image-loader ng-repeat="aImgSrc in remoteImageFiles[0]"
data-ori='{{aImgSrc}}' class='project-img-s'
ng-src='img/portfolioImages/{{aImgSrc.substring(0,aImgSrc.lastIndexOf(".")) + "_s" + aImgSrc.substr(aImgSrc.lastIndexOf("."), 4)}}'>
</div>
remoteImageFiles[0] is json object saved in pageController scope. And these dynamically added img tags are completely fine.
THE PROBLEM
As you can see in <img> tag, I am using custom directive image-loader. In directive code, I expected "scope" is same scope as pageController as long as there is no any other controller inside ng-view and I didn't give any option for scope in the custom directive.
But I printed scope object in console, I can access pageController scope object as parent of parent... WHY?????
scope.$parent.$parent.modalImgSrc = fileName;
scope.$parent.$parent.isModalOpen = true;
scope.$parent.$parent.$digest();
Thank you for reading my long post... please help... I am spending 2 days for this..
You can see full code in my personal web site. I am noob to angularJS. I am converting JQuery version of site to pure angularJS version to learn AngularJS.. It's in progress.
AngularJS version..(converting now)
http://bear-mj.com/MJKim
JQuery version..(all working)
http://bear-mj.com/MJKimJQuery/
You have 3 levels of scope created. Remember, both ng-repeat and ng-view create a new scope. In this area of your app - inside the directive:
scope is the ng-repeat scope
scope.$parent is the ng-view scope
scope.$parent.$parent is the pageController scope
learning angular so some time things not clear when read article on angular. here i stuck to understand what is the usage or importance of this keywords Controller and controllerAs in directive.
code taken from here http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html
app.controller('SomeController', function () {
this.foo = 'bar';
});
app.directive('someDirective', function () {
return {
restrict: 'A',
controller: 'SomeController',
controllerAs: 'ctrl',
template: '{{ctrl.foo}}'
};
});
i like to know understand the importance of this two keywords in directive and they are controller: 'SomeController', and controllerAs: 'ctrl',
please tell me if we do not use these two keyword controller: 'SomeController', and controllerAs: 'ctrl', then what would happen or what would be worse ?
please help me to understand the usage or importance of this keywords controller: 'SomeController', and controllerAs: 'ctrl', in directive. thanks
You need the controller if you plan on referencing a controller object. This is how you hook it up.
The controllerAs allows you to create a variable that you can reference the controller with in lieu of using the $scope.
Refined answer:
<html ng-app="app">
<head></head>
<body>
<script src="node_modules/angular/angular.js"></script>
<script>
var app = angular.module('app', []);
app.directive('fooDirective', function() {
return {
restrict: 'A',
controller: function($scope) {
// No 'controllerAs' is defined, so we need another way
// to expose this controller's API.
// We can use $scope instead.
$scope.foo = 'Hello from foo';
},
template: '{{foo}}'
};
});
app.directive('barDirective', function() {
return {
restrict: 'A',
controller: function() {
// We define a 'vm' variable and set it to this instance.
// Note, the name 'vm' is not important here. It's not public outside this controller.
// The fact that the 'controllerAs' is also called 'vm' is just a coincidence/convention.
// You could simply use 'this.bar' if you prefer.
var vm = this;
vm.bar = 'Hello from bar';
},
// This allows us to reference objects on the controller's instance by
// a variable called 'vm'.
controllerAs: 'vm',
// Now we can reference objects on the controller using the 'controllerAs' 'vm' variable.
template: '{{vm.bar}}'
};
});
</script>
<div foo-directive></div>
<div bar-directive></div>
</body>
</html>
One of its main advantages, especially if you're new to AngularJS, is that it ensures proper data binding between child scopes.
Just play around with this code sample and try to notice something strange:
angular
.module('myApp', [])
.controller('MainCtrl', ['$scope',
function($scope) {
$scope.truthyValue = true;
$scope.foo = 'hello';
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<p>Start by writing something in the first input. Then write something in the second one. Good job, you've broke AngularJS!</p>
1.
<input type="text" ng-model="foo">
<div ng-if="truthyValue">
2.
<input type="text" ng-model="foo">
</div>
<div>$scope.foo: {{ foo }}</div>
</div>
The reason behind it is that ngIf creates a child scope which inherits from the parent scope. You're basically changing the value inside ngIf's scope which doesn't affect the value from its parent scope.
Finally, I consider controllerAs syntax an important AngularJS best practice. If you get accustomed to it early in your learning process, you'd be avoiding a lot of head-scratching wondering why your code doesn't work, especially when everything seems in order.
You don't need to use both controller and controllerAs. You can use the shorthand:
controller: 'SomeController as ctrl'
The relationship is that a new instance of the controller is created and exposed to the template using the instance handle you provide as ctrl.
Where this comes in handy is if you are using nested controllers -- or using multiple instances of a controller in a view.
UPDATE TO ANSWER COMMENTS
You do not need to use controllers with AngularJS directives. Infact as of AngularJS 1.5 you should probably only use controllers when creating components rather than directives.
Directives and Components are conceptually similar. Up until AngularJS they all components would be defined as a directive.
In many ways a directive interacts with an element (like ng-href) or events (like ng-click).
The simplest way to differentiate Components and Directives is a Component will have a template.
Can't I just create a component using the directive link method?
You can, but I wouldn't recommend it unless you have a good reason. Using controllers allows you to use object oriented classes or prototypes to define the action behaviors with the template and user.
As well these controllers are extremely more easy to unit test than the directive link functions.
Check out this plunkr code
Here is my simple Directive code:
angular.module('app', [])
.directive('someDirective', function () {
return {
scope: {},
controller: function ($scope) {
this.name = 'Pascal';
$scope.color = 'blue';
},
controllerAs: 'ctrl',
template: '<div>name: {{ctrl.name}} and Color: {{color}}</div>'
};
});
And The HTML
<body ng-app="app">
<some-directive />
</body>
So, as you can see, if you need to access some variable which were defined against this keyword in the controller, you have to use controllerAs. But if it was defined against $scope object you can just access it with its name.
For example, you can get the variable color just by using {{color}} as it was defined against $scope but you have to use {{ctrl.name}} as "name" was defined against this.
I don't think there really is much difference, as this answer says,
Some people don't like the $scope syntax (don't ask me why). They say
that they could just use this
Also from their own website you can read the about the motivation behind this design choice,
Using controller as makes it obvious which controller you are
accessing in the template when multiple controllers apply to an
element
Hope it helps.
I have created a test located on plunker at this address: Full example on plunker
Here is the child controller which works:
child.directive('child', function(){
return{
restrict:'E',
templateUrl: 'child.html',
link: function(){},
controller:['$scope', function($scope){
this.inherited = $scope.test;
this.local = "child";
}],
controllerAs:'child'
};
});
I am expecting the controller located in child.js to be a child controller of the controller in script.js. This would mean that if I want to access a variable added to the scope of parent controller from the child controller, I would need to access it using $scope.$parent. Can someone explain why these are the same scope?
By default, directives do not create a new scope. So in your example $scope in the directive will be the same value as the scope that the controller is contained in. The scope is not inherited - they are the same scope.
So for this markup:
<div ng-controller="SomeController">
<child></child>
</div>
scope in SomeController will be the exact same object as scope in your directive. If you did $scope.$parent in the directive, you would actually be accessing the parent scope of SomeController. This is equivalent to
scope: false //default for directives
If you add:
scope: true
to your directive definition, then the directive will create a new scope that prototypically inherits from the parent scope. In this situation,
<div ng-controller="SomeController">
<child></child>
</div>
Now $scope.$parent in the directive will be the $scope of SomeController. Also, since it prototypically inherits, the directive will have access to methods and properties defined on $scope.$parent, with the caveats and complications that come with prototypically inheritance in javascript.
If you use:
scope: {}
Now $scope.$parent in the directive will still be the $scope of SomeController, but the new scope will not prototypically inherit from the parent, so you will not have access to methods and properties defined on $scope.$parent.
The child scope inherits from the parent scope, so anything you define in the parent will appear in the child scope. Keep in mind that this is a new scope, so modifications won't affect the parent directly without using the $scope.$parent scope. Take a look at this question and the accepted answer: AngularJS access parent scope from child controller
In directives the parent scope is inherited by default:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
having same nested directives:
<div mydir="a">
<div mydir="b">
</div>
</div>
if mydir requires ?^mydir it always get itself's controller.
test_plunk
is it possible to access parent's controller?
According the angular documentation on jqLite, you can call controller() to retrieve the controller for any given element:
controller(name) - retrieves the controller of the current element or its parent.
By default retrieves controller associated with the ngController directive.
If name is provided as camelCase directive name, then the controller for this
directive will be retrieved (e.g. 'ngModel').
Within your link function, you can retrieve the parent controller by calling controller() on the parent element and passing in the name of the directive:
var parentCtrl = iElement.parent().controller('mydir');
$scope.$parent.a;
shall give you a
$scope is the scope injected by angular
$parent is a keyword reference to the parent scope from any scope in angular.
Yes this is JavaScript, and as was asked in the question, AngularJS powered Javascript
I have put up my code at jsbin: http://jsbin.com/fewom/1/edit
If any one can guide me, what am I doing wrong there. I have specified scope to be isolated inside myDirective with scope: {} but still when i write myProperty inside my directive in html I am able to read myProperty.
I was using AngularJS https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js
When i changed my library to it started to work,
https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js
then I have tried,
Angular JS version 1.2.16
https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js
and
Angular JS version 1.3.0-beta.5
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.5/angular.min.js
and the problem for isolated scoping shows up again.
You don't have a controller, so nothing ties your directive to your html.
Change your js to this (I just added a blank controller):
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {}
};
})
.controller('ctrl', function (){
});
and in your html, change to this (referenced the controller):
<html ng-app="myApp" ng-controller='ctrl'>