Reinitialise directive on change of attribute - angularjs

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();
}

Related

How to pass ng-model value in radio button from ng-repeat into link function on directive

I want one of the Radio Button to be selected once the page is loaded, from another question on stackoverflow i found that Radio Button will be check if the value of the input attribute is equal to the value of model applied on the Radio Button. But i am unable to access the model($parent.selectedItem) on Radio Button in link function inside child directive. Api i used in example is a placeholder but in realtime i will have a property selected which will be true/false which I want to bind to the $parent.selectedItem
var mainApp = angular.module('mainApp', []);
mainApp.factory('myFactory', function ($http) {
var myFactory = {
myMethod: function () {
var promise = $http.get('https://jsonplaceholder.typicode.com/users').then(function (response) {
return response.data;
});
return promise;
}
};
return myFactory;
});
Controller:
mainApp.controller('myController', function ($scope, myFactory) {
myFactory.myMethod().then(function (result) {
$scope.data = result
})
});
Directives:
mainApp.directive('parent', function (myFactory) {
return {
restrict: 'E',
replace: true,
scope: true,
templateUrl: 'parent.html',
link: function (scope, element, attrs, ctrl) {
myFactory.myMethod().then(function (result) {
scope.Model = result
})
}
}
});
mainApp.directive('child', function () {
return {
restrict: 'E',
scope: {
Model: '=ngModel'
},
replace: true,
require: 'ngModel',
templateUrl: 'child.html',
link: function (scope, element, attrs, ctrl) {
// unable to access scope.selectedItem
console.log(scope.selectedItem)
}
}
});
HTML:
// mainpage.html
<body ng-app="mainApp"><parent></parent></body>
//parent.html
<div><child ng-model = "Model"></child></div>
//child.html
<div ng-repeat="item in Model"><input type="radio" name="itemSelected"
ng-value="item" ng-model="$parent.selectedItem"/>{{item.name}}</div>
when you require ngModel in the child directive, what you're basically requiring is its controller, this controller is then injected into your link function as the 4th parameter, in your case the ctrl argument.
so right now your ngModel might work, but it is not in your link function because you're expecting it to exist on the scope as selectedItem, but on your scope you have declared it as Model (not selectedItem). However, you also have access to the ngModel controller, so you could ask for its value there through its controller: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController.
ex:
ctrl.$viewValue
// or
ctrl.$modelValue
//whichever serves your purpose

Set directive data after ajax request

In my page controller I get data from an ajax call using ngResource:
clientResource.query(
{
searchText: vm.search.text,
pageSize: vm.pageSize
},
(data, headers) => {
vm.clients = data;
vm.headers = JSON.parse(headers("X-Pagination"))
// ...
I have a directive for the pagination which is simply:
<ix-pager headers="vm.headers"></ix-pager>
In the directive controller, I have:
function ixPagerController($scope) {
var vm = this;
vm.headers = $scope.headers;
}
Now when the directive renders and the directive controller fires, $scope.headers is undefined, which is because the AJAX call hasn't returned yet. But when it does and vm.headers is set, this doesn't update the model on the directive. So I can change my directive to use a link function with a watch statement, like so:
return {
templateUrl: "/app/partials/pager.html",
restrict: "E",
controller: ixPagerController,
controllerAs: "vm",
replace: true,
link: function (scope, element, attrs) {
scope.$watch("headers", function (newValue, oldValue) {
if (newValue) {
//scope.headers = newValue;
}
});
},
scope: {
headers:"="
}
}
The problem is, at the commented out line, if I set a breakpoint, the scope.headers value is ALREADY the correct value, i.e. it has already been set. However, on the directive, template:
<pre>{{vm.headers|json}}</pre>
Still shows nothing. It's almost as if there's a missing digest or something. How do I get the model on the directive to update the view correctly?
This is what seems to work:
link: function (scope, element, attrs, ctrl) {
var c = ctrl;
scope.$watch("headers", function (newValue, oldValue) {
if (newValue) {
c.headers = newValue;
}
});
},

how to make angular directive accept string or model respectively

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>'
}
}]);

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;
}
};
}]);

How to use require option in directives

In documentation I can read next for the require option:
When a directive uses this option, $compile will throw an error
unless the specified controller is found. The ^ prefix means that this
directive searches for the controller on its parents (without the ^
prefix, the directive would look for the controller on just its own
element).
So I try to use it:
<div ng-sparkline></div>
app.directive('ngCity', function() {
return {
controller: function($scope) {}
}
});
app.directive('ngSparkline', function() {
return {
restrict: 'A',
require: '^ngCity',
scope: {},
template: '<div class="sparkline"><h4>Weather </h4></div>',
link: function(scope, iElement, iAttrs) {
// get weather details
}
}
});
But I have an error if my html have not ng-city attribute, so if I need controller of another directive - need to add exactly same attribute in html, but why (<div ng-sparkline ng-city="San Francisco"></div>)? And it looks on another directive's controller with this name (directive!!!) but not at controller with this name, is that true? Thanks. Just want to make it clear
With require you can get the controller of another (cooperating) directive. The controller in Angular is not semantically a function, but an object constructor, i.e. called essentially as var c = new Controller() (this is a simplification for the sake of clarity). Since the controller is an object, it can have properties and methods. By requiring the controller of another directive, you gain access to those properties/methods. Modifying your example to demonstrate:
app.directive('ngCity', function() {
return {
controller: function($scope) {
this.doSomething = function() {
...
};
}
}
});
app.directive('ngSparkline', function() {
return {
restrict: 'A',
require: '^ngCity',
scope: {},
template: '<div class="sparkline"><h4>Weather </h4></div>',
link: function(scope, iElement, iAttrs, ngCityController) {
// use the controller, e.g.
ngCityController.doSomething();
}
}
});
In your case, the city would be a property of the controller of the ngCity directive, exposed as a property. It will be read by the ngSparkline to know for which city the graph is about.
<b> added directives.js</b>
<code>
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngCity',
scope: {
ngCity: '#'
},
templateUrl: '/scripts/templates/tpl.html',
controller: ['$scope', '$http', function ($scope, $http) {
var url = "https://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=imperial&cnt=7&callback=JSON_CALLBACK&q=";
console.log(url + $scope.ngCity);
$scope.showTemp = function () {
$scope.getTemp($scope.ngCity);
};
$scope.getTemp = function (city) {
$http({
method: 'JSONP',
url: url + city
}).success(function(data) {
var weather = [];
angular.forEach(data.list, function(value){
weather.push(value);
});
$scope.weather = weather;
});
}
}],
link: function (scope, iElement, iAttrs, ctrl) {
scope.getTemp(iAttrs.ngCity);
scope.$watch('weather', function (newVal) {
if (newVal) {
var highs = [];
angular.forEach(scope.weather, function (value) {
highs.push(value.temp.max);
});
//chartGraph(iElement, highs, iAttrs);
}
});
}
}
}).directive('ngCity', function () {
return {
controller: function ($scope) {
//console.log("hello");
}
}
});
</code>
<b> and added tpl.htm</b>
<code>
<div class="sparkline">
<input type="text" data-ng-model="ngCity">
<button ng-click="showTemp()" class="btn1">Check {{ngCity}}</button>
<div style="color:#2743EF">{{weather}} ÂșC</div>
<div class="graph"></div>
</div>
</code>

Resources