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

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

Related

how to pass function defined inside directive to another directive?

I need to pass function defined in directive to another one , my code is something like below
angular
.module('myApp')
.controller('myAppCtrl', function($scope) {})
.directive('first', function() {
return {
restrict: 'E',
scope: {
originFn: '&',
},
template: '<button ng-click="originFn()" >Click</button>',
controller: function() {
var self = this;
self.originFn = function() {
console.log('called from First');
};
},
};
})
.directive('second', function() {
return {
restrict: 'E',
scope: {
passedFn: '&',
},
template: '<button ng-click="passedFn()" >Click</button>',
};
});
html
<div ng-controller="myAppCtrl">
<first origin-fn="originFn" />
<second passed-fn="originFn" />
</div>;
when click on second button origin function not called ,
How can pass function to be called inside second directive ?
Both of directives are siblings, there is nothing in common, one directive can't call other directive controller without being it child.
So, if you can nest second directive into the first, you can use require functionality to get the parent directive controller.
.directive('second', function () {
return {
restrict: 'E',
require: '^^first',
scope: {},
template: '<button ng-click="innerFunc()" >Click</button>',
link: function ($scope, $elem, $attr, firstCtrl) {
// -------------------------------------^
$scope.innerFunc = firstCtrl.originFn;
}
};
})
<div ng-controller="myAppCtrl">
<first>
<second></second>
</first>
</div>
For more complete example.

Angularjs scope var in template of custom directive

I have a custom directive hello
myapp.directive('hello', function() {
return {
link : function (scope, elem, attr) {
scope.url = attr.url;
console.log(scope.url);
},
template: '<div ng-include="\'[{url}]\'"></div>'
};
});
my html code is
<div url="mypage.html"></div>
I get the value mypage.html in my console.log but is not able to include the page it says
http://127.0.0.1:5000/[%7Burl%7D]
I think you need to sanitize the html before binding it to ng-include, but below is the alternate solution to your problem you are facing
app.directive('widget', function(){
return {
scope: {
url: '#'
},
template: '<div ng-include="getTemplateUrl()"></div>',
controller: function($scope) {
$scope.getTemplateUrl = function() {
console.log($scope.url)
return $scope.url
}
}
}
})
HTML Code
<div url="mypage.html"></div>

two-way data binding inside attribute directive that requires element directive

I have an element directive (e-dir) and an attribute directive (a-dir) on the same element:
<e-dir a-dir attr="msg"></e-dir>
I pass msg into e-dir's isolate scope via the attr attribute:
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
}
};
});
In this way, msg is bound (two-ways) with $scope.attr (in EDirCtrl) or scope.attr (in e-dir's link function).
Is there a simple way I can achieve the same two-way data-binding inside a-dir's directive? Or would you recommend another, simpler approach?
The closest thing I've been able to come up with is to set eDirCtrl.attr = $scope.attr; inside e-dir's controller (EDirCtrl):
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
},
controllerAs: 'eDirCtrl'
};
});
Then, have a-dir require e-dir, and access attr via e-dir's controller (eDirCtrl.attr):
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
eDirCtrl.attr = 'eDirCtrl.attr';
}
});
But, it's not bound two-ways. As you can see this code snippet:
var app = angular.module('app', []);
app.controller('Ctrl', function Ctrl($scope) {
$scope.msg = 'initial message';
})
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
template: '<div>$scope.attr: {{attr}}</div>'+
'<div>eDirCtrl.attr: {{eDirCtrl.attr}}</div>',
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
$timeout(function() {
$scope.attr = 'changing $scope.attr also changes msg';
}, 2000);
},
controllerAs: 'eDirCtrl'
};
});
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
$timeout(function() {
eDirCtrl.attr = 'changing eDirCtrl.attr does not effect $scope.attr or msg';
}, 4000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
msg: <input type="text" ng-model="msg"><br>
<e-dir a-dir attr="msg"></e-dir>
</div>
The reason the two-way binding isn't working is that attr is being bound to a string rather than an object. In JavaScript, primitives (booleans, numbers, strings) are immutable, so when you change one, the previous instance is discarded and a new one is used. This breaks Angular's two-way binding and any changes to scope.msg are not propagated through attr into the directive.
You can get this to work as expected by setting msg on an object e.g. scope.test.msg and binding attr to test (the object) rather than msg (a string).
I've updated your code snippet to do this:
var app = angular.module('app', []);
app.controller('Ctrl', function Ctrl($scope) {
$scope.test = {msg : 'initial message'};
})
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
template: '<div>$scope.attr: {{attr.msg}}</div>'+
'<div>eDirCtrl.attr: {{eDirCtrl.attr.msg}}</div>',
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
$timeout(function() {
$scope.attr.msg = 'changing $scope.attr also changes msg';
}, 2000);
},
controllerAs: 'eDirCtrl'
};
});
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
$timeout(function() {
eDirCtrl.attr.msg = 'changing eDirCtrl.attr does not effect $scope.attr or msg';
}, 4000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
msg: <input type="text" ng-model="test.msg"><br>
<e-dir a-dir attr="test"></e-dir>
</div>

Change text value when button is pressed with directives in angular

Here's my fiddle
I basically want to be able to change the text when a button is pressed. I have tried with both $observe and $watch inside link, but I still don't manage to get it working.
Code:
(function(){
angular.module('app', [])
.directive('testDirective', function(){
return {
restrict: 'E',
scope: {
title: '#'
},
template: '<div>this is a {{ title }}</div>',
link: function(scope, element, attrs) {
//?
}
};
});
})()
You need to pass data as scope variable, you should not pass it as a string if you want to track changes.
check this fiddle, replace counter data with your desired data. Hope this helps
<div ng-controller='myctrl'>
<test-directive title="counter"></test-directive>
<hr></hr>
<button type="button" ng-click = 'onclickbutton()'>Change names</button>
</div>
(function(){
angular.module('app', [])
.controller('myctrl',function($scope){
$scope.counter = 0;
$scope.onclickbutton = function(){
$scope.counter++;
}
})
.directive('testDirective', function(){
return {
restrict: 'E',
scope: {
title: '='
},
template: '<div>this is a {{ title }}</div>',
link: function(scope, element, attrs) {
}
};
});
})();

scope variable of the controller is not recognized in nested directives

I have created two directives and inserted the first directive into the second one. The content of template attribute works fine but the scope variable of the controller is not recognized. Please provide me solution on this
sample link: http://jsbin.com/zugeginihe/2/
You didn't provide the attribute for the second directive.
HTML
<div second-dir first-dir-scope="content">
<div first-dir first-dir-scope="content"></div>
</div>
Link demo: http://jsbin.com/jotagiwolu/2/edit
The best option would using parent directive, We could take use of require option of directive like require: '?secondDir' in firstDir
Code
var myApp = angular.module("myApp", []);
myApp.controller("myController", function($scope) {
$scope.content = "test1";
});
myApp.directive("firstDir", function() {
return {
restrict: "AE",
require: '?secondDir',
scope: {
firstDirScope: "="
},
template: "<div>first content</div>",
link: function(scope, element, attrs, secondDir) {
console.log(scope.firstDirScope, 'first');
}
};
});
myApp.directive("secondDir", function() {
return {
restrict: "AE",
scope: {
firstDirScope: "="
},
controller: function($scope) {
console.log($scope.firstDirScope, 'second');
}
};
});
Working JSBin

Resources