I have "components" (I'm using Angular 1.4.4 so they are directives) that need some data when bootstrapping, passed as an attribute. But sometimes that data is being loaded async.
I actually come to this:
module('whatever').directive('my-directive', function() {
return {
restrict: 'E',
scope: {
valueBinded: '=valueBinded'
},
controller: ['$scope', function($scope) {
// binded value is accesible through scope
var unbindActivateListener = $scope.$watch("valueBinded", function(newVal, oldVal) {
if (typeof newVal !== "undefined") { // Check if valueBinded contains valid information
unbindActivateListener();
activate();
}
});
function activate() {
console.log($scope.valueBinded) // Logs
}
}],
template: '<input type="text" ng-model="valueBinded"/>';
};
});
```
Which constantly watches for value and once is not undefined it calls to activate() method.
Obviously, adding this on each component is not nice. Is there a way where this can be refactored? Or preventing loading a directive until data is retrieved?
Related
Basically I have made custom directive to which i pass Url as string then inside the directive controller i'm calling http.get() method which creates the content inside this directive. What i want is to be able to change the value of annotation-url attribute which in return will change the content inside the directive because the new Url will return different JSON object. But it seems the directive is not getting refreshed when i change the annotationData from the controller.
HTML
<span ng-click="annotatonData = 'data/annotations1.json'">John</span>
<span ng-click="annotatonData = 'data/annotations2.json'">Marry</span>
<ng-annotate user-options="user" annotation-url="{{annotatonData}}"></ng-annotate>
Controller:
app.controller('ngAnnotationsController', ['$scope', '$http', function ($scope, $http) {
//InIt default http get
$scope.annotatonData = 'data/annotations1.json';
}]);
Directive:
app.directive('ngAnnotate', function () {
return {
templateUrl: 'templates/...',
restrict: 'E',
scope: {
annotationUrl: "#"
},
controller: ['$scope', '$http', function ($scope, $http) {
$http.get($scope.annotationUrl).then(function (response) {
$scope.data = response.data;
...
...
First, I suggest you change to
scope: {
annotationUrl: "="
}
the you can add a watcher to invoke $http whenever value is changed
$scope.$watch('annotationUrl', function(newVal, oldVal) {
if (newVal != oldVal) {
$http.get(newVal).then(function (response) {
$scope.data = response.data;
...
...
}
}
However, if you want to keep annotationUrl as it is, you need to use
$attr.$observe('annotationUrl', fn) to catch value changes.
I have a function I'm binding on:
angular.module('app').directive('resizable', function($window) {
return function(scope) {
angular.element($window).bind('resize', function() {
scope.$apply(function() {
//console.log($window.innerWidth);
scope.windowWidth = $window.innerWidth;
});
})
}
});
But this doesn't fire on onload. I need the initial screen width upon page load. How do I get this using Angular?
UPDATE:
I've also tried this ...
angular.module('ccsApp').directive('setSize',
['$document', function($document) {
return {
restrict: 'A',
link: function($scope, elements, attrs) {
$document.on("load", function() {
$scope.$apply(function () {
console.log('initial=');
});
});
}
}}
]
);
This code is in a directive. So you probably don't want the function to be executed when the application is loaded, but only when this directive is used. So, simply execute the function directly in the directive function:
angular.module('app').directive('resizable', function($window) {
return function(scope) {
// define the function
var updateWindowWidth = function() {
// console.log($window.innerWidth);
scope.windowWidth = $window.innerWidth
};
// call it immediately to initialize the scope variable as soon as the directive is used
updateWindowWidth();
// and make sure it's called every time the window is resized
angular.element($window).bind('resize', function() {
scope.$apply(updateWindowWidth);
});
};
});
You should probably also make sure that the event handler is unbound when the directive is destroyed. Otherwise, every time the directive is used, an additional handler is added to the window.
You could inject $window into a .run function of your module. Although there would be no scope available.
It is unclear what your are trying to do with the window size so our ability to provide helpful answers is limited
I have build a directive for pagination that takes two arguments; the current page and the total number of pages.
<pagination page="page" number-of-pages="numberOfPages"></pagination>
The issue is that I will only know the value of numberOfPages after an AJAX call (through ng-resource). But my directive is already rendered before that the AJAX call is done.
app.controller('MyController', function ($scope, $routeParams) {
$scope.page = +$routeParams.page || 1,
$scope.numberOfPages = 23; // This will work just fine
MyResource.query({
"page": $scope.page,
"per_page": 100
}, function (response) {
//This won't work since the directive is already rendered
$scope.numberOfPages = response.meta.number_of_pages;
});
});
I prefer to wait with the rendering of my controllers template until the AJAX call is finished.
Plan B would be to append the template with the directives template when the AJAX call is done.
I'm stuck working out both scenarios.
But isn't it possible to just prevent the rendering until all is done
I think ng-if would do that, contrary to ng-show/ng-hide which just alter the actual display
You have to wait for the value using a $watch function like:
<div before-today="old" watch-me="numberOfPages" >{{exampleDate}}</div>
Directive
angular.module('myApp').directive('myPagingDirective', [
function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
scope.$watch(attr.watchMe,function(newValue,oldValue){
//check new value to be what you expect.
if (newValue){
// your code goes here
}
});
}
};
}
]);
Imporant: Your directive may use an isolated scope but even so the same principle stands.
If you use resolve from ui-router, you can have meta or meta.number_of_pages injected in your controller BEFORE it's view gets rendered.
//routes.js
angular.module('app')
.state('some_route', {
url: '/some_route',
controller: 'MyController',
resolve: {
meta: ['MyResource', function (MyResource) {
return MyResource.query({
"page": $scope.page,
"per_page": 100
}, function (response) {
return response.meta;
});
}]
}
});
//controllers.js
app.controller('MyController', function ($scope, $routeParams, meta) {
$scope.page = +$routeParams.page || 1,
$scope.numberOfPages = meta.number_of_pages;
});
I have a cancel function in my controller that I want to pass or bind to a directive. This function essentially clears the form. Like this:
app.controller('MyCtrl', ['$scope', function($scope){
var self = this;
self.cancel = function(){...
$scope.formName.$setPristine();
};
}]);
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
cancel : '&onCancel'
},
templateUrl: 'form.html'
};
});
form.html
<div>
<form name="formName">
</form>
</div>
However, the $setPristine() don't work as the controller don't have access on the form DOM. Is it possible to extend the functionality of controller's cancel within the directive so that I will add $setPristine()?
Some suggested using jQuery to select the form DOM, (if it's the only way) how to do that exactly? Is there a more Angular way of doing this?
Since the <form> is inside the directive, the controller should have nothing to do with it. Knowing it would break encapsulation, i.e. leak implementation details from the directive to the controller.
A possible solution would be to pass an empty "holder" object to the directive and let the directive fill it with callback functions. I.e.:
app.controller('MyCtrl', ['$scope', function($scope) {
var self = this;
$scope.callbacks = {};
self.cancel = function() {
if( angular.isFunction($scope.callbacks.cancel) ) {
$scope.callbacks.cancel();
}
};
});
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
callbacks: '='
},
templateUrl: 'form.html',
link: function(scope) {
scope.callbacks.cancel = function() {
scope.formName.$setPristine();
};
scope.$on('$destroy', function() {
delete scope.callbacks.cancel;
});
}
};
});
Use it as:
<custom-directive callbacks="callbacks"></custom-directive>
I'm not sure I am OK with this either though...
I want to compile a third-party api (uploadcare) to a directive.
The api will return the data info after uploaded in async then I want to do something with the return data in my controller but I have to idea how to pass the return data from directive to controller. Below is my code.
in js
link: function (scope, element, attrs) {
//var fileEl = document.getElementById('testing');
var a = function() {
var file = uploadcare.fileFrom('event', {target: fileEl});
file.done(function(fileInfo) {
//scope.$apply(attrs.directUpload)
//HERE IS MY PROBLEM.
//How can I get the fileInfo then pass and run it at attrs.directUpload
}).fail(function(error, fileInfo) {
}).progress(function(uploadInfo) {
//Show progress bar then update to node
console.log(uploadInfo);
});
};
element.bind('change', function() {a()});
}
in html
<input type="file" direct-upload="doSomething()">
in controller
$scope.doSomething = function() {alert(fileInfo)};
AngularJS allows to execute expression in $parent context with specified values, in your case doSomething().
Here's what you need to do that:
In directive definition, mark directUpload as expression:
scope: {
directUpload: "&"
}
In done callback, call:
scope.directUpload({fileInfo: fileInfo})
Update markup:
<input type="file" direct-upload="doSomething(fileInfo)">
To summorize: scope.directUpload is now a callback, which executes expression inside attribute with specifeid values. This way you can pass anything into controller's doSomething.
Read $compile docs for detailed explanation and examples.
Example you might find useful:
angular
.module("app", [])
.directive("onDone", function ($timeout) {
function link (scope, el, attr) {
$timeout(function () {
scope.onDone({
value: "something"
});
}, 3000)
}
return {
link: link,
scope: {
onDone: "&"
}
}
})
.controller("ctrl", function ($scope) {
$scope.doneValue = "nothing";
$scope.done = function (value) {
$scope.doneValue = value;
};
})
<body ng-controller="ctrl">
Waiting 3000ms
<br>
<div on-done="done(value)">
Done: {{doneValue}}
</div>
</body>
You can pass through an object to the scope of the directive using = within the directive to do two way data binding. This way you can make updates to the data within the directive on the object and it will be reflected in it's original location in the controller. In the controller you can then use $scope.watch to see when the data is changed by the directive.
Something like
http://plnkr.co/edit/gQeGzkedu5kObsmFISoH
// Code goes here
angular.module("myApp",[]).controller("MyCtrl", function($scope){
$scope.something = {value:"some string"}
}).directive("simpleDirective", function(){
return {
restrict:"E",
scope:{someData:"="},
template:"<button ng-click='changeData()'>this is something different</button>",
link: function(scope, iElem, iAttrs){
scope.changeData=function(){
scope.someData.value = "something else";
}
}
}
});