I m trying to pass some information through a view in a directive and it doesnt seem to work at all. In fact, even if I bind the scope, I have the string value writter :
Here's the directive
angular.module('app')
.directive('anomalieTableau', function () {
var controller = ['$scope', '$attrs', 'srv_traitementsSite', function ($scope, $attrs, srv_traitementsSite) {
$scope.anomalies = [];
var idSite = $attrs.idsite;
alert(idSite);
var idAgence = $attrs.idagence;
var dateDebut = $attrs.datedebut;
var dateFin = $attrs.datefin;
var promise = undefined;
if (idAgence && idSite) {
promise = srv_traitementsSite.TraitementSiteAgenceAnomalie(idSite, idAgence, dateDebut, dateFin);
}
promise.then(function (response) {
$scope.anomalies = response.data;
}, function (response) {
});
}];
return {
restrict: 'EAC',
templateUrl: 'tpl/directive/AnomalieTableauDirective.html',
controller: controller,
scope: {
idsite: '=',
idagence: '=',
datedebut: '=',
datefin: '='
}
};
});
Here's the HTML call :
<div anomalie-tableau idsite="site._id" idagence="AgenceId" datedebut="dateDebutStats"
datefin="dateFinStats" class="col-md-12"></div>
And this is the alert result in the directive :
site._id
Instead of :
123456789
EDIT : Changing site._id by {{site._id}} in the attribute directive's call, it changes nothing and gives me this error :
Syntax Error: Token 'site._id' is unexpected, expecting [:] at column
3 of the expression [{{site._id}}] starting at [site._id}}].
What am I doing wrong ?
Attributes are always strings. So you will need to interpolate the value ({{site._id}}) and then possibly convert the string ($attrs.idsite) to your desired type.
In respect to your scope-settings: You then have to use $scope instead of $attrs (and dont need the interpolation) since angular will copy those values to your scope. If you use = it will be a two-way-binding and you dont need to interpolate the values in your directive-attribute.
If you for some reason need to use $attrs you can do the interpolation yourself. Remove the scope-settings, use idsite="{{...}}" and inject the $interpolation Service into your directive.
Then use $interpolate($attrs.idsite)($scope.$parent) to get the value (string).
http://plnkr.co/edit/zhBXyiz82EdmysLzeBNL?p=preview
(note that in my plnkr I used # in the scope-settings. You can remove that or leave it. With the # angular will execute the interpolation for you and store the value in the $scope object of your directive.)
Related
How might one go about passing an object to Angular's (Angular 1.4.8) & ampersand scope binding directive?
I understand from the docs that there is a key-destructuring of sorts that needs named params in the callback function, and the parent scope uses these names as args. This SO answer gives a helpful example of the expected & functionality. I can get this to work when explicitly naming the params on the parent controller function call.
However, I am using the & to execute actions via a factory. The parent controller knows nothing of the params and simply hands the callback params to a dataFactory, which needs varied keys / values based on the action.
Once the promise resolves on the factory, the parent scope updates with the returned data.
As such, I need an object with n number of key / value pairs, rather than named parameters, as it will vary based on each configured action. Is this possible?
The closest I have seen is to inject $parse into the link function, which does not answer my question but is the sort of work-around that I am looking for. This unanswered question sounds exactly like what I need.
Also, I am trying to avoid encoding/decoding JSON, and I would like to avoid broadcast as well if possible. Code stripped down for brevity. Thanks...
Relevant Child Directive Code
function featureAction(){
return {
scope: true,
bindToController: {
actionConfig: "=",
actionName: "=",
callAction: "&"
},
restrict: 'EA',
controllerAs: "vm",
link: updateButtonParams,
controller: FeatureActionController
};
}
Child handler on the DOM
/***** navItem is from an ng-repeat,
which is where the variable configuration params come from *****/
ng-click="vm.takeAction(navItem)"
Relevant Child Controller
function FeatureActionController(modalService){
var vm = this;
vm.takeAction = takeAction;
function _callAction(params){
var obj = params || {};
vm.callAction({params: obj}); // BROKEN HERE --> TRYING
//TO SEND OBJ PARAMS
}
function executeOnUserConfirmation(func, config){
return vm.userConfirmation().result.then(function(response){ func(response, config); }, logDismissal);
}
function generateTasks(resp, params){
params.example_param_1 = vm.add_example_param_to_decorate_here;
_callAction(params);
}
function takeAction(params){
var func = generateTasks;
executeOnUserConfirmation(func, params);
}
Relevent Parent Controller
function callAction(params){
// logs undefined -- works if I switch to naming params as strings
console.log("INCOMING PARAMS FROM CHILD CONTROLLER", params)
executeAction(params);
}
function executeAction(params){
dataService.executeAction(params).then(function(data){
updateRecordsDisplay(data); });
}
I think the example below should give you enough of a start to figure out your question:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Angular Callback</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var myApp = angular.module("myApp", []);
myApp.controller('appController', function($scope) {
$scope.var1 = 1;
$scope.handleAction1 = function(params) {
console.log('handleAction1 ------------------------------');
console.log('params', params);
}
$scope.handleAction2 = function(params, val1) {
console.log('handleAction2 ------------------------------');
console.log('params', params);
console.log('val1', val1);
}
});
myApp.controller('innerController', innerController);
innerController.$inject = ['$scope'];
function innerController($scope) {
$scope.doSomething = doSomething;
function doSomething() {
console.log('doSomething()');
var obj = {a:1,b:2,c:3}; // <-- Build your params here
$scope.callAction({val1: 1, params: obj});
}
}
myApp.directive('inner', innerDirective );
function innerDirective() {
return {
'restrict': 'E',
'template': '{{label}}: <button ng-click="doSomething()">Do Something</button><br/>',
'controller': 'innerController',
'scope': {
callAction: '&',
label: '#'
}
};
}
</script>
</head>
<body ng-controller="appController">
<inner label="One Param" call-action="handleAction1(params)"></inner>
<inner label="Two Params" call-action="handleAction2(params, val)"></inner>
</body>
</html>
In the appController I have two functions that will be called by the inner directive. The directive is expecting the outer controller to pass in those functions using the call-action attribute on the <inner> tag.
When you click on the button within the inner directive it called the function $scope.doSomething This, in turn calls to the outer controller function handleAction1 or handleAction2. It also passes a set of parameters val1 and params:
$scope.callAction({val1: 1, params: obj});
In your template you specify which of those parameters you want to be passed into your outer controller function:
call-action="handleAction1(params)"
or
call-action="handleAction2(params, val)"
Angular then uses those parameter names to look into the object you sent when you called $scope.callAction.
If you need other parameters passed into the outer controller function then just add then into the object defined in the call to $scope.callAction. In your case you would want to put more content into the object you pass in:
var obj = {a:1,b:2,c:3}; // <-- Build your params here
Make that fit your need and then in your outer controller you would take in params and it would be a copy of the object defined just above this paragraph.
It this is not what you were asking, let me know.
In my controller HTML I am passing an object into a directive as such:
<div cr-count-summary countdata="vm.currentCountData"></div>
The vm.currentCountData is an object that is returned from a factory
My directive code is below:
function countSummary() {
var directive = {
scope: {
countData: '='
},
link: link,
templateUrl: function(element, attrs) {
if (attrs.countdata.type === 'Deposit') {
return 'app/count/countsummary/countDeposit.html';
} else {
return 'app/count/countsummary/countRegisterSafe.html';
}
}
}
}
I have verified that vm.currentCountData is a valid object with a .type property on it. However, it doesn't recognize it. I have tried simplifying things by just passing in countdata="Deposit" in the controller HTML. I've also changed attrs.countdata.type to just attrs.countdata and it does evaluate as the string.
When I have it set up as I have shown above the directive templateUrl function seems to evaluate prior to the controller
I've looked at this, but it seems to only be evaluating strings
What do I need to do in order to allow attrs recognize the object?
This is not possible in this way, because at the time of evaluating templateUrl function angular doesn't have any scope variable, scope gets created after the compile function of directive generates preLink & postLink.
I'd prefer you to use ng-include directive inside the directive template, and then on basis of condition do load the desired template in it.
Markup
<div cr-count-summary count-data="vm.currentCountData"></div>
Directive
function countSummary() {
var directive = {
scope: {
countData: '='
},
link: link,
template: "<div ng-include=\"countdata.type === 'Deposit' ? "+
"'app/count/countsummary/countDeposit.html' :" +
"'app/count/countsummary/countRegisterSafe.html'\">"+
"</div>"
}
}
I have a directive which has this piece of code:
return {
restrict: "E",
scope: {
moduleProgress: "=",
modules: "=",
parentScroll: "="
},
templateUrl: 'app/program/module.list.template.html',
link: function(scope,el,attrs,ctrl,tf) {
$templateRequest("app/program/chapter.list.template.html").then(function(template) {
var template = angular.element(template);
var elements = [];
console.log(scope);
attrs.$observe("modules", function(val) {
scope.modules.forEach(function(module) {
module.open = false;
});
});
For some reason, the first time I navigate to the view that contains this directive everything works fine, but from the second time onwards I always get a Cannot call method 'forEach' of undefined error. I read through some similar questions on SO which mentioned interpolated attributes not being instantly available on the link function, so they reccomended the use of $observe. I even added a console.log(scope) and the modules property shows up on the object. Any idea of what might be causing this?
It should be rather $scope.$watch rather that attrs.$observe as $observe required # in isolated scope of directive & on html it would be modules="{{module}}", For big object DOM will get messed up so I'd prefer you to use $watch which make sense
Code
scope.$watch("modules", function(newVal) {
if(newVal){
scope.modules.forEach(function(module) {
module.open = false;
});
}
});
I'd like to do simple notifications in angular. Here is the code I've written.
http://pastebin.com/zYZtntu8
The question is:
Why if I add a new alert in hasAlerts() method it works, but if I add a new alert in NoteController it doesn't. I've tried something with $scope.$watch but it also doesn't work or I've done something wrong.
How can I do that?
Check out this plnkr I made a while back
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
I show a couple of ways controllers can use data from services, in particular the first two show how to do it without a watch which is generally a more efficient way to go:
// Code goes here
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
The notable thing here I guess is that I use angular.copy to re-use the same object that's created in the service instead of assigning a new object or array to that property. Since it's the same object if you reference that object from your controllers and use it in any data-binding situation (watches or {{}} interpolation in the view) will see the changes to the object.
I want to access a variable in this case map in the directive without having to predefine it like setting it as an attr of the directrive map="someValue". And also i dont want to use scope.$apply because i actually only want the variable in the isolated scope of the directive. Is this even possible ?
What is the best practice here? Basically my directive needs to do both. Access the parent scope and have its own scope which with i can build the template with.
Thank you everybody.
Here my Js code:
.directive('myFilter', function() {
return {
restrict: 'E',
scope: {
source: '=source',
key: '=key',
},
link: function(scope, element, attrs) {
scope.$on('dataLoaded', function(e) {
scope.map = {};
angular.forEach(scope.source, function(paramObj) {
if (!scope.map[paramObj[scope.key]]) {
var newEntry = {
value: paramObj[scope.key],
isChecked: false
}
scope.map[paramObj[scope.key]] = newEntry;
}
});
});
}
}
});
and my html:
<my-filter source="metaPara.filteredParameters" key="'value'">
<table class="table table-borered">
<tr data-ng-repeat="item in map">
<td>{{item.value}}</td>
</tr>
</table>
</my-filter>
You might want to refer to the Angular documentation for directives, again.
If you want an isolate-scope (a scope which has no access to ancestors), then use
scope : { /* ... */ }
otherwise, if you want a unique scope, which does have access to ancestors, use
scope : true
Then, you can put your HTML-modifying or event-listening (that doesn't rely on ng-click or something else Angular already covers) in
link : function (scope, el, attrs, controller) { }
...and you can put all of your regular implementation inside of
controller : ["$scope", function ($scope) {
var myController = this;
myController.property = "12";
}],
controllerAs : "myController"
So that in your template you can say:
<span>{{ myController.property }}</span>
You can also use a pre-registered controller, which you call by name:
controller : "mySimpleController",
controllerAs : "myController"
Also, rather than using $scope.$apply, I'd recommend using $timeout (has to be injected).
The difference is that $scope.$apply will only work at certain points -- if you're already inside of a digest cycle, it will throw an error, and not update anything.
$timeout( ) sets the updates to happen during the next update-cycle.
Ideally, you should know whether or not you need an $apply or not, and be able to guarantee that you're only using it in one spot, per update/digest, but $timeout will save you from those points where you aren't necessarily sure.