Dependency issue in Angular JS - javascript

I just created a new directive, however the console is giving me an error. I believe there is a problem with dependencies as the directive is unable to see a method within the controller.
How can i fix this?
Error message:
Error: $scope.resetMessages is not a function
Controller:
angular.module('modulename').controller('controllerName', ['$scope', '$location', 'Global', 'Company', function ($scope, $location, Global, Company) {
/* A bunch of declarations and methods... */
$scope.resetMessages = function() {
$scope.errorMessage = null;
$scope.successMessage = null;
};
}]);
Directive:
angular.module('modulename').directive('directiveName', function() {
return {
restrict: 'E',
templateUrl: 'path/to/template.html',
scope: {
'Company': '&',
'Global': '&'
},
controller: function($scope) {
/*
A bunch of methods...
*/
}
};
});

As Anthony Chu alluded to, this section creates an isolated scope:
scope: {
'Company': '&',
'Global': '&'
},
That means it does NOT prototypically inherit from the parent scope, although the parent scope is available via $parent although, as Anthony mentions, this isn't a great idea because it creates coupling between the two, which you are presumably trying to sever with an isolated scope in the first place.
You shouldn't need an isolated scope at all in this case. To get access to Company and Global (both available in the Controller via dependency injection) you can just inject them into your directive too:
angular.module('modulename').directive('directiveName',
['Global', 'Company', function (Global, Company) {
// Return directive config here
}]);
Then you can omit scope: (anything) in the directive completely, and it will be the SAME scope as the controller, or if necessary, you can do scope: true to get a new scope that DOES prototypically inherit from the parent. In either case, $scope.resetMessages() would be available.
Here is a handy cheat sheet to the different directive scope options.

Related

Angular directive: passing scope objects

I got a directive which has a model passed by an attribute:
use strict;
angular.module('ebs-front')
.directive('ebsIa', function() {
return{
restrict: 'A'.
scope: {
opened: '=ebsIaOpened',
model: '=ebsIaModel',
cb: '&ebsIaCb'
},
controller: function($scope, $uibModal){
console.log('check');
$scope.text = { text: 'test'};
$scope.$watch('opened', function(newValue) {
if(newValue === true){
var modalInstance = $uibModal.open({
controller: 'ImpactAnalyseController',
templateUrl: 'common/directive/ebs-ia-template.html'
});
}
});
}
}
});
In this directive, I need to do some operations and then open a modal window. So for so good, but the thing is, I want the $scope.model to be accessible in ImpactAnalysisController as well.
My assumption was that $scope.test and $scope.model will be available in ImpactAnalysisController automatically, but apparently a isolated scope is created which is only valid for the controller: function part.
What would be a good way to pass the model variable of the scope to the ImpactAnalysisController?! And why isn't it default behaviour in angular?
If I define my directive like below, then the removeFromFilters (in this case) IS available in the directive, so I'm kinda puzzled. Any help would be appreciated...
use strict;
angular.module('ebs-front')
.directive('ebsIa', function() {
return{
restrict: 'A'.
scope: {
opened: '=ebsIaOpened',
model: '=ebsIaModel',
cb: '&ebsIaCb'
},
controller: 'ImpactAnalysisController'
};
)};
There are several ways to share data between controllers in Angular. A couple that come to mind:
1- Use a $rootScope.broadcast('keyName', value) and listen for the value with $scope.on('keyName', function(){...} Use with care, not the best approach most of the time
2- Keep the data not in the controller but in a Service or Factory, and inject that into your controllers (preferable)
What would be a good way to pass the model variable of the scope to the ImpactAnalysisController?!
Depends on what the controller has access to and intends to do with it.
And why isn't it default behaviour in angular?
You're asking the wrong question. You chose an Isolate Scope. Why did you choose an Isolate Scope, if you wanted to inherit properties from its parent?
What may solve your problem:
If you're passing a pure model and expect to have some IO where the user is potentially altering the model I recommend reading and implementing: NgModelController
It will make the model and mechanisms to interact with it available to your directive(s) via an injectable Controller, independent of the type of scope you choose. All you have to do is require 'ngModel' according to $compile documentation.
Fixed with uibmodal's resolve functionality:
var modalInstance = $uibModal.open({
animation: $scope.animationsEnabled,
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
})
Item is passed from the parent scope to the ModalInstanceCtrl and becomes available in the controller as variable. Exactly was I was looking for!

angularjs give all html elements of a specific type their own scope

I need to do this to make use of the <dialog> tag in HTML5, I want every <dialog> on my site to have its own unique scope accessible using the controller and controllerAs syntax.
Here is what I thought would work.
Javascript
\\Dialog Controller
function dialog () {
return {
scope: {},
controller: function () {
this.test = 'Dialog Test';
},
controllerAs: 'Dialog',
bindToController: true
}
}
angular.module('app',[]).directive('dialog', dialog);
HTML
<!-- Dialog HTML Example Case -->
<body ng-app='app'>
<dialog id='test'>{{Dialog.test}}</dialog>
</body>
I would expect that when the dialog was activated Dialog.test would evaluate to Dialog Test. What happens instead is that it evaluates to an empty string. What's more is that if I add a controller to the body, the dialog has access to its scope. It is as though the isolate scope definition in my directive is completely ignored.
Plunk
Note that I have modified the plunk to use <span> instead of <dialog> due to the lack of support in most browsers.
http://plnkr.co/edit/eXtUq7BxCajOZAp8BpVe?p=preview
You are creating isolated scope, thats good thing. But after AngularJS1.2 version, they have done some breaking changes, where isolated scope will be completely isolated.
So Span directive's scope will be visible to template of that directive(Span) only.
And inner html of that directive will get only parent/current scope only instead of directive isolated scope(As Isolated Scope will be visible to template only). To print value of Span.test, you have to create template and refer that template in your directive as below:
var app = angular.module('test', []);
function mainCtrl() {
this.test = 'test';
};
function spanCtrl() {
this.test = 'Span Test';
}
function span () {
return {
scope: {},
controller: spanCtrl,
controllerAs: 'Span',
template: '{{Span.test}}'
}
}
app.controller('mainCtrl', mainCtrl);
app.directive('span', span);
You can checkout two awesome blog for more detail Component In AngularJS and Transclude In AngularJS
Contents of an element do not "see" the isolate scope of a directive. The fact that the scope is "isolate" means that it is separate from the scope of the View where both the directive and contents of its hosting element reside.
To link the contents against the internal scope, you'd need to transclude them - the transclude function allows you to link it against any scope:
function dialog () {
return {
scope: {},
controller: function () {
this.test = 'Dialog Test';
},
controllerAs: 'Dialog',
bindToController: true
transclude: true.
link: function(scope, element, attrs, ctrls, transclude){
// scope here is the isolate scope of the directive
transclude(scope, function cloneAttachFn(contentsClone){
element.append(contentsClone);
});
}
}
}
The above would work, but it's still important to understand why the "default" behavior makes sense. Consider that to a user of your directive, the directive's internal (i.e. isolate) functionality (and thus scope variables) ought to be invisible. If it wasn't, the user of your directive would need to know about "special" scope variables, like Dialog, in your case. So, someone who reads your HTML would have no idea where Dialog came from without knowing how the directive operates.
There is a mistake in original assertion: the contents of dialog directive node
<dialog id='test'>{{Dialog.test}}</dialog>
belong to parent's scope, not to directive's scope. Therefore, it interpolates Dialog.test from parent controller (or root scope if there is none).
It is possible to achieve the behaviour that was expected with something like this:
app.directive('dialog', function ($interpolate) {
return {
scope: {},
controller: function ($scope) {
this.test = 'test';
},
controllerAs: 'Dialog',
compile: function (element) {
var template = element.text();
return function (scope, element) {
element.text($interpolate(template)(scope));
}
}
};
});
But it can hardly be called a promoted way to use Angular. Let the directive handle its template.

Angular root scope and scope issues

I have noticed that in one of my controllers I am getting $rootScope and $scope injected, and they both point to the same object.
Furthermore, in all my other controllers, the $scope object is shared. So whenever I inject scope, it contains the properties/methods assigned to it in all the other controllers that have so far been instantiated.
This isn't an app that I worked on from the beginning and it's pretty massive. I haven't seen this behavior before and I don't know where to begin diagnosing it. Any ideas what is causing this?
The way that we are setting up our controllers/directives is pretty standard and it looks like this:
angular.module('myApp')
.directive('mainNav', function() {
return {
restrict: 'A',
templateUrl: 'scripts/directives/mainNav/mainNav.html',
controller: 'mainNavCtrl',
replace: true,
link: function(scope, element) {
//Do DOM-related stuff
});
}
};
})
.controller('mainNavCtrl', function($rootScope, $scope, $state) {
//Do controller stuff
});
We do also configure our app as follows:
angular.module('myApp', ['ui.router', 'kendo.directives'])
.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('app', {
url: '/',
templateUrl: 'views/app.html',
resolve: {
//Fetch stuff
}
})
; });
In response to Kursad Gulseven's comment, this is what I'm seeing in Batarang:
The scope with ID 002 gets passed in as $scope and $rootScope to the first controller. When properties are added to $scope, they show up on $rootScope. Then all the other controllers are receiving the scope with ID 00A. So properties added to $scope in those controllers are visible to all other controllers getting $scope injected.
$rootScope and $scope are not the same object.
If you have an object named anObject in root scope and it has an attribute named anAttribute; then you can access that attribute using $scope like this:
$scope.anObject.anAttribute
That's because Angular looks up parent scopes if it cannot find an object in $scope.
UPDATE:
Your mainNav directive should have an inherited child scope by adding scope: true.
When "scope" is false, which is the default setting for directives, the parent controller and directive's controller share the same scope object. Setting scope true will create a new child scope for the directive.

AngularJS : Scope not shared across directives

I have 2 directives on the same tag, <settings> and <modal>. settings provides a template, while modal creates an isolated scope:
jsfiddle
var app = angular.module('app', []);
app.directive('settings', function () {
return {
restrict: 'E',
template: '<div>SETTINGS</div>',
link: function (scope) {
console.log('settings', scope.$id);
}
};
});
app.directive('modal', function () {
return {
restrict: 'A',
scope: {},
link: function (scope) {
console.log('modal', scope.$id);
}
};
});
However, they do not end up sharing the same scope, as shown by the logs:
<settings modal="settings"></settings>
settings 002
modal 003
Why is that ?
Till version 1.2.0rc3, sibling directives were sharing the same isolated scope if created.
As of version 1.2.0, you cannot (and shouldn't) access the isolated scope from outside the contents compiled against this isolated scope. You shouldn't because you are creating a hidden relation between both directives. Better use directive's controller requirement via the require property, or share informations through injected services if singleton pattern can be applied to your use case.
It is finally deeply related to this other question about Directives with Isolated scope versions conflict
scope: {} option always creates an isolated scope for the directive, it doesn't care about other directives
app.directive('modal', function () {
return {
restrict: 'A',
scope: false,
link: function (scope) {
console.log('modal', scope.$id);
}
};
});
scope: false will not create a new isolated scope. I you want to make two directives work between them use require a controller on the directive that way you can share data between them.

Directive behavior changes when defining isolated scope

I have a directive for a javascript grid library slickgrid.
http://plnkr.co/edit/KWZ9i767ycz49hZZGswB?p=preview
What I want to do is pass the selected row back up the controller. So I want to use isolated scope (using the '=') to get two-way binding working between the controller and directive.
Everything works if I define the directive without any sort of scope declaration:
<slickgrid id="myGrid" data="names" selected-item="selectedItem"></slickgrid>
app.directive('slickgrid', function() {
return {
restrict: 'E',
replace: true,
//scope: {
// selectedItem: '='
//},
template: '<div></div>',
link: function($scope, element, attrs) {
...
var redraw = function(newScopeData) {
grid.setData(newScopeData);
grid.render();
};
$scope.$watch(attrs.data, redraw, true);
But if I uncomment the lines above (lines 19-21 in app.js) it looks like the $scope.$watch which is watching the attrs.data object is calling redraw but the attrs.data is being passed in as undefined.
My analysis could be wrong, but I'm not sure why defining the scope would cause this. Can someone explain why that might be?
.nathan.
If you define an isolate scope, then any $watch in your directive will be looking for whatever attrs.data evaluates to on your isolate scope. attrs.data evaluates to the string names, so the $watch is looking for $scope.names on your isolate scope, which doesn't exist. (Without the isolate scope, the directive uses the same scope as MainCtrl, and $scope.names exists there.)
To use an isolate scope, you'll need to define another isolate scope property to pass in names:
scope: {
selectedItem: '=',
data: '='
},
...
$scope.$watch('data', redraw, true);
The HTML can remain the same.

Categories

Resources