I have a directive that gets its data from a factory. It works fine, when loads first time. But later, when the factory data changed, directive do not react on this changes. How can i fix it?
appWD.directive('widgetName', ['WidgetData', function(WidgetData) {
return {
restrict: 'E',
templateUrl: '_widget.html',
link: function(scope, elem, attrs) {
scope.data = WidgetData.GetWidgetData('widgetName');
//both do not work
//scope.$watch(scope.data);
//scope.$watch(WidgetData.GetWidgetData('widgetName'));
}
};
}]);
GetWidgetData('widgetName') returns object.
codepen example: http://codepen.io/anon/pen/xwLLxM?editors=101
Try using
scope.$watchCollection(function(){return WidgetData.getWidgetData('widget');}, function() {
alert('scope.$watchCollection');
});
$watchCollection "shallow watches the properties of an object and fires whenever any of the properties change" AngularJs Docs
I created a small app to replicate your problem. Here is the Demo
setTimeout(function(){
$scope.$apply()
},0);
put this after data updation, this vl update UI
May work
Related
I'm trying to create a "Clock" directive as a way to learn Angular. How would I go about making sure it "ticks?"
Here's what I tried fiddle Link:
angular.module('app')
.directive('clock' , function(){
return {
restrict: 'E',
template: '<div>{{date}}</div>',
link: function(scope, elem, attr){
scope.date = getDate();
setInterval(function(){
scope.date = getDate();
}, 100);
}
}
})
Using setInterval to upate the scope variable isn't working. I'm assuming because the directive isn't watching for it to be changes anymore?
It is updating the scope variable, but Angular isn't aware of the change.
It is recommended to use Angular $interval.
Here is your fiddle with $interval: https://jsfiddle.net/oafcde6g/11/
In other words, Angular doesn't do any magic by "listening" to scope properties, it simply does dirty-checking. But to do it, it needs to be notified that something changed. So if you really wanted to use setInterval instead of $interval, you could notify it manually that something changed:
setInterval(function () {
scope.$apply(function () {
scope.date = getDate();
});
});
This way, you're notifying it to run your anonymous function inside $apply and then digest the scope, checking if anything changed. If it did, it will update the view.
"Magic" of ng-* events (ng-click, ng-model etc)
Even though it looks like angular automatically knows to rerender the view when you click/change something by using it's built-in directives, it really isn't so.
Some simple implementation of ng-click would look something like:
angular.module('app')
.directive('ngClick' , function(){
return {
restrict: 'A',
link: function(scope, elem, attr){
elem.on('click', function () {
scope.$apply(function () {
scope.$eval(attr.ngClick);
});
});
}
}
})
You can see this for yourself if you try to bind click event normally, outside of angular context and make it change a scope variable. It won't update until you explicitly tell it to (like we did in setInterval example).
I am just getting started with angular and ran into the directive below. I read a few tutorials already and am reading some now, but I really don't understand what "require: ngModel" does, mainly because I have no idea what ngModel does overall. Now, if I am not insane, it's the same directive that provides two way binding (the whole $scope.blah = "blah blah" inside ctrl, and then {{blah}} to show 'blah blah' inside an html element controlled by directive.
That doesn't help me here. Furthermore, I don't understand what "model: '#ngModel' does. #ngModel implies a variable on the parents scope, but ngModel isn't a variable there.
tl;dr:
What does "require: ngModel" do?
What does "model : '#ngModel'" do?
*auth is a service that passes profile's dateFormat property (irrelevant to q)
Thanks in advance for any help.
angular.module('app').directive('directiveDate', function($filter, auth) {
return {
require: 'ngModel',
scope: {
model : '#ngModel',
search: '=?search'
},
restrict: 'E',
replace: true,
template: '<span>{{ search }}</span>',
link: function($scope) {
$scope.set = function () {
$scope.text = $filter('date')($scope.model, auth.profile.dateFormat );
$scope.search = $scope.text;
};
$scope.$watch( function(){ return $scope.model; }, function () {
$scope.set();
}, true );
//update if locale changes
$scope.$on('$localeChangeSuccess', function () {
$scope.set();
});
}
};
});
ngModel is an Angular directive responsible for data-binding. Through its controller, ngModelController, it's possible to create directives that render and/or update the model.
Take a look at the following code. It's a very simple numeric up and down control. Its job is to render the model and update it when the user clicks on the + and - buttons.
app.directive('numberInput', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<span></span><button>+</button><button>-</button>',
link: function(scope, element, attrs, ngModelCtrl) {
var span = element.find('span'),
plusButton = element.find('button').eq(0),
minusButton = element.find('button').eq(1);
ngModelCtrl.$render = function(value) {
updateValue();
};
plusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue + 1);
updateValue();
});
minusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue - 1);
updateValue();
});
function updateValue(value) {
span.html(ngModelCtrl.$modelValue);
}
}
};
});
Working Plunker
Since it interacts with the model, we can use ngModelController. To do that, we use the require option to tell Angular we want it to inject that controller into the link function as its fourth argument. Now, ngModelController has a vast API and I won't get into much detail here. All we need for this example are two methods, $render and $setViewValue, and one property, $modelValue.
$render and $setViewValue are two ways of the same road. $render is called by Angular every time the model changes elsewhere so the directive can (re)render it, and $setViewValue should be called by the directive every time the user does something that should change the model's value. And $modelValue is the current value of the model. The rest of the code is pretty much self-explanatory.
Finally, ngModelController has an arguably shortcoming: it doesn't work well with "reference" types (arrays, objects, etc). So if you have a directive that binds to, say, an array, and that array later changes (for instance, an item is added), Angular won't call $render and the directive won't know it should update the model representation. The same is true if your directive adds/removes an item to/from the array and call $setViewValue: Angular won't update the model because it'll think nothing has changed (although the array's content has changed, its reference remains the same).
This should get you started. I suggest that you read the ngModelController documentation and the official guide on directives so you can understand better how this all works.
P.S: The directive you have posted above isn't using ngModelController at all, so the require: 'ngModel' line is useless. It's simply accessing the ng-model attribute to get its value.
I am relatively new to Angular.
I have a html document in which angular creates a html table with ng-repeat. When this table has been built, I would like to apply to it a Jquery function. How can I do that ?
function : $("#creneaux").footable()
If I apply the function in the controller when it is instantiated, nothing happens. when I apply it in the javascript console when the page has been displayed, it works.
Firstly, I would move the $("#creneaux").footable() into a directive.
Solution:
Use $timeout without a delay to (a bit simplified) put the action at the end of the browser event queue after the rending engine:
app.directive('tableCreator', function($timeout) {
return {
link: function(scope, element, attrs) {
$timeout(function() {
$("#creneaux").footable();
});
}
};
});
Demo: http://plnkr.co/edit/b05YKhipeVmrVHu2Xzsm?p=preview
Good to know:
Depending on what you need to perform, you can instead use $evalAsync:
app.directive('tableCreator', function($timeout) {
return {
link: function(scope, element, attrs) {
scope.$evalAsync(function() {
$("#creneaux").footable();
});
}
};
});
The difference is that now the code will run after the DOM has been manipulated by Angular, but before the browser re-renders.
This can in certain cases remove some flickering that might be apparent between the rendering and the call to for example the jQuery plugin when using $timeout.
In the case of FooTable, the plugin will run correctly, but the responsiveness will not kick in until the next repaint, since the correct dimensions are not available until after rendering.
Try writing a directive.
app.directive('sample', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
// your jquery code goes here.
},
};
});
Learn to write everything in angular instead jquery. This may help you "Thinking in AngularJS" if I have a jQuery background?
I've been trying to implement Selectize with AngularJS (1.2.4). I'm using this directive to interface with the plugin and everything is working smoothly until now. When using the ngModel from a normal select box It works fine, and returns the expected object but when I try to use it with the multiple attribute, it won't set the model.
I've inspected the DOM and appears the script removes unselected options from the hidden select and that might be messing with the angular binding.
I've created a Plunkr to demonstrate the behaviour.
http://plnkr.co/It6C2EPFHTMWOifoYEYA
Thanks
As mentioned in the comments above, your directive must listen to changes in the selectize plugin and then inform angular of what happened via ng-model.
First, your directive needs to ask for an optional reference to the ngModel controller with the following:
require: '?ngModel'.
It is injected into your link function as an argument in the 4th position:
function(scope,element,attrs,ngModel){}
Then, you must listen for changes in selectize with
$(element).selectize().on('change',callback)
and inform ngModel with ngModel.$setViewValue(value)
Here is a modified version of your directive. It should get you started.
angular.module('angular-selectize').directive('selectize', function($timeout) {
return {
// Restrict it to be an attribute in this case
restrict: 'A',
// optionally hook-in to ngModel's API
require: '?ngModel',
// responsible for registering DOM listeners as well as updating the DOM
link: function(scope, element, attrs, ngModel) {
var $element;
$timeout(function() {
$element = $(element).selectize(scope.$eval(attrs.selectize));
if(!ngModel){ return; }//below this we interact with ngModel's controller
//update ngModel when selectize changes
$(element).selectize().on('change',function(){
scope.$apply(function(){
var newValue = $(element).selectize().val();
console.log('change:',newValue);
ngModel.$setViewValue(newValue);
});
});
});
}
};
});
Also:
plunker
angular docs for ngModelController
I really love how the new ng-click directive in Angular now automatically includes functionality for touch events. However, I am wondering if it is possible to access that touch-event service from my custom directive? I have lots of directives that require that I bind a click event to the given element, but I'm simply doing that using the typical jquery syntax (ex: element.on('click', function(){ ... })). Is there a way that I can bind an ng-click event to an element within a directive? Without having to manually put a ng-click tag on my element in the HTML of my view...?
I want to be able to harness the power of both click and touch events. I could obviously import a library (such as HammerJS or QuoJS) but I would prefer not to have to do that, especially since Angular is already doing it.
I can access the $swipe service and bind different elements to that, but is there a similar service for ngTouch?
For reference, this is an example of when I would want to do this:
mod.directive('datepicker', ['$timeout', function($timeout){
return {
link: function(scope, elem, attrs){
var picker = new DatePicker();
elem.on('click', function(e){
picker.show();
});
// I would rather do something like:
// elem.on('ngTouch', function(){ ... });
//
// or even:
// $ngTouch.bind(elem, {'click': ..., 'touch': ...});
}
}
}]);
UPDATE: As noted by below, the source code for the ng-click directive is here. Can anyone see a way to harness that code and turn it into a "bindable" service?
I don't think that's quite the right approach. I'd approach this by using a template within your directive and then using ngTouch within that.
mod.directive('datepicker', ['$timeout', function ($timeout) {
return {
template: '<div ng-touch="doSomethingUseful()"></div>',
link: function (scope, elem, attrs) {
var picker = new DatePicker();
scope.doSomethingUseful = function () {
// Your code.
}
}
}
}]);
UPDATE
Full example with additional attributes on the directive element:
http://codepen.io/ed_conolly/pen/qJDcr