How to use $watch in angularjs component - angularjs

I am using jCarousel for image thumbnail slider. but previously I was using directive for the same but now I changed my code to component. but now I am not able to use that link function and watch reload in component. because I am using first time component in agularjs.
//Previous code
directive('jblogcarousel', function() {
return {
restrict: 'A',
replace: true,
transclude: true,
scope: {
jitems: "="
},
templateUrl: '/templates/blog-carousel.html',
link: function link(scope, element, attrs) {
var container = $(element);
var carousel = container.find('.jcarousel');
carousel.jcarousel({
wrap: 'circular'
});
scope.$watch(attrs.jitems, function (value) {
carousel.jcarousel('reload');
});
container.find('.jcarousel-control-prev')
.jcarouselControl({
target: '-=1'
});
container.find('.jcarousel-control-next')
.jcarouselControl({
target: '+=1'
});
}
};
});
//Current code
.component('jCarousel', {
bindings: {
jitems: '='
},
templateUrl: '/templates/carousel.html'
})

From what I understood, in Angular 1.5 components bindings will bind the value to the controller.
So you can add a controller (with a $watch inside):
// bindings: { ... },
// templateUrl: '...',
controller: function ($scope) {
var vm = this;
console.log(vm.jitems); // vm.jitems should exist and be bound the value you passed to the component from the outside
// you should be able to watch this value like this
$scope.$watch(
function () { return vm.jitems; },
function (newValue) { console.log(newValue); }
);
}
Also, with components, you should in most situations use one way binding '<' instead of two-way binding '=', and use functions/events (binding '&') for the other direction.

Related

Reinitialise directive on change of attribute

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

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 bind my tab to tabContent using AngularJS approach with DevExpress dx-tabs

I have created dx-tabs that are dynamically loaded in the view using custom directives. I am able to output the tabs but I cannot figure out how to bind the cooresponding tab-pane with the tab. I am thinking inside of my ji-Tabset directive in the addTab function I need to somehow create a variable that will loop through my scope.tabs array and then for each integer I need to create an instance of that variable. Then inside of my ji-tab directive I need to push both the text and the content to the scope.tabs array. Any thoughts?
Directive 1 - Ji-Tabset
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTabset', function () {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: function ($scope) {
var tabs = $scope.tabs = [];
$scope.select = function (args) {
var tab = args.itemData;
angular.forEach(tabs, function (tab) {
tab.selected = false;
});
tab.selected = true;
};
$scope.tabSettings = {
bindingOptions: { items: "tabs" },
onItemClick: $scope.select
}
this.addTab = function (tab) {
tabs.push(tab);
};
},
templateUrl: "FormTest/views/ji-Tabset.html",
};
});
}
Directive 2 - Ji-Tab
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
return {
require: '^jiTabset',
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'FormTest/views/ji-Tab.html',
link: function (scope, element, attrs, tabsCtrl) {
scope.text = attrs.tabName;
tabsCtrl.addTab(scope);
}
};
});
}
Ji-Tabset templateUrl
<div dx-tabs='tabSettings'></div><div ng-transclude></div>
Ji-Tab templateUrl
<div ng-show="selected" ng-transclude></div>
Main view
<ji-tabset name="Tabs" label="testing">
<ji-tab tab-name="General">
<ji-button label="Button 1"></ji-button>
</ji-tab>
<ji-tab tab-name="Stats"></ji-tab>
<ji-tab tab-name="Stuff"></ji-tab>
<ji-tab tab-name="Other"></ji-tab>
<ji-tab tab-name="More stuff"></ji-tab>
</ji-tabset>
In case anyone ever runs into this, I was able to figure out the solution.
I had to make a few changes in my directives and use DevExpress' onItemClick event handler itemData. I had to add a view for ji-tab directive and transclude everything inside of it. And also loop through my tabs array with angular.foreach. I have updated the original post with the correct solution.

Angular: controller's methods are not working in directive's template

Here is a directive that is loading new Template from file:
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html'
}
})
Next HTML-element calls this directive from the other HTML-Template (e.g. xxx.html):
<candidates-filter></candidates-filter>
There is next controller for this parent Template (xxx.html):
app.controller('candidatesController', function($scope, $location ){
$scope.addPeson = function() {
$location.url('/candidate/0');
};
});
Method addPerson() is not accessible inside the Directive's template AAAA.html, because
data-ng-click="addPerson()"
is not working there. How to change the Directive to make addPerson() method available inside the directive's template?
TEMPORARY Solution
I fixed this issue by next solution
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html',
controller: function(){
$('button.add').on('click',function(){
location.hash = '#/candidate/0';
});
}
}
})
If I understand the problem correctly:
You can pass a function into the directive for it to use
<candidates-filter></candidates-filter>
becomes
<candidates-filter add-candidate="addPerson()"></candidates-filter>
and the directive definition changed as follows:
.directive('candidatesFilter', function() {
return {
resctict: 'E',
replace: true,
scope: {
addCandidate: '&addCandidate'
}
templateUrl: 'views/directives/filters/AAAA.html'
link: function(scope, element, attrs) {
scope.someFunctionInDirective = function() {
scope.addCandidate();
}
};
}
})
Alternatively you can call it with the ng-click like normal from the button
Hope this helps clarify it?

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