I'm having a bit of trouble understanding scopes and the relation between scopes in a directive and scope in a controller..
I have a directive displaying a navbar like this, in the navbar I want to display a name, that i stored using localStorage
(function (module) {
var navbar = function () {
return {
restrict: "AE",
controller: function ($scope, localStorage) {
$scope.name = localStorage.get("name");
},
templateUrl: "/app/NSviewer/templates/nav.html"
};
};
module.directive("navbar", navbar);
}(angular.module("anbud")));
Now when this page is loaded for the first time the localStorage haven't set the name value. So the navbar gets name = null. Then the controller does:
localStorage.add("name", name);
and the name is set, if I refresh the page the navbar is loaded again, this time the name is stored in localstorage and it is displayed correctly.
So I want to do something like $scope.name = 'John Smith' in the controller and then have my directive / navbar update.
Storing a value to be shared between a controller and a directive in local storage is overkill. Typically you do the following:
(function() {
'use strict';
angular
.module('app')
.directive('dashboard', dashboard);
function dashboard() {
var directive = {
restrict: 'E',
controller: 'DashboardController',
controllerAs: 'vm',
bindToController: {
msg: '#'
},
scope: {},
templateUrl: '/templates/dashboard/dashboard.html'
};
return directive;
}
angular
.module('app')
.controller('DashboardController', DashboardController);
DashboardController.$inject = [];
function DashboardController(){
var vm = this;
vm.msg = 'Hello World'
}
})();
Some things to note:
The bindToController can accept an object. This is something that could only accept a boolean pre angular 1.4. Now it can act just like the scope property and attach stuff to be used by your controller to pass data.
The use of # means that it's a 1 way data bound string. = sets up a two-way bound relationship and there's also the & property. See this post for an overview of what they all mean
Another difference between what I'm doing is that I'm using var vm = this as opposed to injecting $scope This is quite a popular approach nowadays and means you don't get riddled with scope soup. But you can think of it as a way to do the same thing essentially (that is binding stuff but it cannot listen for events so please remember that)
Good luck
Related
I have - ng-view - template create item functionality and same template containing one directive that load the saved items.
Now, when i do save create item and immediately, its not refreshing list of items (from directive).
Can anyone tell me how I would resolve this, so, after saving item, immediately directive is refreshed.
Note: directive link function is making call to $http and retrieving data and populate in directive template. And directive element is added in other html template.
html template: (which has separate controller and scope).
<div>.....code</div>
<div class="col-md-12">
<parts-list></parts-list>
</div>
directive code:
(function () {
angular.module("application")
.directive("partsList", function (partService) {
return {
templateUrl: 'partsListView.html',
restrict: 'E',
scope: {},
link: function ($scope) {
$scope.partList = [{}];
RetrieveParts = function () {
$scope.partList=partService.RetrieveParts();
};
}
};
});
})();
For starters, your ReceiveParts variable doesn't have proper closure. Also, are you calling this function? I'm not sure where this function gets executed.
link: function ($scope) {
$scope.partList = [{}];
RetrieveParts = function () {
$scope.partList=partService.RetrieveParts();
};
}
An easy trick I've learned that makes it trivial to execute some of the the directives linking function logic in sync with angularjs's digest cycle by simply wrapping the logic I need in sync with the $timeout service ($timeout is simply a setTimeout call followed by a $scope.$apply()). Doing this trick would make your code look like:
link: function ($scope) {
$scope.partList = [{}];
$scope.fetchedPartList = false;
$timeout(function() {
$scope.partList = partService.RetrieveParts();
$scope.fetchedPartList = true;
});
}
Additionally, you'll notice the boolean value I set after the partList has been set. In your HTML you can ng-if (or ng-show/hide) on this variable to only show the list once it's been properly resolved.
I hope this helps you.
Use isolated scope in directive:
return {
templateUrl: 'partsListView.html',
restrict: 'E',
scope: {partList: '='},
and in template:
<parts-list partList="list"></parts-list>
Where list is where ui will update with updated data.
See how isolated scope using basic Example
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!
I'm curious as to how I would go about unit testing what I think is an anonymous controller inside of a directive.
directive.js
app.directive('directive',
function() {
var controller = ['$scope', function($scope) {
$scope.add = function() { ... };
}];
return {
restrict: 'A',
scope: {
args: '='
},
templateUrl: '...',
controller: controller
};
}
};
Is a controller defined as such able to be unit tested? I have tried to initialize it several different ways. Currently I have it setup like this:
describe('The directive', function() {
var element,
scope,
controller;
var args = {
...
}
beforeEach(module('app'));
beforeEach(module('path/to/template.html'));
beforeEach(function() {
inject(function($compile, $rootScope, $controller) {
scope = $rootScope.$new();
scope.args = args;
element = angular.element('<div directive></div>');
template = $compile(element)(scope);
scope.$digest();
controller = element.$controller;
});
});
// assertions go here
});
I keep getting TypeError: 'undefined' is not an object (evaluating ...) errors, so I don't think I am initializing the controller correctly. I mainly want to know if something like this is unit testable without changing the directive's source code at all.
I'm not sure if what you are trying to do is possible. However, I do know that there is a much easier way and that is to make it a standard controller. (You seem to be aware of this already but it's worth pointing out.)
The logic in a controller really shouldn't be dependent on the directive anyway so by making a named controller you are separating concerns which is a good thing. You can even see this used in recommended style guides for AngularJS. Once you have the controller set up properly you shouldn't have any issues testing it. Splitting it out like that also helps in doing proper dependency injection making for simpler code and simpler tests.
I have a form made up of directives. Two parts of this form are identical in input fields, selects, and check boxes. I made a directive using isolate scope and controllerAs syntax (with bindToController=true) where the controller has a method that fires from an ngChange on the checkBox. My problem is that the controller alias I assign 'this' to in the controller is fired twice, once for each directive. The second time through that controller alias name, in my example case 'ctrlVm', is overwritten with the second scope.
A simpler directive and controller that duplicate this problem is:
function myDirective() {
var directive = {
restrict: 'AE',
priority: 100,
require: 'ngModel',
scope: {
boundObject: '=boundObj',
myArray: '='
},
templateUrl: 'myTemplate.html',
controller: controller,
controllerAs: 'ctrlVm',
bindToController: true
};
return directive;
function controller() {
ctrlVm = this;
ctrlVm.runTest = runTest;
function runTest() {
ctrlVm.myArray.push(ctrlVm.boundObject.first);
}
}
}
A full demo with the above code and html can be found at:
http://plnkr.co/edit/TdZgadsmVQiZhkQQYly1?p=preview
I am basically calling the directive twice and expecting them to result in a "one" or a "two", depending on which box you click in. But as you can see, the first controller was "overwritten" by the second and regardless of which box you check it fires the second directive's function and adds a "two" to the array regardless of which box you click.
I have done exhaustive searching all day, learning some other things along the way, but have not found more than a couple references related to this problem and both of those seemed to imply a need for either two different directives that have different controllerAs alias names or a directive that accepts a variable name for the controllerAs name. Both of those solutions seem to require two different views since my view includes the use of the controllerAs alias (which would now be different for the two directives).
So is there a way to do this, reusing the controller and with controllerAs syntax, and have the example runTest() function exist separately for the two directives? Thank you for any help.
In your controller for the directive you don't declare the variable ctrlVm.
So change:
function controller() {
ctrlVm = this;
ctrlVm.runTest = runTest;
function runTest() {
ctrlVm.myArray.push(ctrlVm.boundObject.first);
}
}
To:
function controller() {
var ctrlVm = this;
ctrlVm.runTest = runTest;
function runTest() {
ctrlVm.myArray.push(ctrlVm.boundObject.first);
}
}
http://plnkr.co/edit/Qab1lpgbrK8ZRLZiRdaQ?p=preview
I have the following directive
directiveModule.directive('typeahead', function() {
return {
restrict: 'E',
transclude: true,
scope: 'isolate',
templateUrl: 'assets/partials/common/typeahead.html' ,
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
console.log(eval($attrs.service + ".query();"));
$scope.search = function(){ console.log("Searching for:" + $scope.selectedValue) };
$scope.selectedValue = "";
$scope.providedItems = [];
}],
};
});
With the following template:
<div>
<input ng-change="search()" ng-model="selectedValue" ng-click="log()" autocomplete="off" type="text"/>
<ul class=".typeahead">
<li ng-repeat="item in providedItems">{{eval(item + "." + descriptor)}}</li>
</ul>
and the following call inside my view:
I would like to let the service attribute be evaluated at runtime when injecting to the controller in the directive. So that where it now says
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
It would say something similar to:
controller: ["$scope","$element","$attrs","$transclude", eval($attrs.service),
function ($scope,$element,$attrs,$transclude, eval($attrs.service)){
However I can't access the $attrs from the scope inside the directiveModule call.
If there is any way to access a service declared in the view that would suffice. Please help!
One solution for this, would be creating and binding the controller yourself. All you need is to inject both $injector (in order to resolve the service dynamically) and $controller (in order to resolve the controller dynamically). And in your linking function you create the controller yourself:
link: function(scope, elm, attr) {
$controller(function YourController($scope, dynamnicService) {
dynamicService.query();
}, {
$scope: scope,
dynamicService: $injector.get($attr.service)
}
)
}
There is one thing important here. I'm considering, in this example, that your service is a string in the element attribute. If it's a string inside the scope, referred by the attribute, then you have to change the approach. You should $attr.observe the attribute, and on change, you should grab the service $injector.get(...) and pass it to the controller. You ould either create a this.setService method on the controller itself, or $scope.setService method, your call. I'd rather the controller, as this is related to accessing a service. Using this second approach, you don't need to create the controller by hand, you can simple expose this method and set the data from outside.
One more info, you should NEVER TOUCH THE DOM FROM YOUR CONTROLLER. So passing $element, $attr and $transculde to the controller is probably a bad idea, no matter what.
Take a look at the docs about $controller, $injector and directive.