I have a parent directive in which its controller makes a call through a service to get some data.
sharedService.getData(options).then(function(data){
$scope.data = data;
});
Now i need this data in my child controller.
What i have already tried are the ff:
1) Through $timeout i get the data after sometime but it doesn't seem a good solution impacting performance
2) watchCollection() - i watched if newValue !== oldValue
problem being the data is huge so it takes a toll of performance
Now the issue i'm getting is the child directive gets executed after parent BUT before the data comes back from the service and i'm not able to get that data in my child directive via $scope.data.
Is there any solution to get data from parent directive to child directive when i have to wait for data to come in parent?
You can include your parent directive controller in your child directive by using require.
angular.module('myApp', [])
.directive('dirParent', function() {
return {
restrict: 'E',
scope: {},
controller: ['$scope', function($scope) {
}],
};
})
.directive('dirChild', function() {
return {
require: '^dirParent', // include directive controller
restrict: 'E',
scope: {
},
link: function(scope, element, attrs, paretCtrl) {
var data = paretCtrl.getMyData();
}
};
})
It's always a best to use service for communication and and business logic. Here is an example. Please check. This might solve your problem.
// Code goes here
angular.module('app', [])
.factory('messageService', function() {
return {
message: null
}
})
.directive('parentDir', function() {
return {
scope: {}, //isolate
template: '<input type="text" ng-model="PDirInput"/><button ng-click="send()">Send</button>',
controller: function($scope, messageService) {
$scope.send = function() {
messageService.message = $scope.PDirInput;
}
}
}
})
.directive('childDir', function() {
return {
scope: {}, //isolate
template: '<code>{{CDirInput.message}}</code>',
controller: function($scope, messageService) {
$scope.CDirInput = messageService;
}
}
})
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#*" data-semver="2.0.0-alpha.31" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<HR/>Parent Directive
<parent-dir></parent-dir>
<br/>
<HR/>Child Directive
<child-dir></child-dir>
<HR/>
</body>
</html>
Related
I have a isolate scope directive that I am using inside ng-repeat, which is iterating over an array from the controller of that template. The template is as follows:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="bootstrap.min.css" />
<script src="angular.min.js"></script>
<script src="script1.js"></script>
</head>
<body ng-app="AddNewTest" ng-controller="AddNewController">
<div class="items" ng-repeat="row in rows">
<add-new-row data="row" index="$index"></add-new-row>
</div>
</body>
</html>
The directive is defined as follows:
angular.module('AddNewTest', []).
directive('addNewRow', function ($timeout) {
return {
controller: 'AddNewController',
link: function (scope, element, attribute) {
element.on('keyup', function(event){
if(scope.index + 1 == scope.rows.length) {
console.log('keyup happening');
$timeout(function () {
scope.rows.push({ value: '' });
scope.$apply();
});
}
})
},
restrict: 'E',
replace: true,
scope: {
index: '='
},
template: '<div class="add-new"><input type="text" placeholder="{{index}}" ng-model="value" /></div>'
}
}).
controller('AddNewController', function ($scope, $timeout) {
$scope.rows = [
{ value: '' }
];
});
But even after adding new row and doing a $apply() the ng-repeat is not rendering the new data added. Please help.
Plnkr Link Here
Pass array of rows to directive as follow:-
scope: {
index: '=',
rows :'='
},
<add-new-row rows="rows" index="$index"></add-new-row>
Working plunker
Each ng-repeat creates an isolated scope than you declare an isolated scope inside your directive that has the same controller as the div. You're swimming in the $scope soup :)
I would personnally make a clean and independent directive with its own controller.
angular.module('AddNewTest', []).
directive('addNewRow', function () {
return {
restrict: 'E',
controller: MyController,
controllerAs: '$ctrl',
template: '{{$ctrl.rows.length}}<div class="add-new"><pre>{{$ctrl.rows | json}}</pre><input type="text" placeholder="0" ng-model="value" /></div>'
}
}).
controller('MyController', MyController);
function MyController($scope) {
var vm = this;
this.rows = [ { value: '' } ];
$scope.$watch("value",function(value){
if(value)
vm.rows.push({ value: value });
});
}
http://plnkr.co/edit/AvjXWWKMz0RKSwvYNt6a?p=preview
Of course you can still bind some data to the directive using bindToController (instead of scope:{}) and if you need an ng-repeat, do it in the directive template directly.
The main point of this post is that I want to avoid using $scope.$watch as it is taught to cause a performance decrease.
So imagine having one shared view partial/template, call it "mypage" with two different directives, call them "directive1" and "directive2", in it that share a data model, lets call it "awesomeData"
Maybe it looks something like this:
<div class="mypage-root">
<directive1 shared-data="awesomeData"></directive1>
<directive2 shared-data="awesomeData"></directive2>
</div>
Now obviously when "awesomeData" changes in either directive or the root view, the data changes in the other parts too (assuming it is two-way bound).
But what if i want something else to happen in directive2 when directive1 has updated the data model, like calling a function in directive2?
I could use a watcher but as mentioned, that is a performance decrease.
What other approaches are there and what is the "true" angular way to do this?
I know you're asking about how to do this without a watcher, but I think in this instance a watcher is the best answer as it gives separation of concerns. directive1 shouldn't have to know about directive2, but only about MySharedService and anything it might broadcast:
$rootScope.$broadcast('myVar.updated');
...
$scope.$on('myVar.updated', function (event, args) {
});
Edit:
Also, depending on the complexity of the model the directives will be using, you could pass this model from their parent controller (The down side to this is that you have to instantiate a model from a parent):
HTML:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angularjs#1.4.9" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MyCtrl">
<directive1 my-model="model"></directive1>
<directive2 my-model="model"></directive2>
</body>
</html>
JS:
var app = angular.module('app', []);
app.controller('MyCtrl', function($scope) {
$scope.model = { info: 'Starting...' };
});
app.directive('directive1', function() {
var controllerFn = function($scope) {
$scope.changeInfo = function() {
$scope.vm.myModel.info = 'Directive 1';
};
};
return {
restrict: 'E',
controller: controllerFn,
template: '<div><span>hello</span> <span ng-bind="vm.myModel.info"></span> <button ng-click="changeInfo()">Click</button></div>',
controllerAs: 'vm',
bindToController: true,
scope: {
myModel: '='
}
}
});
app.directive('directive2', function() {
var controllerFn = function($scope) {
$scope.changeInfo = function() {
$scope.vm.myModel.info = 'Directive 2';
};
};
return {
restrict: 'E',
controller: controllerFn,
template: '<div<span>bye</span> <span ng-bind="vm.myModel.info"></span> <button ng-click="changeInfo()">Click</button></div>',
controllerAs: 'vm',
bindToController: true,
scope: {
myModel: '='
}
}
});
plunker: https://plnkr.co/edit/EwdHFl7eZl2rd43UVG4J?p=preview
Edit 2:
As with method 2, you could have a shared service, that provides this model:
plunker: https://plnkr.co/edit/EwdHFl7eZl2rd43UVG4J?p=preview
I'm passing through a JavaScript object to an Angular.js directive. When logging attrs in the link function, I see my attribute there, but when logging attrs.myOptions there is no data:
var directive = function() {
return {
restrict: 'E',
replace: true,
scope: {
myOptions: '='
},
link: function(scope, element, attrs) {
console.log(scope)
console.log(attrs)
console.log(attrs.myOptions)
}
}
}
And i'm implementing it in my template like so:
<directive my-options="myObject" />
myObject is a JavaScript object that's set in the controller for the template.
And I'm getting this error in the console:
Syntax Error: Token 'myObject' is unexpected, expecting [:] at column 3 of the expression [{{myObject}}] starting at [myObject}}].
As suggested above you can use like this. only change that you have to access model though scope.
var myApp = angular.module('myApp', []);
myApp.directive("directive",function() {
return {
restrict: 'E',
replace: true,
scope: {
myOptions: '='
},
link: function(scope, element, attrs) {
console.log(scope)
console.log(attrs)
console.log(scope.myOptions)
}
}
});
myApp.controller('myCtrl', function($scope) {
$scope.myObject = [1,2,3,4,5];
console.log("app controller");
});
<!doctype html>
<html>
<script src="https://code.angularjs.org/1.4.8/angular.js"></script>
<head>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<directive my-options="myObject" />
</div>
</body>
</html>
JSBin Example:
http://jsbin.com/yuyetakonowu/1/edit?html,js,output
Summary:
I have two directive's (myParentDirective and myChildDirective). myParentDirective transcludes myChildDirective content. I am attempting to two-way-bind a model object within myChildDirective. It works successfully when I "update" the object by simply changing or adding properties to an existing object instance. However, when I "assign" a new object (using equals operator from the controller's timeout function) myChildDirective will not be updated.
HTML:
<html ng-app='ValidationApp'>
<head>
<title>Assigning a model object after isolated scope is set doesn't work</title>
</head>
<body ng-controller='MyController'>
<h2>MyController.assignedObject: {{assignedObject}}</h2>
<h2>MyController.updatedObject: {{updatedObject}}</h2>
<my-parent-directive assigned-object='assignedObject' updated-object='updatedObject'>
<my-child-directive></my-child-directive>
</my-parent-directive>
<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.17/angular.js'></script>
<script src='app.js'></script>
</body>
</html>
JavaScript:
var app = angular.module('ValidationApp', [])
app.controller('MyController', [
'$scope',
'$http',
function($scope, $http) {
// Model objects loaded on page-load
$scope.assignedObject = {value: 'pre-update'}
$scope.updatedObject = {value: 'pre-update'}
// Mock ajax request
setTimeout(function() {
// This is what I'm ultimately trying to accomplish. However, myChildDirective is not properly
// showing the updated value 'post-update'.
$scope.assignedObject = {value: "post-update"}
// I noticed that this line will properly update myChildDirective, but it's not an ideal solution.
// I'm including it in the example just to show the inconsistent results in myChildDirective.
$scope.updatedObject.value = "post-update"
$scope.$apply()
}, 1000)
}
])
app.directive('myParentDirective', function() {
return {
restrict: 'E',
transclude: true,
scope: {
assignedObject: '=',
updatedObject: '='
},
template: '\
<h2>myParentDirective.assignedObject: {{assignedObject}}</h2>\
<h2>myParentDirective.updatedObject: {{updatedObject}}</h2>\
<div ng-transclude></div>\
',
controller: function($scope, $element) {
this.assignedObject = $scope.assignedObject
this.updatedObject = $scope.updatedObject
}
}
})
app.directive('myChildDirective', function() {
return {
restrict: 'E',
require: '^myParentDirective',
scope: false,
template: '\
<h2>myChildDirective.myParentDirective.assignedObject: {{myParentDirective.assignedObject}}</h2>\
<h2>myChildDirective.myParentDirective.updatedObject: {{myParentDirective.updatedObject}}</h2>\
',
link: function($scope, $element, $attrs, myParentDirective) {
$scope.myParentDirective = myParentDirective
}
}
})
I found a few solutions to my problem...
The problem is that I'm assigning $scope.assignedObject to this.assignedObject within myParentDirective. When I do this, there's no way for myChildDirective to know when the property has changed. Ordinarily, $scope.$apply() function would be called to notify all observers that a scope property has changed, but since I'm re-assigning this object reference to this.assignedObject myChildDirective never receives that event.
The simplest solution can be found here: http://jsbin.com/yuyetakonowu/11/edit. Basically, this is simply inheriting the parent scope so that I can rely on angular's scope to emit the appropriate event and update myChildDirective accordingly.
However, this wasn't good enough for me as I also needed myChildDirective to have an isolated scope with its own properties. This means I can't simply "inherit" the parent scope. I've resolved this issue with the following: http://jsbin.com/yuyetakonowu/9/edit.
The end result:
HTML:
<html ng-app='ValidationApp'>
<head>
<title>Assigning a model object after isolated scope is set doesn't work</title>
</head>
<body ng-controller='MyController'>
<h1>MyController</h1>
<h2>assignedObject: {{assignedObject}}</h2>
<h2>updatedObject: {{updatedObject}}</h2>
<my-parent-directive assigned-object='assignedObject' updated-object='updatedObject'>
<my-child-directive child-property='child-property-value'></my-child-directive>
</my-parent-directive>
<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.17/angular.js'></script>
<script src='app.js'></script>
</body>
</html>
JavaScript:
var app = angular.module('ValidationApp', [])
app.controller('MyController', [
'$scope',
'$http',
function($scope, $http) {
// Model objects loaded on page-load
$scope.assignedObject = {value: 'pre-update'}
$scope.updatedObject = {value: 'pre-update'}
// Mock ajax request
setTimeout(function() {
// This is what I'm ultimately trying to accomplish. However, myChildDirective is not properly
// showing the updated value 'post-update'.
$scope.assignedObject = {value: "post-update"}
// I noticed that this line will properly update myChildDirective, but it's not an ideal solution.
// I'm including it in the example just to show the inconsistent results in myChildDirective.
$scope.updatedObject.value = "post-update"
$scope.$apply()
}, 1000)
}
])
app.directive('myParentDirective', function() {
return {
restrict: 'E',
transclude: true,
scope: {
assignedObject: '=',
updatedObject: '='
},
template: '\
<h1>myParentDirective</h1>\
<h2>assignedObject: {{assignedObject}}</h2>\
<h2>updatedObject: {{updatedObject}}</h2>\
<div ng-transclude></div>\
',
controller: function($scope, $element) {
// Generally, exposing isolate scope is considered bad practice. However, this directive is intended
// to be used with child directives which explicitly depend on this directive. In addition, child
// directives will likely need their own isolated scope with two-way binding of properties on this scope.
this._scope = $scope
}
}
})
app.directive('myChildDirective', function() {
return {
restrict: 'E',
require: '^myParentDirective',
scope: {
childProperty: '#'
},
template: '\
<h1>myChildDirective</h1>\
<h2>childProperty: {{childProperty}}</h2>\
<h2>assignedObject: {{assignedObject}}</h2>\
<h2>updatedObject: {{updatedObject}}</h2>\
',
link: function($scope, $element, $attrs, myParentDirective) {
myParentDirective._scope.$watch('assignedObject', function(newValue, oldValue) {
$scope.assignedObject = newValue
})
myParentDirective._scope.$watch('updatedObject', function(newValue, oldValue) {
$scope.updatedObject = newValue
})
}
}
})
edit: Modified the code as per stevuu's suggestion as well as added a plunkr to here
I'm currently attempting to have a child directive call a method(resolve) through another directive all the way up to a parent directive but I'm having difficulties identifying the problem with my approach.
The problem right now seems to be that although resolve() does get called as expected on click, selected remains undefined.
the html:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Angular: directive using & - jsFiddle demo</title>
<script type='text/javascript' src='//code.jquery.com/jquery-1.9.1.js'></script>
<link rel="stylesheet" type="text/css" href="/css/normalize.css">
<link rel="stylesheet" type="text/css" href="/css/result-light.css">
<script type='text/javascript' src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<style type='text/css'>
</style>
</head>
<body ng-app="myApp">
<div grand-parent>
<span>selected: {{text}}</span>
<div parent resolve="resolve"></div>
</div>
</body>
</html>
And the js:
var myApp = angular.module('myApp',[]);
myApp.directive('grandParent', function() {
return {
scope:{
resolve: "&"
},
link: function($scope, $element) {
$scope.resolve = function(selected){
$scope.text = selected
}
}
};
});
myApp.directive('parent', function(){
return{
scope: {
resolve: "&"
},
template: "<div child resolve='resolve'></div>"
};
});
myApp.directive('child', function() {
return {
scope: {
resolve: "&"
},
template: "<div>Click me!</div>",
link: function($scope, $element) {
$element.on("click", function(){
$scope.$apply(function(){
$scope.resolve({selected: "Yahoo!!"});
});
});
}
};
});
resolve: "&" is a mapping. So here:
myApp.directive('grandParent', function() {
return {
scope:{
resolve: "&"
},
link: function($scope, $element) {
$scope.resolve = function(selected){
$scope.text = selected
}
}
};
});
you are trying to map "resolve" to ... nothing, because "grandParent" doesn't have any attr named "resolve".
If you want to share some staff betweens directives you should do something like that:
view
<div data-grand-parent resolve="resolved()">
<div data-parent ></div>
</div>
Directives
var app = angular.module('test');
app.directive('grandParent', function() {
return {
scope : {
resolve : "&" // in view we defined resolve attr
// with "resolve()" value, so now resolve=resolved()
// in grandParent scope too.
},
controller: function($scope){
this.getResolve = function(){
return $scope.resolve;
};
}
};
});
app.directive('parent', function() {
return {
require: "^grandParent",
link: function(scope, element, attr, grandParentCtrl){
grandParentCtrl.getResolve()();
},
template : ""
};
});
controller
angular.module('desktop')
.controller('MainCtrl', function ($scope, mocks) {
$scope.resolved = function(){
console.log("calling $scope.resolved() ...");
};
});
output
calling $scope.resolved() ...
So, how does it work?
We defined resolved function in our controller, then we sign this function to attr "resolve" in grandParent directive. Thx to resolve : "&" we could mapped that resolved() function to "resolve" property in grandParent scope. At the end we inject grandParent to other directives. That's all.
I recommend you to read angularJs by Brad Green, Shyam Seshadri. it's not the best book but could be worse and it's free. You can find very good tutorial too on http://www.egghead.io/
ps. Sorry for my english ;/