AngularJS directive with isolate scope, ng-repeat, and controllerAs - angularjs

function directive() {
return {
restrict: 'E',
template: '<button ng-repeat="x in j.array" ng-click="j.set(x)">{{x}}</button>',
replace: true,
//scope: {},
bindToController: {
array: '=',
answer: '='
},
controller: function() {
var j = this;
j.set = function(data) {
j.answer = data;
};
},
controllerAs: 'j'
};
}
When I uncomment scope and create an isolate scope the directive no longer works. I'm trying to determine why.
Normally I still have access to the controllerAs in an ng-repeat, in this example when I lose it it's still available on $parent.j. I think there are 3 solutions.
Solution 1 is to leave it not in isolate scope.
Solution 2 would be to convert every reference to j inside the repeat to $parent.j.
Solution 3 is that there is some way to use j without having to use $parent that I'm unaware of.

It may be to do with the replace: true. If you wrap the button in a div, it seems to work! I've made a little Plunker here to demonstrate.

Related

How to delegate ngFocus/ngBlur to directive's template <input> element?

I'm trying to create a custom component (directive) which is composed of an <input> box and a [-] and [+] buttons. Currently, the example below only implements the input box.
So, say I have the following HTML for my directive:
<my-input ng-blur="onBlur($event)" ng-focus="onFocus($event)"></my-input>
And for testing purposes, I use this code:
app.run(function ($rootScope) {
$rootScope.onBlur = function ($event) {
console.log('onBlur', $event);
};
$rootScope.onFocus = function ($event) {
console.log('onFocus', $event);
};
});
Now I want to create my custom <my-input> directive which has an <input> box on the template and I need the ng-blur and ng-focus set on <my-input> to respond to blur/focus events on the input box.
I have the following solution almost working: http://codepen.io/anon/pen/KpELmj
1) I have a feeling that this can be achieved in a much better way, I just can't seem to do it. Thoughts?
2) $event seems to be undefined and I can't understand why. Thoughts?
Ok figured it out. Doron's answer was a good starting point for research, but now I think I have what you are looking for. The key is you have to use & in the link section in order to get it to execute the expression.
.directive('myInput', function($timeout) {
return {
restrict: 'E',
scope: {
data: '=',
blur: '&myBlur' //this is the key line
},
template: '<input ng-blur="blur($event)" ng-model="data">'
}
})
This is how you use it:
<my-input my-blur="runBlurFunc()"></my-input>
If you really want to define the function on the root scope, you can use $scope.$root.onBlur() instead of runBlurFunc()
Hope I got your question right, did you try to use the link function?
app.directive('myInput', function () {
return {
restrict: 'E',
scope: {
ngBlur: '&',
ngFocus: '&'
},
bindToController: true,
controller: controllerFn,
controllerAs: 'ctrl',
link:function(scope){
scope.onBlur = function(ev){
console.log(ev);
}
scope.onFocus = function(ev){
console.log(ev);
}
},
template: '[-]<input ng-blur="onBlur($event)" ng-focus="onFocus($event)"></input>[+]'
}
});

Why is what I set in rootScope not available in a directive?

I am setting the following:
function appRun(
$rootScope
) {
$rootScope.abc = 99;
}
I am calling a directive like this:
<admin-retrieve-button ctrl="exam" home="home" Network="Network"></admin-retrieve-button>
Here's my directive:
app.directive('adminRetrieveButton', ['stateService', function (stateService) {
return {
scope: {
ctrl: '=',
home: '=',
Network: '=',
abc: '='
},
restrict: 'E',
template: "xxxx {{ abc }} dddd",
link: function (scope, element, attrs) {
scope.stateService = stateService;
scope.entity = attrs["entity"];
}
};
}]);
However when my HTML page comes up it shows:
xxxx dddd
Can someone tell me why the rootScope value of 99 does not show up?
Because you're declaring an isolate scope on your directive, thus creating a new parent less scope. One way to fix this would be to obviously explicitly pass in abc in your HTML:
<admin-retrieve-button ctrl="exam" abc="abc" home="home" Network="Network"></admin-retrieve-button>
Or you could change your directive to not create a scope of its own:
scope:false
or just leave the scope property alone (don't declare it), however passing in a directives dependencies through a scope of its own is good practice IMO, it's more explicit and gets rid of hidden dependencies.

Bind an AngularJS directive to a mapping of an array

I have a directive that takes an array of objects. When declaring the directive in markup, the scope has an array of objects that wrap the ones needed by the directive. So I need to apply a map function on the array. What's the right way to do this so updates made to the original array are reflected inside the directive?
Here's a Plunker with a naive approach (which I was surprised to see mostly "work" except for lots of $digest errors): http://plnkr.co/edit/GUCZ3c
You should avoid calling a function from an Angular expression unless, among other things, that function does some very lightweight work (a quick computation, for instance). This question has more details on that matter.
In your case, you should cache the name list and bind it to the directive. Here's an example:
app.controller('MainCtrl', function($scope) {
$scope.people = [
{ name: 'Alice'},
{ name: 'Bob' },
{ name: 'Chuck' }
];
$scope.addName = function(name) {
$scope.people.push({name: name});
};
$scope.$watch(function() { return $scope.people.length; }, function() {
$scope.names = $scope.people.map(function(p) { return p.name; });
});
});
Your directive code remains the same. Here's a fork of your Plunker.
Update: I've changed the code so it uses a $watch to update the name list automatically, following #KrisBraun advice.
I believe you need to treat expressions a little differently when binding them to your isolated scope. Here is a forked plunker.
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
scope: {
names: '&namesFunc'
},
template: '<div><p>JSON: {{names() | json}}</p><p>Names:</p><ul><li ng-repeat="name in names()">{{name}}</li></ul></div>',
replace: true,
link: function(scope, elem, attr, ctrl) {
}
};
});

Using directives inside directives causes binding issues

We're using Angular and we're having trouble with resolving variables in directives.
This fiddle shows our issue:
Here's the full code: http://jsfiddle.net/VX5LE/65/
//data-translate should handle the translating of the useableButton text
app.directive('window', ['translateService', function (translateService) {
return {
restrict: 'E',
transclude: true,
scope: {
useableButtons: '='},
replace: true,
template:
'<div>' +
'<button data-ng-repeat="useableButton in useableButtons" data-translate>{{useableButton}}</button>' +
'</div>'
};
}]);
I have seen some answers that solve this by:
Using a filter to translate these. - That is actually our current solution but that hinders us with different functionality.
Attaching watches in the controller. - We actually want to avoid watches in our controllers as it makes the code quite dirty if you have a lot of them.
Preferably I would like to see a solution that resides inside of the translate directive without cluttering the controllers.
You can do this by interpolating the values manually, then parsing it with the $eval function of the desired scope.
Here is the fiddle: http://jsfiddle.net/VX5LE/66/
The code of the translate-directive:
app.directive('translate', ['translateService', '$interpolate', function (translateService, $interpolate) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var pHTML = element.html();
var parsed = $interpolate(pHTML);
var translated_result = translateService.translate(scope.$eval(parsed));
element.text(translated_result);
}
}
}]);

Angularjs passing object to directive

Angular newbie here. I am trying to figure out what's going wrong while passing objects to directives.
here's my directive:
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
and this is the template where I call the directive:
<div walkmap="store.walks"></div>
store.walks is an array of objects.
When I run this, scope.walks logs as undefined while scope logs fine as an Scope and even has a walks child with all the data that I am looking for.
I am not sure what I am doing wrong here because this exact method has worked previously for me.
EDIT:
I've created a plunker with all the required code: http://plnkr.co/edit/uJCxrG
As you can see the {{walks}} is available in the scope but I need to access it in the link function where it is still logging as undefined.
Since you are using $resource to obtain your data, the directive's link function is running before the data is available (because the results from $resource are asynchronous), so the first time in the link function scope.walks will be empty/undefined. Since your directive template contains {{}}s, Angular sets up a $watch on walks, so when the $resource populates the data, the $watch triggers and the display updates. This also explains why you see the walks data in the console -- by the time you click the link to expand the scope, the data is populated.
To solve your issue, in your link function $watch to know when the data is available:
scope.$watch('walks', function(walks) {
console.log(scope.walks, walks);
})
In your production code, just guard against it being undefined:
scope.$watch('walks', function(walks) {
if(walks) { ... }
})
Update: If you are using a version of Angular where $resource supports promises, see also #sawe's answer.
you may also use
scope.walks.$promise.then(function(walks) {
if(walks) {
console.log(walks);
}
});
Another solution would be to add ControllerAs to the directive by which you can access the directive's variables.
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
controllerAs: 'dir',
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
And then, in your view, pass the variable using the controllerAs variable.
<div walkmap="store.walks" ng-init="dir.store.walks"></div>
Try:
<div walk-map="{{store.walks}}"></div>
angular.module('app').directive('walkMap', function($parse) {
return {
link: function(scope, el, attrs) {
console.log($parse(attrs.walkMap)(scope));
}
}
});
your declared $scope.store is not visible from the controller..you declare it inside a function..so it's only visible in the scope of that function, you need declare this outside:
app.controller('MainCtrl', function($scope, $resource, ClientData) {
$scope.store=[]; // <- declared in the "javascript" controller scope
ClientData.get({}, function(clientData) {
self.original = clientData;
$scope.clientData = new ClientData(self.original);
var storeToGet = "150-001 KT";
angular.forEach(clientData.stores, function(store){
if(store.name == storeToGet ) {
$scope.store = store; //declared here it's only visible inside the forEach
}
});
});
});

Resources