Using object in multiple nested angularjs directives - javascript

I have a list of person objects. I have a directive for displaying some read only data about the person and nested inside it a directive that acts as the toolbar for actions on the person (delete, friend etc). When you click on the first directive a second nested directive shows up to for editing the person.
In the simple example in plnker this works fine, but in actual life this gets really flimsy with fields not updating, or updating infinitely (editor has quite a few ngRepeats making things even weirder) etc.
It seems awkward that I have 3 isolated scopes and pass the the same object to all three, but on the other hand I need a lot of properties/methods of that object in all 3 directives so it makes sense to pass the whole object. Is there a better way of doing this?
app.directive('personCard', [function () {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'personcard.tpl.html',
scope: {
person: '='
}
};
return directive;
function link(scope, element, attrs) {
scope.isOpen = false;
scope.person.close = function(){
scope.isOpen = false;
}
scope.person.edit = function (){
scope.isOpen = true;
}
}
}]);
app.directive('personToolbar', [
function () {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'personcardtoolbar.tpl.html',
scope: {
person: '=',
close: '&',
edit: '&'
}
};
return directive;
function link(scope, element, attrs) {
}
}]);
app.directive('personEditor', [
function () {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'personeditor.tpl.html',
scope: {
person: '='
}
};
return directive;
function link(scope, element, attrs) {
}
}]);

Related

How can i update a directive $scope from other directive contoller in Angular.js?

I have a diretive with a list of events loading from my service service:
.directive('appointments', [function () {
return {
restrict: 'CE',
scope: {
ngTemplate: '=',
},
controller: ['$scope','calendarService', function ($scope, calendarService) {
var vm = this;
vm.events = calendarService.getEvents();
}],
controllerAS:'vm',
link: function (scope, elem, attrs) {
scope.getTemplateUrl = function () {
if (angular.isDefined(scope.ngTemplate))
return scope.ngTemplate;
else
return "/list.directive.html";
}
},
template: '<div ng-include="getTemplateUrl()"></div>'
}
}])
Now in another directive i am updating this list, how can i update the list in the first controller?
.directive('appointmentsUpdate', [function () {
return {
restrict: 'CE',
scope: {
ngEventId: '=',
},
controller: ['$scope','calendarService', function ($scope, calendarService) {
var vm = this;
vm.update = calendarService.updateEvent(scope.ngEventId).then(function(res){
// Here is where the newly created item, should be added to the List (vm.events) from first directive
)
});
}],
controllerAS:'vm',
link: function (scope, elem, attrs) {
scope.getTemplateUrl = function () {
if (angular.isDefined(scope.ngTemplate))
return scope.ngTemplate;
else
return "/list.directive.html";
}
},
template: '<div ng-include="getTemplateUrl()"></div>'
}
}])
you can use angular broadcast service for this:
in first directive use this:
$rootScope.$broadcast('greeting', data_needs_to_be_send);
in other directive listen the event to update its scope:
$scope.$on('greeting', listenGreeting)
function listenGreeting($event, message){
alert(['Message received',message].join(' : '));
}
We use require property to make communication between directives.
Something like this
return {
restrict: 'AE',
require: '^ParentDirective or ^SameLevelDirective'
}
Here is the clear explanation of Driectives That Communicate by ToddMotto
Services are singletons, so if you update the list from one place (with your calendarService.updateEvent()), then if you retrieve the data from the service in the other directive, it should be the updated list.
You could use a watch to check when the list is updated:
$scope.$watch(() => calendarService.getEvents(), (newValue, oldValue) => {
// update your scope with the new list
}, true);

Sharing Data between Child Directives

I have three directives defined:
Parent
This should share variables between the other two directives - Child One and Child Two.
Child One
This contains an input field representing a search term. Whenever this changes I use a link function to update the variable stored in the parent controller.
In real world use I will then carry out a search based on this term and update the array. But for simplicity in this example, I just want to create a new array with length 1, which I want to be populated with the search term as it's value.
Child Two
This should show the resultant array.
For some reason this does not work, if I set the length of the Array to 0 and push the value I will see the view update in Child Two (I have commented out the code which achieves this), but I want to understand why setting the array value doesn't work.
I understand that a service would be suitable here, but this directive may be re-used multiple times on the same page so I don't want the scope to clash between each item on the page.
Why doesn't the view get updated with the code I currently use?
var app = angular
.module('SampleApplication', [])
.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: "<div ng-transclude></div>",
controller: function($scope) {
this.searchTerm = "";
this.arrayContainingSearchTerm = [{value: ''}];
this.updateSearchTerm = function(searchTerm) {
this.searchTerm = searchTerm;
//When this array is assigned - it doesn't get updated in the view
this.arrayContainingSearchTerm = [{value: searchTerm}];
//This will update the view.
//this.arrayContainingSearchTerm.length = 0;
//this.arrayContainingSearchTerm.push([{value: searchTerm}]);
};
}
}
})
.directive('childOne', function() {
return {
restrict: 'E',
require: '^^parent',
template: "<div><h1>Child One</h1><input ng-model='searchTerm'></input></div>",
link: function(scope, element, attrs, parentController) {
scope.$watch('searchTerm', function(newValue, oldValue) {
parentController.updateSearchTerm(newValue);
});
}
}
})
.directive('childTwo', function() {
return {
restrict: 'E',
require: '^^parent',
template: "<div><h1>Child Two</h1><h2>Value below should be: {{searchTerm}}</h2><h2>{{arrayContainingSearchTerm}}</h2></div>",
link: function(scope, element, attrs, parentController) {
scope.searchTerm = parentController.searchTerm;
scope.arrayContainingSearchTerm = parentController.arrayContainingSearchTerm;
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="SampleApplication">
<parent>
<child-one></child-one>
<child-two></child-two>
</parent>
</div>
Children scope automatically inherit the parent's scope and can specifically access the parent's scope with $parent / requiring the parent controller but take note that siblings cannot [easily] access each others scope. So, the solution is to update the parent scope and reflect the parent scope changes back to the targeted child.
You don't need to update the child scope since the child scope is automatically inherit from parent scope. Also rather than watch the searchTerm ngModel, just use attrs.ngModel in the childOne directive.
var app = angular
.module('SampleApplication', [])
.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: "<div ng-transclude></div>",
controller: function($scope) {
this.searchTerm = "";
this.arrayContainingSearchTerm = [{value: ''}];
this.updateSearchTerm = function(searchTerm) {
this.searchTerm = searchTerm;
//When this array is assigned - it doesn't get updated in the view
this.arrayContainingSearchTerm = [{value: searchTerm}];
//This will update the view.
//this.arrayContainingSearchTerm.length = 0;
//this.arrayContainingSearchTerm.push([{value: searchTerm}]);
};
}
}
})
.directive('childOne', function() {
return {
restrict: 'E',
require: '^^parent',
template: "<div><h1>Child One</h1><input ng-model='searchTerm'></input></div>",
link: function(scope, element, attrs, parentController) {
// Just use attrs.ngModel
parentController.updateSearchTerm(attrs.ngModel);
}
}
})
.directive('childTwo', function() {
return {
restrict: 'E',
require: '^^parent',
template: "<div><h1>Child Two</h1><h2>Value below should be: {{searchTerm}}</h2><h2>{{arrayContainingSearchTerm}}</h2></div>",
link: function(scope, element, attrs, parentController) {
// Comment/remove this since the scope is automatically inherit from parent scope
//scope.arrayContainingSearchTerm = parentController.arrayContainingSearchTerm;
//scope.searchTerm = parentController.searchTerm;
// scope.arrayContainingSearchTerm = parentController.arrayContainingSearchTerm;
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="SampleApplication">
<parent>
<child-one></child-one>
<child-two></child-two>
</parent>
</div>
Your second directive is not aware of changes you're making afterwards - you need to $watch them:
var app = angular
.module('SampleApplication', [])
.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: "<div ng-transclude></div>",
controller: function($scope) {
this.searchTerm = "";
this.arrayContainingSearchTerm = [{value: ''}];
this.updateSearchTerm = function(searchTerm) {
this.searchTerm = searchTerm;
//When this array is assigned - it doesn't get updated in the view
this.arrayContainingSearchTerm = [{value: searchTerm}];
//This will update the view.
//this.arrayContainingSearchTerm.length = 0;
//this.arrayContainingSearchTerm.push([{value: searchTerm}]);
};
}
}
})
.directive('childOne', function() {
return {
restrict: 'E',
require: '^^parent',
template: "<div><h1>Child One</h1><input ng-model='searchTerm'></input></div>",
link: function(scope, element, attrs, parentController) {
scope.$watch('searchTerm', function(newValue, oldValue) {
parentController.updateSearchTerm(newValue);
});
}
}
})
.directive('childTwo', function() {
return {
restrict: 'E',
require: '^^parent',
template: "<div><h1>Child Two</h1><h2>Value below should be: {{searchTerm}}</h2><h2>{{arrayContainingSearchTerm}}</h2></div>",
link: function(scope, element, attrs, parentController) {
scope.arrayContainingSearchTerm = parentController.arrayContainingSearchTerm;
scope.searchTerm = parentController.searchTerm;
scope.$watch(function() {
scope.arrayContainingSearchTerm = parentController.arrayContainingSearchTerm;
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="SampleApplication">
<parent>
<child-one></child-one>
<child-two></child-two>
</parent>
</div>

How to access a require'd controller from another controller?

I have an Angular 1.3 module that looks something like this (directive that requires the presence of a parent directive, using controllerAs):
angular.module('fooModule', [])
.controller('FooController', function ($scope) {
this.doSomething = function () {
// Accessing parentDirectiveCtrl via $scope
$scope.parentDirectiveCtrl();
};
})
.directive('fooDirective', function () {
return {
// Passing in parentDirectiveCtrl into $scope here
link: function link(scope, element, attrs, parentDirectiveCtrl) {
scope.parentDirectiveCtrl = parentDirectiveCtrl;
},
controller: 'FooController',
controllerAs: 'controller',
bindToController: true,
require: '^parentDirective'
};
});
Here I'm just using $scope to pass through parentDirectiveCtrl, which seems a little clunky.
Is there another way to access the require-ed controller from the directive's controller without the linking function?
You must use the link function to acquire the require-ed controllers, but you don't need to use the scope to pass the reference of the controller to your own. Instead, pass it directly to your own controller:
.directive('fooDirective', function () {
return {
require: ["fooDirective", "^parentDirective"],
link: function link(scope, element, attrs, ctrls) {
var me = ctrls[0],
parent = ctrls[1];
me.parent = parent;
},
controller: function(){...},
};
});
Be careful, though, since the controller runs prior to link, so within the controller this.parent is undefined, until after the link function runs. If you need to know exactly when that happens, you can always use a controller function to pass the parentDirective controller to:
link: function link(scope, element, attrs, ctrls) {
//...
me.registerParent(parent);
},
controller: function(){
this.registerParent = function(parent){
//...
}
}
There is a way to avoid using $scope to access parent controller, but you have to use link function.
Angular's documentation says:
Require
Require another directive and inject its controller as the fourth
argument to the linking function...
Option 1
Since controllerAs creates namespace in scope of your controller, you can access this namespace inside your link function and put required controller directly on controller of childDirective instead of using $scope. Then the code will look like this.
angular.module('app', []).
controller('parentController', function() {
this.doSomething = function() {
alert('parent');
};
}).
controller('childController', function() {
this.click = function() {
this.parentDirectiveCtrl.doSomething();
}
}).
directive('parentDirective', function() {
return {
controller: 'parentController'
}
}).
directive('childDirective', function() {
return {
template: '<button ng-click="controller.click()">Click me</button>',
link: function link(scope, element, attrs, parentDirectiveCtrl) {
scope.controller.parentDirectiveCtrl = parentDirectiveCtrl;
},
controller: 'childController',
controllerAs: 'controller',
bindToController: true,
require: '^parentDirective'
}
});
Plunker:
http://plnkr.co/edit/YwakJATaeuvUV2RBDTGr?p=preview
Option 2
I usually don't use controllers in my directives at all and share functionality via services. If you don't need to mess with isolated scopes of parent and child directives, simply inject the same service to both of them and put all functionality to service.
angular.module('app', []).
service('srv', function() {
this.value = '';
this.doSomething = function(source) {
this.value = source;
}
}).
directive('parentDirective', ['srv', function(srv) {
return {
template: '<div>' +
'<span ng-click="srv.doSomething(\'parent\')">Parent {{srv.value}}</span>' +
'<span ng-transclude></span>' +
'</div>',
transclude: true,
link: function(scope) { scope.srv = srv; }
};
}]).
directive('childDirective', ['srv', function(srv) {
return {
template: '<button ng-click="srv.doSomething(\'child\')">Click me</button>',
link: function link(scope) { scope.srv = srv; }
}
}]);
Plunker
http://plnkr.co/edit/R4zrXz2DBzyOuhugRU5U?p=preview
Good question! Angular lets you pass "parent" controller. You already have it as a parameter on your link function. It is the fourth parameter. I named it ctrl for simplicity. You do not need the scope.parentDirectiveCtrl=parentDirectiveCtrl line that you have.
.directive('fooDirective', function () {
return {
// Passing in parentDirectiveCtrl into $scope here
link: function link(scope, element, attrs, ctrl) {
// What you had here is not required.
},
controller: 'FooController',
controllerAs: 'controller',
bindToController: true,
require: '^parentDirective'};});
Now on your parent controller you have
this.doSomething=function().
You can access this doSomething as
ctrl.doSomething().

How to get isolated scope of a directive with ngrepeat and two way bind?

How we can get particular isolated scope of the directive while calling link function from controller(parent)?
I am having a directive and repeating it using ng-repeat. Whenever a button in the directive template is clicked it will call a function- Stop() in directive controller which in-turn calls function test() in parent controller, inside test() it will call a method dirSample () in directive's link function.
When I print the scope inside dirSample(), it prints the scope of the last created directive not the one which called it.
How can I get the scope of the directive which called it?
Find the pluker here
.directive('stopwatch', function() {
return {
restrict: 'AE',
scope: {
meri : '&',
control: '='
},
templateUrl: 'text.html',
link: function(scope, element, attrs, ctrl) {
scope.internalControl = scope.control || {};
scope.internalControl.dirSample = function(){
console.log(scope)
console.log(element)
console.log(attrs)
console.log(ctrl)
}
},
controllerAs: 'swctrl',
controller: function($scope, $interval)
{
var self = this;
self.stop = function()
{
console.log($scope)
$scope.meri(1)
};
}
}});
full code in plunker
I've changed the binding of your function from & to = since you need to pass a parameter. This means some syntax changes are in order, and also you need to pass the scope along the chain if you want to have it all the way at the end:
HTML:
<div stopwatch control="dashControl" meri="test"></div>
Controller:
$scope.test = function(scope)
{
console.log(scope);
$scope.dashControl.dirSample(scope);
}
Directive:
.directive('stopwatch', function() {
return {
restrict: 'AE',
scope: {
meri : '=',
control: '='
},
templateUrl: 'text.html',
link: function(scope, element, attrs, ctrl) {
scope.internalControl = scope.control || {};
scope.internalControl.dirSample = function(_scope){
console.log(_scope);
}
},
controllerAs: 'swctrl',
controller: function($scope, $interval)
{
var self = this;
self.stop = function()
{
console.log($scope);
$scope.meri($scope);
};
}
}});
Plunker

Angular: get Template URL from parent directive

I understand that I can dynamically set a templateUrl base on an option DOM attribute template-url="foo.html" given the following code:
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return attrs.templateUrl || 'some/path/default.html'
}
}
});
However, I need to take this a step further and pass this string one level deeper, to a child directive.
Given this HTML:
Usage in Main project
<parent parent-template="bar.html" child-template="foo.html"></parent>
The child will not be exposed in most cases, so if child-template is set, it needs to implicitly replace templateUrl for all child <child></child> elements that are located in the parent foo.html.
The require: '^parent' attribute passes data from scope to scope, but I'm not seeing this available in templateUrl when it's declared.
foo.html
<h1>Title</h1>
<child ng-repeat="item in array"></child>
Directives
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'some/path/default.html'
},
scope: {
childTemplate: '=childTemplate'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return ??? // parent.attribute.childTemplate? || 'some/path/default.html'
},
require: '^parent',
scope: {
childTemplate: '=childTemplate'
}
}
});
Update
The old answer (see bellow) won't work because it's only possible to access the controller of the required directives inside the link functions, and the templateUrl function gets executed before the link functions.
Therefore the only way to solve this is to handle everything in the templateUrl function of the child directive. However this function only takes 2 arguments: tElement and tArgs.
So, we will have to find the element of the parent directive and access the attribute child-template. Like this:
angular.module('testApp', [])
.directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
},
transclude:true,
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'default.html'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
require:'^parent',
templateUrl: function(elem,attrs) {
//if jQuery is loaded the elem will be a jQuery element, so we can use the function "closest"
if(elem.closest)
return elem.closest("parent").attr("child-template") || 'default.html';
//if jQuery isn't loaded we will have to do it manually
var parentDirectiveElem=elem;
do{
parentDirectiveElem=parentDirectiveElem.parent();
}while(parentDirectiveElem.length>0 && parentDirectiveElem[0].tagName.toUpperCase()!="PARENT");
return parentDirectiveElem.attr("child-template") || 'default.html';
}
}
});
Example
Old Answer
Since you are isolating the scope, you could try this, it's a bit hacky but I guess that it should work:
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
controller: function($scope) {
this.childTemplate=$scope.childTemplate;
},
link: function(scope, element, attrs) {
},
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'some/path/default.html'
},
scope: {
childTemplate: '#'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
require: '^parent',
link: function(scope, element, attrs, parentController) {
if(parentController.childTemplate)
element.data("childTemplate", parentController.childTemplate);
},
templateUrl: function(elem,attrs) {
return elem.data("childTemplate") || 'some/path/default.html'
}
}
});
In my question, I was attempting to provide an override for the templateUrl of an off-the-shelf directive that didn't have one. My original question doesn't mention this, however, I wanted to add this as a reference to others who may have forgotten, as I did. Angular allows you to decorate directives and override their properties.
app.config(function($provide) {
$provide.decorator('child', function($delegate) {
var directive = $delegate[0];
directive.templateUrl = 'path/to/custom.html';
return $delegate;
});
});

Categories

Resources