Directive Isolate Scope 1.2.2 - javascript

I'm working with Angular version 1.2.2 for the first time and trying to make a simple directive that uses isolate scope with '=' binding to pass in an object. I've done this a few times before so I'm wondering if maybe there was a change in 1.2.2 that changed this?
Here is my directive:
.directive('vendorSelector', function (VendorFactory) {
return {
restrict: 'E',
replace: true,
scope: { vendorId: '=' },
template: '<select ng-model="vendorId" ng-options="id for id in vendorIds">' +
'<option value="">-- choose vendor --</option>' +
'</select>',
link: function (scope, element, attrs) {
VendorFactory.getVendorIds().then(function(result) {
scope.vendorIds = result;
});
}
}
})
My HTML template using the directive is as follows:
<div class="padding">
<vendor-selector vendorId="someValue"></vendor-selector>
{{ someValue }}
</div>
And the backing controller:
.controller('AddProductController', function($scope, ProductFactory, AlertFactory) {
$scope.vendorId = 0;
$scope.someValue = undefined;
})
I've tried using both $scope.someValue and $scope.vendorId as the supplied object in the html template. In both cases the error I'm getting back is Expression 'undefined' used with directive 'vendorSelector' is non-assignable!. Am I missing something obvious that is preventing these values from being 2-way bound in the isolate scope?

In your html:
<vendor-selector vendorId="someValue"></vendor-selector>
Change vendorId="someValue"
to vendor-id="someValue"
HTML attributes are case insensitive so to avoid confusion Angular converts all camel cased variables (vendorId) to snake case attributes (vendor-id).
So someValue wasn't bound to vendorId. Resulting in vendorId being undefined in the template. And thus your error.

Related

How to add dynamic directive names from JSON with the $interpolate function

I'm trying to dynamically add directive names to my directive from a json object. Angular however is only interpolating the directive name which is pulled from a JSON tree once, Angular is then not recognizing and compiling the dynamic children directives once the name is interpolated.
I have tried adding the interpolate service to my DDO so that I can manually interpolate the JSON values, and then have Angular compile.
I however get undefined for $interpolate(tAttrs.$attr.layout) I'm passing the json object to my isolated scope as layout, when I try to access the attr layout I get undefined. My question is how can I access layout object values in the pre link or before compile so that I can interpolate the values and inject them in.
Or do I need to have angular recompile as described here: How do I pass multiple attributes into an Angular.js attribute directive?
Any help would be great.
{
"containers": [
{
"fluid": true,
"rows": [
{
"columns": [
{
"class": "col-md-12",
"directive": "blog"
}
]
},
{
"columns": [
{
"class": "col-md-6 col-md-offset-3 col-xs-10 col-xs-offset-1",
"directive": "tire-finder"
}
]
}
]
}
]
}
...
<div layout="layout" ng-repeat="container in layout.containers" ng-class="container">
<div ng-repeat="row in container.rows">
<div ng-repeat="column in row.columns" ng-class="column.class">
<{{column.directive}}></{{column.directive}}>
</div>
</div>
</div>
...
angular.module('rpmsol').directive('wpMain', wpMainDirective);
function wpMainDirective($interpolate) {
var controller = function(brainService, $scope, $state) {
$scope.directive = {};
var currentState = $state.current.name;
brainService.getDirectiveScope('wpMain', {}).then(function(response) {
$scope.layout = response.states[currentState];
});
};
var compile = function(tElement, tAttrs, transclude) {
var directiveNames = $interpolate(tAttrs.$attr.layout);
}
return {
restrict: 'E',
// replace: true,
scope: {
layout: '=',
},
controller: controller,
templateUrl: 'directive/wpMain/wpMain.html',
compile: compile
};
};
If you're only dealing with a couple options for what a column might be, I would suggest going with #georgeawg's answer.
However, if you expect that number to grow, what you might opt for instead is something along the following lines:
<div layout="layout" ng-repeat="container in layout.containers" ng-class="container">
<div ng-repeat="row in container.rows">
<div ng-repeat="column in row.columns" ng-class="column.class">
<column-directive type="column.directive"></column-directive>
</div>
</div>
and then in your JS...
yourApp.directive('columnDirective', columnDirectiveFactory);
columnDirectiveFactory.$inject = ['$compile'];
function columnDirectiveFactory ($compile) {
return {
restrict: 'E',
scope: {
type: '='
},
link: function (scope, elem, attrs) {
var newContents = $compile('<' + scope.type + '></' + scope.type + '>')(scope);
elem.contents(newContents);
}
};
}
To the best of my knowledge, Angular doesn't have any built-in facility to choose directives in a truly dynamic fashion. The solution above allows you to pass information about which directive you want into a generic columnDirective, whose link function then goes about the business of constructing the correct element, compiling it against the current scope, and inserting into the DOM.
There was an issue with the promise in my original posted code which was preventing me from recompiling the template with the correct directive names. The issue was that I was trying to access the JSON object in the preLink function, but the promise hadn't been resolved yet. This meant that my scope property didn't yet have data.
To fix this I added my service promise to the directive scope $scope.layoutPromise = brainService.getDirectiveScope('wpMain', {}); to which I then called and resolved in my link function. I managed to have Angular compile all of my directive names from the JSON object, but I had to do it in a very hackish way. I will be taking your recommendations #cmw in order to make my code simpler and more 'Angulary'
This is currently my working code:
...
angular.module('rpmsol').directive('wpMain', wpMainDirective);
function wpMainDirective($interpolate, $compile) {
var controller = function(brainService, $scope, $state) {
$scope.currentState = $state.current.name;
$scope.layoutPromise = brainService.getDirectiveScope('wpMain', {});
};
var link = function(scope, element, attributes) {
scope.layoutPromise.then(function sucess(response) {
var template = [];
angular.forEach(response.states[scope.currentState].containers, function(container, containerKey) {
template.push('<div class="container' + (container.fluid?'-fluid':'') + '">');
//loop rows
angular.forEach(container.rows, function(row, rowkey) {
template.push('<div class="row">');
angular.forEach(row.columns, function(column, columnKey) {
template.push('<div class="' + column.class + '">');
template.push('<' + column.directive +'></' + column.directive + '>')
template.push('</div>');
});
template.push('</div>');
});
template.push('</div>');
});
template = template.join('');
element.append($compile(template)(scope));
})
};
return {
scope: true,
controller: controller,
link: link
};
};

Pass scope variable from directive to it's controller

This is possibly easy, but I have browsed the different questions here on SO and in the Angular documentation and can't really find what I'm looking for.
In a directive:
function ssKendoGrid() {
return {
scope: {
dataSource: "="
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl",
}
}
That uses the controller:
function ssKendoGridCtrl($scope) {
alert($scope.dataSource);
//other stuff
}
If I want to access the value of dataSource I assumed I'd be able to do something like this:
<div ng-controller="myController">
<div ss-kendo-grid data-source="test"></div>
</div>
MyController is:
function myController($scope) {
$scope.test = "Tested";
}
But it comes as undefined when I try to alert($scope.dataSource); the value..
Now I know I can do this:
<div ss-kendo-grid="test"></div>
And access it in the directive and controller like this:
return {
scope: {
ssKendoGrid: "="
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
//In controller
alert($scope.ssKendoGrid);
But I would like to be able to pass in a JSON object to do various things with and this doesn't seem as clean as in the markup I'd like it to be more intuitive to look at the html and know what the dataSource is.
What I'm really looking for is an understanding of what I'm doing wrong, why doesn't this work?? I've obviously not got the right understanding of how to pass various things to the isolated scope of the directive.
SOLVED
So, turns out I was using the wrong attribute name. HTML5 recognizes data- as a valid attribute, and Angular ignores the fact that data- is prefixed on the variable, which means that I would need to access the variable this way:
HTML:
<div ss-kendo-grid data-source="test"></div>
JS:
return {
scope: {
dataSource: "=source"
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
Cheers
you need to access the directive scope variable as
<div ss-kendo-grid data-source="test"></div>
similarly as you name the directive in the HTML markup
So, turns out I was using the wrong attribute name. HTML5 recognizes data- as a valid attribute, and Angular ignores the fact that data- is prefixed on the variable, which means that I would need to access the variable this way:
HTML:
<div ss-kendo-grid data-source="test"></div>
JS:
return {
scope: {
dataSource: "=source"
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
And a better convention is to simply not use a directive with "data-" at the beginning of it.
invite.directive('googlePlaces', function (){
return {
restrict:'E',
replace:true,
// transclude:true,
scope: {location:'=location'},
template: '<input id="google_places_ac" name="google_places_ac" type="text" class="input-block-level"/>',
link: function(scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
console.log(scope.location);
scope.$apply();
// scope.$apply(function() {
// scope.location = location;
// });
});
}
};
});

How do I use the '&' correctly when binding in a directive in Angularjs?

I have having trouble with understanding the & option in binding scope properties. I have read these tutorials: angularjs-directives-using-isolated-scope-with-attributes, practical-guide-angularjs-directives and the-hitchhikers-guide-to-the-directive as well as looking at the documentation but I am completely confused about how to use the & symbol.
Here is my attempt at using it:
var app = angular.module('directivesApp');
app.directive('simplyIsolated', function () {
return{
restrict: 'EA',
replace: true,
scope:{
attnum: '#numone'
,bindnum: '=numtwo'
,expressnum: '&sq'
},
link: function (scope, elem, attr){
scope.x = scope.expressnum();
},
template:'<div><p> using "#" = {{attnum+attnum}}</p>'+
'<p>using "=" {{bindnum+bindnum}}</p>'+
'<p>using "&" {{x}}</p><br/><p>{{y}}</p>'+
'</div>'
};
})
.controller('MainCtrl', function ($scope) {
$scope.sqr = function(num){
return num*num;
}
});
and this is my html
<div class="container" ng-controller="MainCtrl">
<input type="text" ng-model="num1parent" />
<input type="number" ng-model="num2parent" />
<input type="number" ng-model="num3parent" />
<p>Parent Scope # {{usernameparent}},
= {{userageparent}},
& = {{sqr(num3parent)}}
</p>
<div simply-isolated numone='{{num1parent}}' numtwo='num2parent' sq="sqr"></div>
</div>
This is the result.
the first two inputs are used to show the difference between # and =. The third input is used to show the sqr() method works but in the text underneath the using "&" is supposed to be the square of the 2nd input but I don't get any result or error
If someone could point me in the right direction I would really appreciate it
Also: Why would you use the & over scope.$parent?
Simply because you're not using the function scope.x as a function instead you have it evaluated as a mere function expression with no argument value.
try changing the template to this:
template:'<div><p> using "#" = {{attnum+attnum}}</p>'+
'<p>using "=" {{bindnum+bindnum}}</p>'+
'<p>using "&" {{x(bindnum}}</p><br/><p>{{y}}</p>'+
'</div>'
that should show something when you provide changes in the num2parent model.
Why would you use the & over scope.$parent?
Because it is normally not recommended to do so, if you would be doing it in such a manner it might be better not to isolate the scope of your directive and have direct access to the parent controllers properties in the directive.
Update:
The reference passed by the attribute notiation '&' is a function that returns the function defined in your controllers. To invoke the function reference, simply do this:
{{expressnum()(bindNumb)}}
that will do the trick.
Note:
There is a reason for this, functions often passed via '&' attribute notations are function callbacks such as events. So if your exressnum() was an callback function for an event then it would have been used like this in you template perhaps
<a ng-click="expressnum()">Click Expression</a>
Furthermore, the function sqr() is a type of function that changes the result of an expression, such functions must be defined as a filter.

AngularJS The scope for dynamic content through $compile isn't attached to the controller scope

When I generate a new element through a string that has a directive (that's why I need to compile) and that directive generates an association with a variable in the controller scope through "=", the variable in my controller isn't associated to the one in the directive.
I created a jsfiddle to show the example where the "door" ng-model value should be associated to all the directives model values.
See this fiddle: http://jsfiddle.net/aVJqU/2/
Another thing I notice is that the directive that run from elements present in the html show the correct association through the variables (controller and directive).
The html (there is the directive that binds <door>):
<body ng-app="animateApp">
<div ng-controller="tst">
<h2> Controller with its model </h2>
<input ng-model="doorval" type="text"> </input>
{{doorval}}
<h2> Directive render directly from the html </h2>
<door doorvalue="doorval"></door> <key></key>
<h2> Directives that are compiled </h2>
<list-actions actions="actions"></list-actions>
</div>
</body>
This is the directive:
animateAppModule.directive('door', function () {
return {
restrict: "E",
scope: {
doorvalue:"="
},
template: '<span>Open the door <input type="text" ng-model="doorvalue"> </input> {{doorvalue}}</span>',
replace: true
}
})
This is the controller:
var animateAppModule = angular.module('animateApp', [])
animateAppModule.controller('tst', function ($scope, tmplService) {
$scope.doorval = "open"
$scope.actions = tmplService;
})
animateAppModule.service('tmplService', function () {
return [{
form_layout: '<door doorvalue="doorval"></door> <key></key>'
}, {
form_layout: '<door doorvalue="doorval"></door> with this <key></key>'
}]
})
And finally this is the directive that compiles the string that has the directive that doesn't bind:
animateAppModule.directive('listActions', function ($compile) {
return {
restrict: "E",
replace: true,
template: '<ul></ul>',
scope: {
actions: '='
},
link: function (scope, iElement, iAttrs) {
scope.$watch('actions', function (neww, old,scope) {
var _actions = scope.actions;
for (var i = 0; i < _actions.length; i++) {
//iElement.append('<li>'+ _actions[i].form_layout + '</li>');
//$compile(iElement.contents())(scope)
iElement.append($compile('<li>' + _actions[i].form_layout + '</li>')(scope))
}
})
}
}
})
What can I do to bind all the "door" ng-model values together?
Where is the compiled directive binding to?
You just have to pass the doorval reference down through all directives without skip any one. The problem was the listActions directive didn't had access to doorval in its scope.
Check this out: http://jsfiddle.net/aVJqU/5/
#Danypype is basically correct as the problem occurs due to scope isolation, as explained in the documentation.
An alternative solution is to simply eliminate the scope isolation by removing the scope block from within the directive definition.

Angularjs custom form control scope is duplicated

I have created a simple angularjs directive to input city and zip codes. It works fine except that if I used it twice within the same controller the values in the input field are duplicated!
I believe it's a scope problem but I don't know how to solve it?
FDVilleModule = angular.module('FDVille', []).
directive('fdVille', () ->
return {
restrict: 'E'
require: 'ngModel'
template: """
<div class=\"row-fluid\">
<div class=\"span4\">
<input
ng-model=\"cp\"
ng-change=\"edit()\"
maxlength=\"5\"
type=\"text\"
class=\"input-block-level\"
placeholder=\"Code Postal\" />
</div>
<div class=\"span8\">
<select ng-model=\"selected_ville\"
ng-options=\"v.id as v.nom for v in villes\"
class=\"input-block-level\">
</select>
</div>
</div>"""
link: (scope, elem, attr, ctrl) ->
scope.$watch('selected_ville', (value)->
ctrl.$setViewValue(value))
controller: ($scope) ->
download_villes = (cp) -> $.getJSON('/ws/villes/cp', {cp:cp}, set_data)
download_villesid = (id) -> $.getJSON('/ws/villes/id', {id:id}, set_init_data)
set_data = (results) ->
$scope.villes = results
$scope.selected_ville = results[0].id if results.length
$scope.$apply()
saved_cp = ""
$scope.edit = () ->
if isNaN($scope.cp)
$scope.cp = saved_cp
else
saved_cp = $scope.cp
if saved_cp.length == 5
download_villes(saved_cp)
else
$scope.selected_ville = null
$scope.villes = []
}
)
Actually I found the answer in the docs:
scope - If set to:
true - then a new scope will be created for this directive. If
multiple directives on the same element request a new scope, only one
new scope is created. The new scope rule does not apply for the root
of the template since the root of the template always gets a new
scope.
In some other instance, you could try
replace: true
Note: you need to have one root element.
Here's the link to the documentation for us readers who didn't know:
http://docs.angularjs.org/guide/directive

Categories

Resources