2 way binding not working on directive in isolated scope - angularjs

I'm just trying to do a simple directive, but for some reason the 2 way data binding isn't working in my directive. From my code you can see that a console log in the directive that will read the correct information I have in the $scope.displayMaintenance variable, but I can't change it in my directive.
HTML:
<maintenance-banner display-maintenance="displayMaintenance"></maintenance-banner>
Controller:
$scope.displayMaintenance = false;
$scope.$watch('displayMaintenance', function(data) {
console.log("i changed!: " + data);
});
Directive:
.directive('maintenanceBanner', function() {
return {
restrict: 'E',
replace: true,
scope: {
displayMaintenance: '='
},
templateUrl: '/partials/navbar/maintenance-banner.html',
link: function(scope) {
console.log(scope.displayMaintenance);
scope.displayMaintenance = true;
}
};
})
Any suggestions?

The issue may be that you use your directive inside another isolated scope.
I have created a sample: http://jsfiddle.net/2063n7te/
changing the model value using assignment replaces the model object which may not be reflected in the parent scope.
in short: do not bind primitives directly to the scope.
instead of
$scope.text = "foo";
use
$scope.input = {
text: "foo"
};
a good read is: http://www.thinkingmedia.ca/2015/01/learn-how-to-use-scopes-properly-in-angularjs/
point #4 applies specifically to the behaviour you are seeing.

Check that your directive can find the template URL.
Works for me:
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
$scope.displayMaintenance = false;
$scope.$watch('displayMaintenance', function(data) {
alert("i changed!: " + data);
});
});
app.directive('maintenanceBanner', function() {
return {
restrict: 'E',
replace: true,
scope: {
displayMaintenance: '='
},
template: '<div>{{displayMaintenance}}</div>',
link: function(scope) {
console.log(scope.displayMaintenance);
scope.displayMaintenance = true;
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<maintenance-banner display-maintenance="displayMaintenance"></maintenance-banner>
</div>

Related

Two Way binding with isolated scope not working in AngularJs

I have written a directive for simple dropdown. On click of one value, I am calling a function and updating the value.
If I log 'scope.$parent.selectedItem' , I am able to see the value. But that is not updated in parent controller.
This is Directive code
app.directive('buttonDropdown', [function() {
var templateString =
'<div class="dropdown-button">'+
'<button ng-click="toggleDropDown()" class="dropbtn">{{title}}</button>'+
'<div id="myDropdown" ng-if="showButonDropDown" class="dropdown-content">'+
'<a ng-repeat="item in dropdownItems" ng-click="selectItem(item)">{{item.name}}</a>'+
'</div>'+
'</div>';
return {
restrict: 'EA',
scope: {
dropdownItems: "=",
selectedOption: '=',
title: '#'
},
template: templateString,
controller: function($scope,$rootScope,$timeout) {
$scope.selectedOption = {};
$scope.showButonDropDown = false;
$scope.toggleDropDown = function() {
$scope.showButonDropDown = !$scope.showButonDropDown;
};
$scope.$watch('dropdownItems', function(newVal,oldval){
if(newVal){
console.log(newVal);
}
});
$scope.selectItem = function(item){
console.log(item);
$scope.selectedOption = item;
}
},
link: function(scope, element) {
scope.dropdownItems = scope.dropdownItems || [];
window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) {
scope.showButonDropDown = false;
}
console.log(scope.$parent);
}
}
}
}]);
This is my HTML
<button-dropdown title="Refer a Friend" dropdown-items='dropDownList' selected-option='selectedItem'></button-dropdown>
This is my controller code
$scope.$watch('selectedItem',function(newVal,oldVal){
if(newVal){
console.log("*** New Val** ");
console.log(newVal);
}
});
I didn't understand one thing.. If I print 'scope.$parent.selectedItem', I could see the value. but it is not updating in the controller. Didn't understand, what am I missing. Can anyone help on this. Thanks in advance.
Try in this way
1. Try to $emit the scope variable in directive.
2. get that in controller by using $on.
Directive:
$scope.$emit('selectedItem',scopeVariable);
Controller:
$scope.$on('selectedItem',function(event,newVal){
if(newVal){
// logic here
}
});

$scope.$watch doesn't update after service call in directive

I have a directive which loads a image data template.
The problem is that It doesn't update the image date after the service which retrieve the img information is called.
This is my code:
Controller method:
$scope.watchImage = function(file_id){
FileService.getFile(file_id)
.then(
function(data){
if(data.file){
$scope.img = data.file;
console.log('Service called');
}
}
);
}
Directive:
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
link: function($scope, element, attrs){
$scope.$watch(function() {
return $scope.img;
}, function() {
console.log($scope.img);
});
},
template: 'IMG: {img}'
};
});
HTML:
<div class="ui container">
<h2 class="ui dividing header">Images</h2>
</div>
<div ng-view></div>
<image-details img="img"></image-details>
</div>
Log result:
undefined
Service called
Any idea how to solve it ?
Thanks!
First of all, thank you to everyone for your replies. All of them help me in the solution.
Finally this is my working code.
Directive:
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
template: 'IMG: {{img}}'
};
});
And I added the directive to my template (I was adding it outside ngview).
you have some mistake in template and in link function.
var app = angular.module('myApp', []);
app.controller('mainCtrl', function ($scope) {
$scope.img = {id: 1, title: "avatar.jpeg", slug: "avatar.jpeg", filesize: 24875, created_at: "2016-03-10 11:44:59"};
})
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
link: function(scope, element, attrs){
scope.$evalAsync(function() {
return scope.img;
});
},
template: 'IMG: {{img}}'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<image-details img="img"></image-details>
</div>
I think your directive should be Like :
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
link: function(scope, element, attrs){
scope.$watch('img',function(image) {
return image;
}, function() {
console.log(image);
});
},
template: 'IMG: {img}'
};
});
First of all use a controller instead of link function because you don't need that. Link function is deprecated for simple components like this in angular 1.5.
Then, for using $watch, you need to specify what variable you want to watch, and only after what to do when it's change.
$watch('varToWatch', function(newValue) {...});
That said, if you use a controller instead of the link function, you probably use also a "Controller as" syntax. When you use it, you need to specify the "view name" of the variable you want to watch. For example:
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
controllerAs: '$ctrl',
controller: function($scope){
$scope.$watch('$ctrl.img', function(newVal) {
console.log(newVal);
// if you want you can assign new value to your variable
// $scope.img = newVal;
});
},
template: 'IMG: {img}'
};
});
Try that and tell me if it's works for you ;)
This is a clear case of when the scope is affected outside the module. For those cases the lifecycle will not do the digest of the scope as you will expect.
You have to manually $digest or $apply when you want to notify your app that the scope have changed inside your directive

Cant use directive controller values in directive template

Im having a hard time accessing the attributes passed in to my directive from the template of that directive. I want to be able to access 'companyId' from album.tmpl.html but no matter what i try i can't get it. The strangest part is i can see it has made its way in to the controller, but somehow it's not getting from the controller to the template. I know the template is correctly calling the controller as it can succesfully print out the value of 'testVar' which is initialised inside the controller. Any advice would be appreciated.
directive + directive controller
(function () {
'use strict';
angular.module('erCommon')
.directive('erAlbum', albumDirective)
.controller('AlbumController', AlbumController);
function AlbumController() {
var vm = this;
vm.testVar = "test var initiated";
}
function albumDirective($log) {
function albumLink(scope, element, attrs, AlbumController) {
//watch vars in here
}
return {
restrict: 'E',
scope: {
companyId: '=companyId'
},
bindToController: true,
templateUrl: 'components/temp/album.tmpl.html',
controller: 'AlbumController',
controllerAs: 'albumCtrl',
link: albumLink
};
}
})();
template ( album.tmpl.html
<div ng-controller="AlbumController as albumCtrl">
testVar: {{albumCtrl.testVar}}<BR>
companyId:{{albumCtrl.companyId}}<BR>
</div>
usage
<er-album company-id="2"></er-album>
output
test var: test var initiated
companyId:
You need to remove ng-controller from your template:
<div>
testVar: {{albumCtrl.testVar}}<BR>
companyId:{{albumCtrl.companyId}}<BR>
</div>
To achieve the result you wanted i had to modify the structure of your code slightly. Hope this helps you to understand the issue. Look for materials about isolated scopes which Angular uses with directives.
HTML:
<div ng-app="erCommon" ng-controller="AlbumController as albumCtrl">
<er-album company-id="2" test = "albumCtrl.testVar"></er-album>
</div>
Controller:
angular.module('erCommon', [])
.directive('erAlbum', albumDirective)
.controller('AlbumController', AlbumController);
function AlbumController() {
var vm = this;
vm.testVar = "test var initiated";
}
function albumDirective() {
return {
restrict: 'E',
scope: {
test: '=test',
companyId: '#companyId'
},
template: '<div> testVar: {{test}}<BR> companyId:{{companyId}}<BR> </div>', // it will work fine with templateUrl as well, just didn't want to cr8 another file...
link: function(scope, element, attrs){
//do whatever else you might need;
}
};
}

Outputting a Service Property Value Through a Directive in Angular

My goal is to output a value (from a service) through a element directive so that the html will look like this <msg msg="alertMsg"></msg> and out pops a value from the service.
Here is my code thus far:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: {//something here to pass MsgService to template },
template: 'Message:{{MsgService.getAlertMsg()}}'
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
HTML would parse/compile to...
<msg msg="alertMsg">Message: default</msg>
What other code do I need?
If a service wont work directly, Should I access it through a controller?
app.directive("msg", function() {
return {
restrict: "E",
scope: {
getMsg: '&msg'
},
controller: 'MsgController',
template:'Message:{{getMsg()}}'
};
}]);
app.controller('MsgController', ['MsgService' , function(MsgService){
this.getAlertMsg = function(){
return MsgService.getAlertMsg();
};
}]);
HTML would parse/compile to...
<msg msg="getAlertMsg()">Message: default</msg>
Sorry for any errors in code or function use, I'm fairly new to Angular.
You can use the link function of the directive. This function is called once for every rendered instance of your directive. It receives, among other things, the scope of your directive. You can extend your scope very easily with the result of calling the MsgSevice.getAlertMsg() service method:
var app = angular.module("app", []);
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: true,
template: 'Message:{{msg}}',
link: function (scope, $element, attrs) {
scope.msg = MsgService.getAlertMsg();
}
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
Later on, I presume you will want to just display the alert message from the msg DOM attribute of the msg directive. Achieving this is much more simple, since AngularJS is already prepared for this common use case. The solution involves creating an isolate scope. The isolate scope can be populated with properties from the parent environment. One possibility is to use the value of a DOM attribute from your directive's element using the "#" syntax. In this case you won't even need the entire MsgService service:
app.directive("msg", function () {
return {
restrict: "E",
scope: {
"msg": "#"
},
template: 'Message:{{msg}}'
};
});
Simplest would be to set the service on your scope and use that in your template:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: { },
template: 'Message:{{MsgService.getAlertMsg()}}',
link: function(scope, element, attrs) {
scope.MsgService = MsgService;
}
};
}]);

ng-show not working inside directive template

I would like to do something like this
fiddle, making the text disappear and reappear with every click.
Problem is, it seem that with an isolated scope you can't have access to the controller scope. I solved it in the link function, handling there click event and setting my "showMe" flag using scope.$apply, like:
scope.$apply('showMe = false');
Is this the right way to go or there is some more elegant method?
Here you go (http://jsfiddle.net/66d0t7k0/1/)
Put your click handler in the link function and expose showMe to the scope
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
To expand on apairet's answer, since your directive is using an isolated scope, you could handle all of that in the template itself like so:
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-init=\"showMe = false\" ng-click=\"showMe = !showMe\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
}
};
});
Another consideration is to use ng-if rather than ng-show as it doesn't render the element in the DOM until the expression evaluates to true.
You can hide the scope in the directive by setting scope: false
You can then put all your function in the main controller scope
angular.module('appMyApp').directive('appMyAppItem', function() {
return {
transclude: true,
templateUrl: 'link/to/url',
controller: 'appMyAppController',
scope: false
}
});
angular.module('appMyApp').controller('appMyAppController', ['$scope', function($scope){
$scope.showItem = true;
$scope.toggleItem = function(){
$scope.showItem = !$scope.showItem;
};
}]);
Hope this helps

Resources