I have a ng-repeat that will repeat a directive which does a $http call. then it will display the returned data, but I can't seem to stop the directives from updating each other. Here is the idea:
MAIN
...ng-repeat='item in itemList'...
... my-directive item="item.url"....
my-directive template
...{{result.count}}..
this returns the template updated only when the scope has changed.
.directive('myDirective', function($parse, $http) {
return {
restrict: 'E',
scope: {
item : '='
},
replace: true,
transclude: false,
templateUrl: '...template...',
controller: function($scope) {
$http.get(item.url).success(function(data) {
result.count=data.count;
})
}
at times, the result .count is the same through out the ng-repeat. I will like to know if I am doing something wrong, or maybe there is a way around this?
note: I have checked the results from the http call, and they are different all the time. also there are no syntax errors.
Use link in the directive and isolate your scope like:
.directive('myDirective', function($parse, $http) {
return {
restrict: 'E',
scope: true,
replace: true,
transclude: false,
templateUrl: '...template...',
link: function (scope, element, attrs) {
$http.get(attrs.item).success(function(data) {
scope.result.count=data.count;
}),
}
}
})
Read more about link and isolated scopes on https://docs.angularjs.org/guide/directive
Like someone said on comments using that is not the best way cos you a doing a GET for each item so if you have 10 items you will do a GET 10 times. If you can do the get 1 time on your controller and fetch all the data that you need.
Related
question regarding constants within angularjs. I have the following constants created within app.js:
... angular
.module('blocTime', ['firebase', 'ui.router'])
.config(config)
.constant('STOP_WATCH', {
"workTime": 1500,
"breakTime": 300
});
})();
I've injected the constant inside my directive as follows:
(function() {
function clockTimer($interval, $window, STOP_WATCH) {
return {
templateUrl: '/templates/directives/clock_timer.html',
replace: true,
restrict: 'E',
scope: {},
link: function(scope, element, attributes) {
console.log(STOP_WATCH.workTime); ...
...
angular
.module('blocTime')
.directive('clockTimer', clockTimer);
I can console log the constant from my directive just fine. However, my view is not rendering the constant. HTML:
<div>
<div class="stop-watch">{{ STOP_WATCH.workTime }}</div>
It comes back as undefined. Thoughts as to why or how to make it display in the view? Thanks
Figured it out. Within my directive I had to add scope.STOP_WATCH = STOP_WATCH:
(function() {
function clockTimer($interval, $window, STOP_WATCH) {
return {
templateUrl: '/templates/directives/clock_timer.html',
replace: true,
restrict: 'E',
scope: {},
link: function(scope, element, attributes) {
scope.STOP_WATCH = STOP_WATCH;
...
I have a Directive:
var ActorDisplayDirective = function() {
return {
replace : false,
restrict : 'AE',
scope : {
actor : "="
},
templateUrl: staticContext + '/angular-app/templates/actor-display-template.html',
link : function(scope, elem, attrs) {
},
}
};
This works fine in some places, but not others. Here is my code to show it where it is not working:
<p>CAP: {{can_approve_for}}</p>
<p>
Actor display template:
<span actor-display actor='can_approve_for'></span>
After template
</p>
The CAP: ... displays the data, the directive's actor value is null. Why? My controller does:
dataFactory.getCanApproveFor().then(function(data) {
$scope.can_approve_for = data;
});
So, I am able to see the value on the page, but the directive does not show it. I'm assuming it's a timing/refresh thing, but this directive works elsewhere in ng-repeat, because the ng-repeat evaluates after hte object is already set, I guess. How do I do it in this case?
You are not actually declaring ActorDisplayDirective as a directive. Its just a plain function that returns an object that sort of looks like a directive.
You have to tell angular that it is a directive like so:
angular.module('someModule', [])
.directive('actorDisplay', function () {
return {
replace: false,
restrict: 'AE',
scope: {
actor: "="
},
templateUrl: staticContext + '/angular-app/templates/actor-display-template.html',
link: function (scope, elem, attrs) {
},
}
})
I have a directive i'm using to do the same search filtering across multiple pages. So the directive will be using a service and get pretty hefty with code. Because of that I want to link to a controller instead of have the controller inside the directive like this:
.directive('searchDirective', function($rootScope) {
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search'
};
});
I also want access to parent scope data inside the template, so I don't want to use a isolated scope.
Anyway here's what i'm not sure how to do. My directive looks like this:
<search-directive filter="foo"/>
How do I pass in the value in the filter attribute so that I can access it in my controller using $scope.filter or this.filter?
If I were using an isolated scope it'd be simple. If i had the controller in the same page I could use $attrs. But since i'm using a controller from another spot and don't want an isolated scope i'm not sure how to get the attrs values into the controller.
Any suggestions?
What about using the link function and passing the value to the scope?
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search',
link: function (scope, element, attr) {
scope.filter = attr.filter;
}
};
searchDirective.js
angular
.module('searchDirective', []).controller('SearchCtrl', SearchCtrl)
.directive('SearchDirective', directive);
function directive () {
var directive = {
templateUrl:'searchtemplate.html',
restrict: "E",
replace: true,
bindToController: true,
controller: 'searchCtrl as search',
link: link,
scope: { filter:'=' } // <-- like so here
};
return directive;
function link(scope, element, attrs) {}
}
SearchCtrl.$inject = [
'$scope',
'$filter'];
function SearchCtrl(
$scope,
$filter) {
/** Init SearchCtrl scope */
/** ----------------------------------------------------------------- */
var vs = $scope;
// ....
Also I highly recommend checking out this AngularJS style guide, how you are writing your directive above is how I use to do it too. John Papa shows some way better ways: https://github.com/johnpapa/angular-styleguide
Directives:
https://github.com/johnpapa/angular-styleguide#directives
Controllers:
https://github.com/johnpapa/angular-styleguide#controllers
Flip the values of bindToController and scope around.
{
....
scope: true,
bindToController: { filter:'=' }
...
}
I have just hit the same issue over the weekend, and made a simple complete example here: bindToController Not Working? Here’s the right way to use it! (Angular 1.4+)
As mentioned in the title in my angular application due to the below approach it leads to creation of many watch, i want to find some alternative methods for this.
<div ng-app="myapp">
<first></first>
</div>
var myApp = angular.module('myapp', []);
myApp.directive('first', [
function() {
return {
restrict: 'AE',
replace: true,
transclude: true,
template: '<div id="first"><second id="second" param="paramData"></second></div>',
scope: {
},
controller: [
'$scope',
'$element',
'$attrs',
function($scope, $element, $attrs) {
}
],
link: function(scope, element, attrs, ctrl,$timeout) {
scope.paramData = "Test";
scope.updateParamData = function(){
scope.paramData = "TimeOut";
};
//$timeout(scope.updateParamData,5000);
}
};
}
]);
myApp.directive('second', [
function() {
return {
restrict: 'AE',
replace: true,
template: '<div></div>',
scope: {
param: '=param'
},
controller: [
'$scope',
'$element',
'$attrs',
function($scope, $element, $attrs) {
console.log("inside controller",$scope.param);
}
],
link: function(scope, element, attrs) {
console.log("inside link",scope.param);
scope.$watch(scope.param,function(){
console.log("inside watch",scope.param);
element.innerHTML = scope.param;
});
}
};
}
]);
In the above example the param which is passed from first directive to the second directive is controlled by first directive so the para can change at any time so in the second directive i am using the watch to update the second directive HTML based on the param update.
So now the problem is if i used same kind of approach in my application at many places it leads to multiple watch, so i want to check is this approach is correct or is there is any other alternative approach for this.?
There must be a $watch somewhere to detect the change in the value.
One way to reduce the number of watches is not to use two-way binding scope: {param: "="} in the second directive, and instead use one-way binding of "&".
.directive("second", function(){
return {
scope: { param: "&" }, // this does not create a watch on the parent
template: "<div>{{param()}}</div>" // {{ }} creates a watch
}
})
Of course, you can also explicitly add a $watch in the link/controller (although in your particular example where you use element.innerHTML) it can easier be done with the template approach above):
link: function(scope, element){
scope.$watch(function(){ return scope.param(); },
function(newValue, oldValue){
console.log(newValue, oldValue);
});
}
So, the number of watches is 1 in each case.
I see no way to improve this. Since you need to actually listen for changes for param I do not see a way different from watchers in this case.
I want to recreate nsClick behavior with my directive ( changing priority).
So this is my code:
angular.module('MyApp').directive('nsClickHack', function () {
return {
restrict: 'E',
priority: 100,
replace: true,
scope: {
key: '=',
value: '=',
accept: "&"
},
link: function ($scope, $element, $attrs, $location) {
$scope.method();
}
}
});
and the line I'm trying to bind to:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
toggle and item.action are from other directives.
Can you point me where I was making mistake?
If you are trying to re-create ng-click, then it's probably better to look at the source of the ngClick directive.
For example, it does not create an isolate scope since only one isolate scope can be created on an element and it tries to be accommodating towards other directives. The alternative is to $parse the attribute value, which is what the built-in implementation is doing.
If you are just creating a "poor's man" version of ngClick, then, sure, you could use a callback function "&" defined on the scope, and invoke it when the element is clicked:
.directive("nsClickHack", function(){
return {
restrict: "A",
scope: {
clickCb: "&nsClickHack"
},
link: function(scope, element){
element.on("click", function(e){
scope.clickCb({$event: e}); // ngClick also passes the $event var
});
}
}
});
The usage is as you seem to want it:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
plunker