I am trying to identify why the scope is being shared from the same directive. I have this:
<locator-service
component="redirect"
url="/api/getAllLas1"
redirect="true"></locator-service>
<locator-service
component="signup"
url="/api/getAllLas2"
redirect="false"></locator-service>
Which is basically a directive to handle locations (it works perfectly)
here is the directive for this class
var app = angular.module("frog").directive('locatorService', LocatorService);
function LocatorService() {
return {
restrict: 'E',
templateUrl: '/scripts/frog/location-service.html',
controller: 'locatorController',
controllerAs: 'locatorController',
bindToController: true,
scope: {
component: "#",
url: "#",
redirect: "#"
}
}
}
I have the "scope" declared, I read on a few threads that this would create an isolated scope.
here is the controller class (minimised)
angular.module("frog").controller('locatorController', LocatorController);
function LocatorController($scope, $rootScope, $http, $interval, $document) {
var self = this;
$scope.$watch('url', function () {
if (self.url === undefined) return;
console.log(self.url);
})
}
I attached the debugger to 'console.log(self.url);' and it printed out the following:
/api/getAllLas2
/api/getAllLas2
Could someone help me identify why this is happening?
I have an example on my Github that works perfectly fine for directives not sharing the same scope, but it uses $scope (which I have tried to implement but never even worked slightly)
https://github.com/zackdavidson/tappers-web/tree/master/public/scripts/tappers/app/components/transaction
Related
I've managed to get cross-domain HTML templates working by applying a url to the rootScope which I can access from controllers and other HTML files, but the problem arises when it comes to accessing the template from a directive. Here's my directive code:
angular.module("bertha")
.directive("bthToggleHeader", function() {
var controller = [
"$scope", "$rootScope", "_", function ($scope, $rootScope, _) {
if ($scope.tglOpen)
$scope.tglShowSection = true;
$scope.toggleShowSection = function() {
$scope.tglShowSection = !$scope.tglShowSection;
};
}
];
return {
restrict: "E",
scope: {
tglHeading: "#",
tglShowSection: "=",
tglOpen: "=?"
},
transclude: true,
controller: controller,
templateUrl: $rootScope.cdnUrl + "/html/directives/bthToggleHeader.html"
};
});
When attempting this I get: ReferenceError: $rootScope is not defined. Is there something blatantly obvious that I'm doing wrong here?
In a work project we tried using the link function but that didn't play nicely with being minified at all, hence the controller approach.
Any help would be greatly appreciated! Thanks.
You can use angular's dependency injection at directive level - so just place $rootScope there. See my example below:
angular
.module('bertha')
.directive('bthToggleHeader', ['$rootScope', function($rootScope) {
var controller = [
'$scope', '_',
function($scope, _) {
if ($scope.tglOpen)
$scope.tglShowSection = true;
$scope.toggleShowSection = function() {
$scope.tglShowSection = !$scope.tglShowSection;
};
}
];
return {
restrict: 'E',
scope: {
tglHeading: '#',
tglShowSection: '=',
tglOpen: '=?'
},
transclude: true,
controller: controller,
templateUrl: $rootScope.cdnUrl + '/html/directives/bthToggleHeader.html'
};
}]);
As Joe Clay said, $rootScope exists only in the controller function - that's why it's undefined outside of it.
$rootScope has fallen out of scope by the time you try to access it in templateUrl - you can't use a function parameter outside of the function (or at least, not without saving a reference somewhere)!
var controller = [
"$scope", "$rootScope", "_", function ($scope, $rootScope, _) {
if ($scope.tglOpen)
$scope.tglShowSection = true;
$scope.toggleShowSection = function() {
$scope.tglShowSection = !$scope.tglShowSection;
};
} // FUNCTION ENDS HERE - past this point $rootScope is undefined!
];
EDIT: While this answer gives some context on why your current code doesn't work, I wasn't 100% sure of the best way to solve the problem - Cosmin Ababei's answer seems like an effective solution, and I'd recommend you follow his advice!
I have two directives: directiveA and directiveB, hanging from the same module in my AngularJS application.
They are called at the same level in the same HTML template, so we could say they are brothers.
<directive-a>
<directive-b>
Both of them have an own method hanging from its scope, like this:
$scope.clickOkey = function () {
... whatever
};
They both have a 'clickOkey' method, but their behaviours are different.
My problem comes out when I try to call the 'clickOkey' of directiveA from directiveA's template. It executes the 'clickOkey' from directiveB.
Inside directiveA's own template:
<label ng-click="clickOkey()">Okey</label>
They are placed at same level so there it shouldn't be way for them to share their $scope or misunderstanding methods.
Also, is important to say that if I change the method's name to 'clickOkeyA', for example, it takes the right method, so the template can access to its scope without problems.
What am I missing?
Thanks for your help!
Edit:
Both directives are isolated and have a controller, and inside each one of them is defined a 'clickOkey' method. There are two methods with the same name.
Both directives are like this:
angular.module('myModule').directive('directiveA', function () {
return {
restrict: 'AE',
templateUrl: '/whatever.html',
controller: function ($scope, $http, $rootScope) {
$scope.clickOkey = function () {
... whatever
};
}
}
});
You should add an isolated scope to your directives :
https://docs.angularjs.org/guide/directive
For your directive it would be :
angular.module('myModule').directive('directiveA', function () {
return {
restrict: 'AE',
templateUrl: '/whatever.html',
scope : {},
controller: function ($scope, $http, $rootScope) {
$scope.clickOkey = function () {
... whatever
};
}
}
});
So I have this directive that has its own scope but I want to access to a function inside its parent controller. I can do this if the parent controller exposes the function with a $scope.getElementsList(), although I'm trying to avoid the use of $scope and I have the function exposed with self.getElementsList() and the directive cannot reach it.
Directive:
angular.module('myApp').directive('accountBalance', function() {
return {
scope: {
elementId: '=elementid'
},
transclude: true,
restrict: 'E',
templateUrl: '../views_directives/account-balance.html',
controller: function($scope) {
$scope.removeElement = function(){
//this where I want to access the parent function
console.log($scope.$parent.getElementsList());
console.log("ALSO I WANT TO ACCESS THIS DIRECTIVE elementId WITHOUT USING $scope", $scope.elementId);
}
}
};
});
ParentController:
angular.module('myApp').controller('AppDesignCtrl', function ($scope) {
var self = this;
self.elementsList = [];
self.getElementsList = function(){
return self.elementsList;
}
});
I also want to know what is the best way to access, inside the directive controller, the data passed to the directive's $scope.
scope: {
elementId: '=elementid'
},
UPDATE
<div>
<i class="fa fa-arrows element-drag"></i>
<i class="fa fa-trash-o element-remove" ng-click="removeElement()"></i>
</div>
And what about calling functions from the directive template inside the controller of the directive? Do I need to expose them with something like $scope.removeElement()? How do I use this.removeElement() and be able to access it from the template?
Sorry about the long question. I'm trying to set the best practices to my new project since I've been away from angular for a year+.
Thanks in advance
(Going from bottom to top...)
To call functions in the controller without using the scope in Angular >= 1.2, use the controllerAs syntax:
<div ng-controller="AppDesignCtrl as appDesignCtrl">
...
<i class="fa fa-trash-o element-remove" ng-click="appDesignCtrl.removeElement()"></i>
</div>
And removeElement() must be a method of the controller:
angular.module('myApp').controller('AppDesignCtrl', function ($scope) {
...
this.removeElement = function() {
...
};
});
To access the scope data from the controller in Angular >= 1.3, use the new bindToController: true configuration (this is especially useful when combined with the new controllerAs syntax):
angular.module('myApp').directive('accountBalance', function() {
return {
...
scope: {
elementId: '=elementid'
},
controller: function() {
// now elementId is a member of the controller:
console.log(this.elementId);
}
};
});
Having said these, the answer to how you can call getElementsList from the directive would be:
angular.module('myApp').directive('accountBalance', function() {
return {
...
scope: {
elementId: '=elementid',
getElementList: '&'
},
controller: function() {
...
// invoking the expression that was passed to us
var theElements = this.getElementList();
}
};
});
The correct expression should be passed as:
<div ng-controller="AppDesignCtrl as appDesignCtrl">
<account-balance element-id="xxx"
get-elements-list="appDesignCtrl.getElementsList()"></account-balance>
</div>
It is generally not recommended, because directives are meant to be self-contained. It isn't critical if you don't plan to reuse the directive. And wise usage of isolate scope can solve this.
angular.module('myApp').directive('accountBalance', function() {
return {
scope: {
outerScope: '#'
elementId: '='
},
transclude: true,
restrict: 'E',
templateUrl: '../views_directives/account-balance.html',
controller: function($scope) {
console.log("we can use anything from other controller", $scope.outerScope.elementsList)
$scope.elementId = "and share data with any other scope";
}
};
});
Controller is defined as ng-controller="AppDesignCtrl as appDesign", and directive usage is
<account-balance element-id="sharedParentScopeVar" outer-scope="appDesign">
So there won't be any problem if the directive should be moved to other controller.
I guess 'best practice' may be to set up a service that embraces the data and is used by both app controller and directive, so directive controller operates on data items and not DOM elements.
And what about calling functions from the directive template inside
the controller of the directive? Do I need to expose them with
something like $scope.removeElement()?
You surely don't. If there's a need to use functions from outside, you're doing something wrong. Send a message to respective element to run the function if it is DOM-related. Or put the function into the service if it is data-related.
I have a set of tabs which all have a directive in them:
<div class="col-md-9 maincols" id="formEditor">
<tabset>
<tab heading="New subscriber" select="isSelected('newSub')">
<new-subscriber></new-subscriber>
</tab>
<tab heading="Existing subscriber" select="isSelected('existingSub')">
<existing-subscriber></existing-subscriber>
</tab>
<tab heading="Landing page" select="isSelected('landing')">
<landing-page></landing-page>
</tab>
</tabset>
</div>
All these 3 directives have been defined similarly like this:
angular.module('myApp')
.directive('newSubscriber', function () {
return {
restrict: 'E',
scope: {},
replace: true,
templateUrl: 'scripts/newsubscriber/newsubscriber.html',
controller: 'newsubscriberCtrl'
};
}); //... and so on
I am (probably wrongly) under the impression that because I have set scope: {} for all the directives, they should now have completely isolated scopes and leave each other alone.
But that is not the case and bindings from the first directive's controller manage to stop values in the second or third controller from being binded
for example in newsubscriberCtrl I have:
app.controller('newsubscriberCtrl', ["$scope", "$routeParams", "UserMessages", "FormProvider", function ($scope, $routeParams, UserMessages, FormProvider) {
$scope.formId = $routeParams.formId;
var newSubscriberForm = new FormProvider.Form($scope);
angular.extend($scope, newSubscriberForm);
$scope.title = UserMessages.exampleText.genericPageTitle;
$scope.description = UserMessages.exampleText.genericPageDescription;
$scope.validationMessages = {
contactNotSaved: UserMessages.validationMessages.contactNotSaved,
contactCreatedOk: UserMessages.validationMessages.contactCreatedOk,
contactNotCreated: UserMessages.validationMessages.contactNotCreated,
requiredField: UserMessages.validationMessages.requiredField,
passwordMismatch: UserMessages.validationMessages.passwordMismatch,
isOpen: false
}
}]);
which is overriding the similar object in existingSubscriber controller:
app.controller('existingsubscriberCtrl', ["$scope", "$routeParams", "UserMessages", "FormProvider", function ($scope, $routeParams, UserMessages, FormProvider) {
$scope.formId = $routeParams.formId;
var existingSubscriberForm = new FormProvider.Form($scope);
angular.extend($scope, existingSubscriberForm);
$scope.title = UserMessages.exampleText.genericPageTitle;
$scope.description = UserMessages.exampleText.genericPageDescription;
$scope.validationMessages = {
contactNotSaved: UserMessages.validationMessages.contactNotSaved,
contactSavedOk: UserMessages.validationMessages.contactSavedOk,
requiredField: UserMessages.validationMessages.requiredField,
passwordMismatch: UserMessages.validationMessages.passwordMismatch,
isOpen: false
}
}]);
So in the view of both directives <pre>{{validationMessages | json }}</pre> the validationMessages object has the props of the first controller.
Why is this happening? Am I missing to understand a concept here? How Can I isolate these controllers from each other and comfortably have similar props in the controllers without them affecting each other?
Side note: I strongly want to avoid having to prefix everything on all scopes with their controller name, e.g $scope.newSubscriber.validationMessages and so on... as that would defeat the whole point pretty much as I will effectively one big controller for the whole tab section and directives would also be pointless.
Angular is on v.1.3.0-beta.11
angular-ui-bootstrap is on v.0.10.0
You have reuse the same controller newsubscriberCtrl in /app/scripts/formbanner/formbanner.js:
.directive('formBanner', function () {
return {
restrict: 'E',
replace: 'true',
templateUrl: 'scripts/formbanner/formbanner.html',
controller: 'newsubscriberCtrl'
};
});
The existingSubscriber directive have the formBanner as a child directive, plus the formBanner directive doesn't have an isolated scope.
Therefore, the $scope that get injected into the newsubscriberCtrl of formBanner is the same as the scope of the existingSubscriber!!
I've tried removing the controller property in the formBanner directive and I saw it works as expected.
Have you tried this?
angular.module('myApp')
.directive('newSubscriber', function () {
return {
restrict: 'E',
scope: { someValue = "&validationMessages" },
replace: true,
templateUrl: 'scripts/newsubscriber/newsubscriber.html',
controller: 'newsubscriberCtrl',
link: function (scope, iElm, iAttrs) {
var x = scope.someValue();
// x = your messages
}
};
});
In your controller
$scope.someValue
EDIT Disclaimer: this is sort of from memory. When I was facing something similar I felt this to be rather enlightning:
http://umur.io/angularjs-directives-using-isolated-scope-with-attributes/
Not sure if I am misunderstanding how directives are created here. Say for example I have a controller such as:
angular.module('myApp.controllers').controller('MyController', ['$scope', 'MyService', function($scope, MyService) {
$scope.restangularService = MyService;
}
I then have a directive such as:
angular.module('myApp.directives').directive('myGrid', function() {
return {
restrict: 'A',
templateUrl: 'some/path/here.html',
scope: {
restangularService: '&'
},
controller: ['$scope', function($scope) {
//access $scope.restangularService to run some queries
}
};
});
I then use my directive as such:
<div data-my-grid data-restangular-service='restangularService'></div>
I would expect that in my directive I could access $scope.restangularService and make calls however it's not being populated correctly. Am I doing this totally wrong? Any input? I have a feeling I need to be using the ngModel directive somehow.
The "&" prefix of an isolate scope value in a directive provides "one-way binding" which makes available a getter function in the directive's scope.
Any changes you make to the object will not make their way back up to the parent controller of the directive (it is "read-only"). So you can't access your 'restangularService' variable as you would in the controller's scope, without calling the getter function:
angular.module('myApp.directives', []).directive('myGrid', function() {
return {
restrict: 'A',
templateUrl: 'some/path/here.html',
scope: {
restangularService: '&'
},
controller: ['$scope', function($scope) {
console.log($scope.restangularService()); // outputs service value
}]
};
})
Alternatively, you could use "=", which would allow you directly access the scope object you pass in:
angular.module('myApp.directives', []).directive('myGrid', function() {
return {
restrict: 'A',
templateUrl: 'some/path/here.html',
scope: {
restangularService: '='
},
controller: ['$scope', function($scope) {
console.log($scope.restangularService); //outputs service value
}]
};
})
Plunk demonstrating both types