Iterate config array in html template via Angular directives? - javascript

I've got into AngularJS recently and trying to create a registration form where I would fill country list from my JavaScript config that is widely used elsewhere, thus I keep it as JS object.
I've been trying to use ng-repeat-start and *-end on my select input, but it fails.
The main question is, how do I load countries array and iterate it in my template?
Edit: 30.11.2014 - Better examples
HTML:
<div class="form-group">
<label for="phone">Country</label>
<select-country ng-model="credentials.country"></select-country>
<pre>{{credentials.country}}</pre>
</div>
File:
/public/directives/countrySelector.directive.js
Directive contents:
'use strict';
angular.module('app', [])
.value('countries', [
{name: 'Afghanistan', code: 'AF'},
{name: 'Åland Islands', code: 'AX'}
])
.directive('selectCountry', ['countries', function (countries) {
function link (scope, element, attrs) {
scope.countries = countries;
}
return {
template: '<select ng-options="country[1] as country[0] for country in countries"' +
' ng-model="credentials.country">' +
'</select>',
link: link,
scope: {
credentialsCountry: '='
}
};
}]);

just replace country[0] and country[1] with country.code and country.name
http://plnkr.co/edit/OIGGitze5LLehDes2MQ8?p=preview
or i missed something?

I don't think you need to make a new directive for this. There is already a built in select directive for angular, so you are making more work for yourself than necessary. So what you could do is inject that service into your controller for the page, then bind the service to the scope of the controller like you did for the directive. It would end up looking something like:
angular.module('app', [])
.value('countries', [
{name: 'Afghanistan', code: 'AF'},
{name: 'Åland Islands', code: 'AX'}
]);
and the you have a controller
app.controller('whateverTheControllerIs', ['countries', '$scope', function (countries, $scope) {
$scope.countries = countries;
});
and then your scope is available to the template
<div class="form-group">
<label for="phone">Country</label>
<select ng-options="country.name for country in countries" ng-model="credentials.country"></select>
<pre>{{credentials.country}}</pre>
</div>
As a side note: if you would like to find out about best practices for the latest Angular 1.* version. Read everything Todd Motto says.

It's a good idea to create directives for things that could be commonly reused, like a country selector. You want to inject your countries into a directive where you can then iterate over it with ng-options. Plunker: http://plnkr.co/edit/zfkeLNQ0LHxR7FB0nM18?p=preview
.directive('selectCountry', ['countries', function (countries) {
var directive = {
template: '<select ng-options="country[1] as country[0] for country in countries"' +
' ng-model="ngModel">' +
'</select>',
link: link,
scope: {
ngModel: '='
}
};
return directive;
function link (scope, element, attrs) {
scope.countries = countries;
}
}]);

Related

AngularJs-share object array between directives

I am trying to send an array of objects from parent directive to child directive but i am getting console.log($scope.$eval(attrs.model)) as undefined .
angular.module('tester').directive('gChart',gChart);
function gChart() {
var template = getTemplate;
return {
restrict: 'E',
require: "^Logs",
replace:true,
template: template,
scope:{
model: "="
},
link: function($scope, $element, attrs, LogsCtrl) {
console.log($scope.$eval(attrs.model));
LogsCtrl.show($scope.$eval(attrs.model));
}
};
function getTemplate() {
return'<div id="chart" style="width: 600px; height: 500px;margin: 0px auto;"></div>';
}
}
In this situation I would use factory and inject that factory in both directive. Sharing as per coding standard in angular should be done via factory. It will make ur life easy. If u need any help with that let me know but use factory to share data between controller and directive make application more testable also
You need to post all of your code for us to understand. Besides, your array of data should be in $scope.model, and not in the attrs.model. This is because you define the scope of your directive as
scope:{
model: "="
},
So, in your html, you should have something like:
<tester model="YOUR_DATA" ></tester>

Dynamic adding controller to ng-include

Is that a dynamic way to change ng-include controller?
My app allows users to create page some content and controllers. I can change ng-include src but I don't know how to dynamic associate a new controller. The following code isn't working:
<div ng-app="MyApp">
<div ng-controller="ParentController">
<select ng-model="currentItem" ng-options="item as item.url for item in items track by item.url">
</select>
{{ currentItem }}
<div ng-include src="currentItem.url" ng-controller="currentItem.controller"></div>
</div>
</div>
And I have the following JS:
var app = angular.module("MyApp",[]);
app.controller('ParentController', ['$scope',function($scope){
$scope.items = [{
url: 'page1.html',
controller: 'Page1Controller'
},
{
url: 'page2.html',
controller: 'Page2Controller'
}];
$scope.currentItem = {};
}]);
app.controller('Page1Controller', ['$scope',function(){
alert('Page1');
}]);
app.controller('Page2Controller', ['$scope',function(){
alert('Page2');
}]);
I've done by using a directive instead:
<div ng-include src="currentItem.url" dyn-controller="currentItem.controller"></div>
JS Directive:
app.directive('dynController', ['$compile', '$parse',function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem, attrs) {
// Parse the scope variable
var name = $parse(elem.attr('dyn-controller'))(scope);
elem.removeAttr('dyn-controller');
elem.attr('ng-controller', name);
// Compile the element with the ng-controller attribute
$compile(elem)(scope);
};
}]);
The trick here is watching for attribute changes, add ng-controller and then compile the element.
Thanks for
How to watch property in attrs of directive
and
Dynamic NG-Controller Name
The following array of items is an array of plain objects that have two fields: url and controller, both containing string values.
$scope.items = [{
url: 'page1.html',
controller: 'Page1Controller'
},... ]
What you really need is a reference to the function and that can be achieved with the $injector service.
Eventough this might work, I don't get why you aren't using the ParentController to alter the object in your array (or a directive).

How to pass a variable from a directive (ngModel) into the html outside of the directive

I've got a custom directive with an html template and it basically is a menu option. When a user makes a selection it updates the ng-model variable within the directive.
But I would like for the ng-model variable within the directive passed outside of the directive into the html page.
Here's the code snippets:
Directive:
(function() {
'use strict';
angular
.module('myModule')
.controller('myController', ['$scope', function($scope) {
$scope.sortByOptions = [
{ value:'red', displayText:'Redder' },
{ value:'blue', displayText:'Bluer' },
{ value:'gold', displayText:'Golder' },
{ value:'brown', displayText:'Browner' }
];
}]
)
.directive('myDirective', myDirective);
myDirective.$inject = [];
function myDirective() {
return {
restrict: 'A',
templateUrl: 'mydirective/sorting.html',
}
}
})();
HTML Template for the directive:
<select ng-model="sortBy" ng-options="sortByOption.displayText for sortByOption in sortByOptions track by sortByOption.value"> </select> {{sortBy.value}}
HTML of the Page:
<div class="col-md-8 form-inline" my-directive>
</div>
<!-- need ng-model variable from my-directive passed into sortBy --> <!-- it's being called on same page. I turned a menu options into a directive to save from copying/pasting same code everywhere. when the menu option gets selected it populates a list which is the li you see below -->
<li ng-repeat="object in objects | filter: searchTool | orderBy: (sortAscending ? '' : '-') + sortBy">
<div class="plank">
<div class="plank-header">
</div>
</div>
</li>
As you can see I'm trying to pass ng-model="sortBy" value from the directive which is chosen by the user into other parts of the page called sortBy that is within the li.
It would be awesome if someone can give an example of what they did.
I've done something similar by essentially exposing the variable in your directive to your controller. You can do this by passing a function from your controller into your directive such that that function gets called and essentially sets a variable in your controller. Something like this.
<div mydirective call-outside-function="setSortBy".....>
mycontroller function(...) {
$scope.setSortBy = function(sb) {
$scope.localSortBy = sb;
};
}
mydirective .....
link: function(scope,element,attrs) {
scope.$watch('sortBy',function(newval) {
attrs.callOutsideFunction(newval);
});
}
Probably not the most elegant solution but it works
I did the following:
1) added a scope.$watch to allow my directive to listen for a change in that variable on the DOM. Then it will set the new value in the controller
2) Added the controller into the directive. I originally forgot to add a line in the function colorSorter to return the controller
3) I did not need to modify the directive's html template or the main html template for the page to get it working.
Here is the directive:
(function() {
angular
.module('myModule')
.controller('sortController', ['$scope', function($scope) {
$scope.sortByOptions = [
{ value:'red', text:'Redder' },
{ value:'blue', text:'Bluer' },
];
$scope.sortBy = {value: undefined}
}]
)
.directive('colorSorter', colorSorter);
colorSorter.$inject = [];
function colorSorter() {
return {
restrict: 'A',
templateUrl: 'app/color-sorter/color-sorter.html',
controller: 'sortByController',
link: function (scope) {
scope.$watch('sortBy.value', function (value) {
console.log(value);
})
}
}
}
})();

How to pass JSON data to AngularJS directive

I am learning AngularJS. I try to create a reusable component called .
Unfortunately I cannot prefill the fields inside element with the data obtained from JSON.
I looked around SO and the web but could not solve it. Could you please let me know what am I doing wrong?
I have two controllers. One gets a list of all countries:
app.controller('MainController', ['$scope', 'Countries',
function ($scope, Countries) {
$scope.countries = Countries.query();
}]);
The other gathers a specific address:
app.controller('AddressesController', ['$scope', '$routeParams', 'Address',
function($scope, $routeParams, Address) {
if ($routeParams.addressId) {
$scope.senderAddress = Address.get({addressId: $routeParams.addressId});
} else {
$scope.senderAddress = {"id":null, "country":null, "city":null, "street":null};
}
$scope.adData = {"id": 1, "country": "Poland", "city": "Warsaw", "street": "Nullowska 15"};
}]);
The services are defined as follows, they seem to work correctly and provide correct JSONs:
myServices.factory('Countries', ['$resource',
function($resource) {
return $resource('data/countries.json', {}, {
query: {method:'GET'}
})
}]);
myServices.factory('Address', ['$resource',
function($resource) {
return $resource('data/:addressId.json', {}, {
query: {method:'GET', params:{addressId:'addressId'}}
})
}])
I have routing set so that it directs to AddressesController:
app.config(function ($routeProvider) {
$routeProvider
.when('/address', {
templateUrl: 'partials/addresses.html',
controller: 'AddressesController'
})
.when('/address/:addressId', {
templateUrl: 'partials/addresses2.html',
controller: 'AddressesController'
})
});
The partial view is simple, I create 2 elements
<label> Sender </label>
<address address-data='{{senderAddress}}'></address> <!-- I tried all combinations of passing this as argument -->
<label> Receiver </label>
<address></address>
Now the directive is declared as:
app.directive("address", function () {
return {
restrict: "E",
templateUrl: "/directives/address.html",
scope: {addrData: '#senderAddress'},
link: function(scope, element, attributes) {
scope.adData = attributes["addressData"];
}
}
});
and template for it is:
<div>
<label> {{senderAddress}} </label> <!-- senderAddress from Addresses Controller is filled correctly -->
<div>
<label>Country</label>
<select>
<option value=""></option>
<option ng-repeat="country in countries.countries" value="{{country}}">{{country}}</option>
</select>
</div>
<div>
<label>City {{dto.adData.city}}</label>
<input type="text" data-ng-model="dto.adData.city" /> <!-- this I cannot pre-fill -->
</div>
<div>
<label>Street{{data.adData.city}}</label>
<input type="text" data-ng-model="dto.adData.street"> <!-- this I cannot pre-fill -->
</div>
</div>
It all works well outside of directive. But I miss something regarding how to handle the scope inside a directive with data being obtained from JSON service. Is it because JSON data is a promise object when the links to the directive are created? How to handle it?
PS
I also tried observing the attributes:
link: function(scope, element, attributes) {
//scope.dto.adData = attributes["addressData"];
attrs.$observe('addressData', function(data) {
if (!data)
return;
scope.dto.adData = data;
})
}
Even for statically defined data it doesn't work:
app.directive("address", function () {
return {
controller: function($scope) {
$scope.dto = {};
$scope.dto.data = {"id": 1, "country": "Poland", "city": "Warsaw", "street": "Nullowska 15"};
},
Passing in the JSON like this isn't how I'd do it as it's kind of hacking in the data binding and you probably don't get two-way binding. I'd use an isolate scope.
Your directive would be used without handlebars, to link up the scope variable:
<address address-data='senderAddress'></address>
And then you'd include a scope option in the directive definition:
app.directive("address", function () {
return {
restrict: "E",
templateUrl: "/directives/address.html",
scope: {
addressData: '='
}
}
});
The bare equals-sign '=' tells Angular to double-bind the parent scope variable referenced in the address-data attribute to the child scope variable addressData. This is done automatically by normalizing the name "address-data" into the JS-style "addressData." If you wanted to name the two scope variables differently, you could do innerAddressData: '=addressData' instead.
If you do it like this, you don't need a linking function at all and the binding still should work.
OK, I solved it, in case anyone has similar issues, it may help to check if the scope is set to true and to check if JSON is parsed from string ;-).
app.directive("address", function () {
return {
restrict: "E",
templateUrl: "/directives/address.html",
scope: true, // creates its own local scope
link: function(scope, element, attributes) {
attributes.$observe('addressData', function(data) {
if (!data)
return;
scope.dto = {};
// works almost fine but in 2nd case data is also filled
scope.dto.adData = angular.fromJson(data);
})
}
}
});

Any way to dynamically load angular directives?

Here's a short fiddle:
http://jsfiddle.net/aSg9D/
Basically, neither <div data-foo-{{letterA}}></div> nor <div data-ng:model="foo-{{letterB}}"></div> are interpolated.
I'm looking for a way to dynamically load one of several inline templates.
Pardon me if this has already been asked before, but I searched and couldn't find it.
I believe Radim Köhler has the correct answer. Just before it was posted, I hacked together something to load directives from another directive like this:
angular.module('myApp', []).directive('loadTmpl', function($compile) {
return {
restrict: 'A',
replace: true,
link: function($scope, $element, $attr) {
$element.html("<div data-card-"+$attr.loadTmpl+"></div>");
$compile($element.contents())($scope);
}
};
});
And:
<div data-load-tmpl="{{directiveName}}"></div>
I think that's the minimalist approach, but there's probably something wrong with it, so just look at the answer below.
Let's adjust it this way (the udpated fiddle). The view:
<div my-selector name="letterA"></div>
<div my-selector name="letterB"></div>
the controller:
function myCtrl($scope) {
$scope.letterA = 'bar';
$scope.letterB = 'baz';
}
And here is new directive mySelector, containing the selector
.directive('mySelector',
[ '$templateCache','$compile',
function($templateCache , $compile) {
return {
scope: {
name: '='
},
replace: true,
template: '',
link: function (scope, elm, attrs) {
scope.buildView = function (name) {
var tmpl = $templateCache.get("dir-foo-" + name);
var view = $compile(tmpl)(scope);
elm.append(view);
}
},
controller: ['$scope', function (scope) {
scope.$watch('name', function (name) {
scope.buildView(name);
});
}],
};
}])
.run(['$templateCache', function ($templateCache) {
$templateCache.put("dir-foo-bar", '<div data-foo-bar></div>');
$templateCache.put("dir-foo-baz", '<div data-foo-baz></div>');
}])
In case you like it, all credits goes to Render a directive inside another directive (within repeater template) and AngularJS - Directive template dynamic, if you don't, blame me.

Categories