I am trying to create an angular directive that will be able to get BOTH model object and a string.
if the directive get a string it just output HTML, but if it's a model the the directive will watch the model for changes and will output data respectively.
I had tried to use the next code:
App.directive('iso2symbol', function () {
return {
restrict: 'E',
replace: true,
link: function ($scope, $element, $attrs) {
var curIsoObj = $scope.$eval($attrs.curIso);
//this is object it may change
if (typeof curIsoObj !== 'undefined') {
console.log('not a text');
$scope.$watch('curIso', function (value) {
console.log(value);
});
}
},
template: '<span>{{currencySymbol}}</span>'
}
}]);
This is not working, I had googled it for long time and I don't find the problem....
here is a link to JSfiddle where I had set a DEMO
Becareful with what you're watching.
according to your watch function you're watching $scope.curIso which really isn't a scope object.
you should be watching
$scope.$watch(function(){return $scope.$eval($attrs.curIso);}, function (value) {
$scope.txt = value;
});
Try this:
App.directive('iso2symbol', function () {
return {
restrict: 'E',
replace: true,
require: 'ngModel',
scope: {
curIso: '='
},
link: function ($scope, $element, $attrs) {
$scope.$observe('curIso', function(newValue, oldValue){
var curIsoObj = newValue;
// Do your test now to see if it's undefined,
// a string, or generic object.
// (the first time it will likely be undefined)
}
},
template: '<span>{{currencySymbol}}</span>'
}
}]);
Related
I am trying to implement a directive with its own model and change attribute (as an overlay for ng-model and ng-change). It works apparently fine but when the function of the father scope is executed and some variable of the scope is modified in it, it is delayed, the current change is not seen if not the one executed in the previous step.
I have tried adding timeouts, $apply, $digest ... but I can not get it synchronized
angular.module('plunker', []);
//Parent controller
function MainCtrl($scope) {
$scope.directiveValue = true;
$scope.textValue = "init";
$scope.myFunction =
function(){
if($scope.directiveValue === true){
$scope.textValue = "AAAA";
}else{
$scope.textValue = "BBBB";
}
}
}
//Directive
angular.module('plunker').directive('myDirective', function(){
return {
restrict: 'E',
replace: true,
scope: {
myModel: '=model',
myChange: '&change'
},
template: '<span>Check<input ng-model="myModel" ng-change="myChange()"
type="checkbox"/></span>',
controller: function($scope) {
},
link: function(scope, elem, attr) {
var myChangeAux = scope.myChange;
scope.myChange = function () {
setTimeout(function() {
myChangeAux();
}, 0);
};
}
});
// Html
<body ng-controller="MainCtrl">
<my-directive model="directiveValue" change="myFunction()"></my-directive>
<div>Valor model: {{directiveValue}}</div>
<div>Valor texto: {{textValue}}</div>
</body>
The correct result would be that the "myFunction" function runs correctly
Example: https://plnkr.co/edit/q3IqRCIhwLChlGrkDxyO?p=preview
You should use AngularJS' $timeout which is a wrapper for the browser default setTimeout and internally calls setTimeout as well as $digest, all at the right time in the execution.
Your directive code should change as such:
angular.module('plunker').directive('myDirective', function($timeout){
return {
restrict: 'E',
replace: true,
scope: {
myModel: '=model',
myChange: '&change'
},
template: '<span>Check<input ng-model="myModel" ng-change="myChange()" type="checkbox"/></span>',
controller: function($scope) {
},
link: function(scope, elem, attr) {
var myChangeAux = scope.myChange;
scope.myChange = function () {
$timeout(myChangeAux, 0);
};
}
};
});
Docs for AngularJS $timeout
I have two directives, calling 2nd directive from 1st directive.
This is my 1st directive
var initializeWidget = function ($compile, $timeout, $rootScope) {
return {
restrict: 'EA',
scope: {
maxImages: '#',
},
link: function (scope, element, attrs) {
if (!scope.cloudinaryFolder) {
throw 'folder value is missing in image uploader directive';
}
if (!scope.cloudinaryTags) {
throw 'tags value is missing in image uploader directive';
}
//1
attrs.$observe('maxImages', function (newMaxImages) {
console.log('varun==' + newMaxImages);
$timeout(function () {
angular.element(document.body).append($compile('<div class="sp-upload-widget" sp-upload-widget up-max-images="' + scope.maxImages + '"></div>')(scope));
scope.$apply();
}, 10);
});
}
};
};
I am calling my 2nd directive usixng angular.element used in above code.
Below is my 2nd directive:
var spUploadWidget = function ($q, Cloudinary, ENV) {
var templateUrl;
if ('dev' === ENV.name) {
templateUrl = '/seller/modules/uploadWidget/views/upload.html';
}
else if ('prod' === ENV.name) {
templateUrl = './views/upload.html';
}
return {
restrict: 'AE',
scope: {},
bindToController: {
maxImages: '=?upMaxImages',
},
replace: false,
controller: 'uploadWidgetController',
controllerAs: 'up',
templateUrl: templateUrl,
};
};
now in my controller when I am checking value of maxImages then it is giving the updated value but when I am using this variable to call API then it is holding the older value. Here is my controller
console.log('up===' + self.maxImages);
self.openUploader = function () {
self.closeModal();
ABC.UploaderInit( self.maxImages);
};
So when I change the value of maxImages in my directive
<div initialize-widget max-images="maxImages"></div>
It should give the updated value to my ABC.UploaderInit function
Found a solution for my problem,
I was getting this problem because I was calling 2nd directive whenever attribute of 1st directive was changing so I was creating multiple instances of my directive.
So now to handle this I am destroying the older instance of 2nd directive before I call the 2nd directive.
$rootScope.$on('destroySpUploadWidget', function (event, args) {
if (args.modalId === ctrl.modalId) {
scope.$destroy();
element.remove();
}
How can I ensure that data from a controller has been loaded in a directive before the link function is run?
Using psuedo-code, I could have:
<my-map id="map-canvas" class="map-canvas"></my-map>
for my html.
In my directive I might have something like this:
app.directive('myMap', [function() {
return{
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: function ($scope, PathService) {
$scope.paths = [];
PathService.getPaths().then(function(data){
$scope.paths = data;
});
},
link: function(scope, element, attrs){
console.log($scope.paths.length);
}
}
}]);
The above won't work because console.log($scope.paths.length); will get called before the service has returned any data.
I know I can call the service from the link function but would like to know if there is a way to "wait" for the service call before firing the link function.
The easiest solution would be to use ng-if since the element and directive would be rendered only when the ng-if is resolved as true
<my-map id="map-canvas" class="map-canvas" ng-if="dataHasLoaded"></my-map>
app.controller('MyCtrl', function($scope, service){
$scope.dataHasLoaded = false;
service.loadData().then(
function (data) {
//doSomethingAmazing
$scope.dataHasLoaded = true
}
)
})
or use promises
return {
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: function ($scope, PathService) {
$scope.paths = [];
$scope.servicePromise = PathService.getPaths()
},
link: function (scope, element, attrs) {
scope.servicePromise.then(function (data) {
scope.paths = data;
console.log(scope.paths)
});
}
}
app.directive('MyDirective', function() {
return {
controller: function() {
this.$postLink = function() {
// here will run after the link function,
// and also after the binding came in
};
},
controllerAs: 'vm'
};
});
check out the angular 1.5 Components have a well-defined lifecycle and it works on directives to
I have a controller-value that is bound to a value in my directive with "=".
But when i update the value in the directive, and then call a function in the controller, the controller-value does not get updated before det controller function is executed.
This is how i implemented it:
HTML:
<my-directive page="searchCriterias.page" search-parent="search()" ></my-directive>
JavaScript:
app.controller('MainCtrl', function ($scope) {
$scope.searchCriterias: {page: 1};
$scope.search = function() {
SearchService.findCasesByCriteria($scope.searchCriterias).then(function (res) {
//show search result
}, function (e) {
});
}
});
app.directive('myDirective', function () {
return {
restrict: 'E',
template: "<div><button ng-click='nextPage()'>next</button></div>",
replace: true,
scope: { page: '=', searchParent: '&searchParent' }
},
link: function ($scope, element, attr) {
$scope.nextPage(){
$scope.page++;
$scope.searchParent();
}
}
}
)
The problem is that page is not updated before search function executed, so findCasesByCriteria is being excecuted with wrong page number. (If i ad timeout before calling $scope.searchParent() in the directive, the value gets updated before the controller-function gets called)
I think your problem is due to you are in a child scope. See here https://egghead.io/lessons/angularjs-the-dot for more infos.
Can you try with this :
<my-directive search-criterias="searchCriterias" search-parent="search()" ></my-directive>
and :
app.directive('myDirective', function () {
return {
restrict: 'E',
template: "<div><button ng-click='nextPage()'>next</button></div>",
replace: true,
scope: { searchCriterias: '=', searchParent: '&searchParent' },
link: function ($scope, element, attr) {
$scope.nextPage(){
$scope.searchCriterias.page++;
$scope.searchParent();
}
}
}
);
There are two ideas that come to me straight away.
You could pass the controller function to the isolate scope using '&' instead of '='. By doing this you could call the function from the directive confident that the value has changed
Otherwise you could set a $watch on the variable in the controller that would call the function when the value changes
I think no need to do anything. You just need to add $.apply() function before calling the function for directive.
app.directive('myDirective', function () {
return {
restrict: 'E',
template: "<div><button ng-click='nextPage()'>next</button></div>",
replace: true,
scope: { page: '=', searchParent: '&searchParent' }
},
link: function ($scope, element, attr) {
$scope.nextPage(){
$scope.page++;
$scope.$apply();
$scope.searchParent();
}
}
}
)
That's it It should work!!
Is it possible to "watch" for ui changes on the directive?
something like that:
.directive('vValidation', function() {
return function(scope, element, attrs) {
element.$watch(function() {
if (this.hasClass('someClass')) console.log('someClass added');
});
}
})
Yes. You can use attr.$observe if you use interpolation at the attribute.
But if this is not an interpolated attribute and you expect it to be changed from somewhere else in the application (what is extremely not recommended, read Common Pitfalls), than you can $watch a function return:
scope.$watch(function() {
return element.attr('class');
}, function(newValue){
// do stuff with newValue
});
Anyway, its probably that the best approach for you would be change the code that changes the element class. Which moment does it get changed?
attrs.$observe('class', function(val){});
You can also watch variable in the controller.
This code automatically hides notification bar after some other module displays the feedback message.
HTML:
<notification-bar
data-showbar='vm.notification.show'>
<p> {{ vm.notification.message }} </p>
</notification-bar>
DIRECTIVE:
var directive = {
restrict: 'E',
replace: true,
transclude: true,
scope: {
showbar: '=showbar',
},
templateUrl: '/app/views/partials/notification.html',
controller: function ($scope, $element, $attrs) {
$scope.$watch('showbar', function (newValue, oldValue) {
//console.log('showbar changed:', newValue);
hide_element();
}, true);
function hide_element() {
$timeout(function () {
$scope.showbar = false;
}, 3000);
}
}
};
DIRECTIVE TEMPLATE:
<div class="notification-bar" data-ng-show="showbar"><div>
<div class="menucloud-notification-content"></div>