How to pass JSON data to AngularJS directive - javascript

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);
})
}
}
});

Related

Angular: Variable template inside directive

In my Angular template I use an attributive directive as follows:
HTML:
<div id="my-template-one" my-template-directive></div>
JS:
// ...
.directive('myTemplateDirective', ['myconfig', function (myconfig) {
return {
templateUrl: myconfig.TEMPLATE_PATH + 'my-template-one.html',
controller: function ($scope, $rootScope) {
// code
},
controllerAs: 'dir'
}
}]);
For including another template, my-template-two.html, on another page, I would like to use the same directive. I do not want to duplicate the directive. How can I pass the template as an variable?
HTML on another page:
<div id="my-template-two" my-template-directive></div>
My goal is that somehow I can tell my directive to render my-template-two.html when this HTML is called.
The templateUrl property value may be a function which takes two arguments tElement and tAttrs and returns a string value:
app.directive('myTemplateDirective', ['myconfig', function (myconfig) {
return {
templateUrl: function (tElem, tAttrs) {
var template = "my-template-one.html";
if (tAttrs.use) {
template = tAttrs.use;
};
return myconfig.TEMPLATE_PATH + template;
},
controller: function ($scope, $rootScope) {
// code
},
controllerAs: 'dir'
}
}]);
Usage
<div my-template-directive use="my-template-two.html"> </div>
For more information, see AngularJS Comprehensive Directive API -- template

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);
})
}
}
}
})();

One controller's scope blocks other scopes in Angularjs

I have a set of tabs which all have a directive in them:
<div class="col-md-9 maincols" id="formEditor">
<tabset>
<tab heading="New subscriber" select="isSelected('newSub')">
<new-subscriber></new-subscriber>
</tab>
<tab heading="Existing subscriber" select="isSelected('existingSub')">
<existing-subscriber></existing-subscriber>
</tab>
<tab heading="Landing page" select="isSelected('landing')">
<landing-page></landing-page>
</tab>
</tabset>
</div>
All these 3 directives have been defined similarly like this:
angular.module('myApp')
.directive('newSubscriber', function () {
return {
restrict: 'E',
scope: {},
replace: true,
templateUrl: 'scripts/newsubscriber/newsubscriber.html',
controller: 'newsubscriberCtrl'
};
}); //... and so on
I am (probably wrongly) under the impression that because I have set scope: {} for all the directives, they should now have completely isolated scopes and leave each other alone.
But that is not the case and bindings from the first directive's controller manage to stop values in the second or third controller from being binded
for example in newsubscriberCtrl I have:
app.controller('newsubscriberCtrl', ["$scope", "$routeParams", "UserMessages", "FormProvider", function ($scope, $routeParams, UserMessages, FormProvider) {
$scope.formId = $routeParams.formId;
var newSubscriberForm = new FormProvider.Form($scope);
angular.extend($scope, newSubscriberForm);
$scope.title = UserMessages.exampleText.genericPageTitle;
$scope.description = UserMessages.exampleText.genericPageDescription;
$scope.validationMessages = {
contactNotSaved: UserMessages.validationMessages.contactNotSaved,
contactCreatedOk: UserMessages.validationMessages.contactCreatedOk,
contactNotCreated: UserMessages.validationMessages.contactNotCreated,
requiredField: UserMessages.validationMessages.requiredField,
passwordMismatch: UserMessages.validationMessages.passwordMismatch,
isOpen: false
}
}]);
which is overriding the similar object in existingSubscriber controller:
app.controller('existingsubscriberCtrl', ["$scope", "$routeParams", "UserMessages", "FormProvider", function ($scope, $routeParams, UserMessages, FormProvider) {
$scope.formId = $routeParams.formId;
var existingSubscriberForm = new FormProvider.Form($scope);
angular.extend($scope, existingSubscriberForm);
$scope.title = UserMessages.exampleText.genericPageTitle;
$scope.description = UserMessages.exampleText.genericPageDescription;
$scope.validationMessages = {
contactNotSaved: UserMessages.validationMessages.contactNotSaved,
contactSavedOk: UserMessages.validationMessages.contactSavedOk,
requiredField: UserMessages.validationMessages.requiredField,
passwordMismatch: UserMessages.validationMessages.passwordMismatch,
isOpen: false
}
}]);
So in the view of both directives <pre>{{validationMessages | json }}</pre> the validationMessages object has the props of the first controller.
Why is this happening? Am I missing to understand a concept here? How Can I isolate these controllers from each other and comfortably have similar props in the controllers without them affecting each other?
Side note: I strongly want to avoid having to prefix everything on all scopes with their controller name, e.g $scope.newSubscriber.validationMessages and so on... as that would defeat the whole point pretty much as I will effectively one big controller for the whole tab section and directives would also be pointless.
Angular is on v.1.3.0-beta.11
angular-ui-bootstrap is on v.0.10.0
You have reuse the same controller newsubscriberCtrl in /app/scripts/formbanner/formbanner.js:
.directive('formBanner', function () {
return {
restrict: 'E',
replace: 'true',
templateUrl: 'scripts/formbanner/formbanner.html',
controller: 'newsubscriberCtrl'
};
});
The existingSubscriber directive have the formBanner as a child directive, plus the formBanner directive doesn't have an isolated scope.
Therefore, the $scope that get injected into the newsubscriberCtrl of formBanner is the same as the scope of the existingSubscriber!!
I've tried removing the controller property in the formBanner directive and I saw it works as expected.
Have you tried this?
angular.module('myApp')
.directive('newSubscriber', function () {
return {
restrict: 'E',
scope: { someValue = "&validationMessages" },
replace: true,
templateUrl: 'scripts/newsubscriber/newsubscriber.html',
controller: 'newsubscriberCtrl',
link: function (scope, iElm, iAttrs) {
var x = scope.someValue();
// x = your messages
}
};
});
In your controller
$scope.someValue
EDIT Disclaimer: this is sort of from memory. When I was facing something similar I felt this to be rather enlightning:
http://umur.io/angularjs-directives-using-isolated-scope-with-attributes/

How to pass a collection to a directive in angular.js?

Please forgive my lack of understanding.
I pass the name of a collection to my directive:
<ul tag-it tag-src="preview_data.preview.extract.keywords"><li>Tag 1</li><li>Tag 2</li></ul>
The directive is defined:
app.directive('tagIt', function (){
return {
restrict: 'A',
link: function(scope,elem, attr) {
elem.tagit();
console.log(attr.tagSrc); //the name of my collection, but how do I access it?
}
}
});
How do I access my collection from the directive and make sure my directive is called when the collection is populated? Here is how preview_data.preview.extract.keywords gets populated.
app.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
console.log('config');
$routeProvider.when("/", {
templateUrl: "/templates/addItem.html",
controller: "AddItemController",
resolve: {
loadData: addItemCtrl.loadData
}
});
});
var addItemCtrl=app.controller("AddItemController", function ($scope, $route, $sce, Preview) {
var title = decodeURIComponent($route.current.params.title);
var ua = decodeURIComponent($route.current.params.ua);
var uri = decodeURIComponent($route.current.params.uri);
$scope.preview_data = {
uri: uri,
title: title,
ua: ua
}
//pass parameters to web preview API
Preview.get(uri, ua, title).then(function (data) {
$scope.preview_data.preview = data;
if (data.embed.html) {
$scope.preview_data.preview.embed.html = $sce.trustAsHtml(data.embed.html);
}
}, function (data) {
alert('Error: no data returned')
});
});
You need to set the variable in the directive scope and set the template to iterate between the tags:
template : '<li data-ng-repeat="tag in tagSrc">{{tag.name}}</li>',
scope : {
tagSrc : '='
},
And will became this:
app.directive('tagIt', function (){
return {
restrict: 'A',
template : '<li data-ng-repeat="tag in tagSrc">{{tag.name}}</li>',
scope : {
tagSrc : '='
},
link: function(scope,elem, attr) {
console.log(attr.tagSrc);
}
}
});
the '=' attribute will tells to angular to use a tw way binding with the array passed in the directive declaration in the HTML.
Here is a plunker with a working example.
And here is a good arcticle explaning the directive's attributes and life cycle.
I hope it helps.
[EDIT]
If you want just iterate the array, without creating some different behavior in the list items, you can just simply use the ng-repeat directive:
<ul>
<li data-ng-repeat="tag in tags">{{tag.name}}</li>
<ul>

How to require a controller in an angularjs directive

Can anyone tell me how to include a controller from one directive in another angularJS directive.
for example I have the following code
var app = angular.module('shop', []).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/js/partials/home.html'
})
.when('/products', {
controller: 'ProductsController',
templateUrl: '/js/partials/products.html'
})
.when('/products/:productId', {
controller: 'ProductController',
templateUrl: '/js/partials/product.html'
});
}]);
app.directive('mainCtrl', function () {
return {
controller: function ($scope) {}
};
});
app.directive('addProduct', function () {
return {
restrict: 'C',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
//console.log(cartController);
}
};
});
By all account I should be able to access the controller in the addProduct directive but I am not. Is there a better way of doing this?
I got lucky and answered this in a comment to the question, but I'm posting a full answer for the sake of completeness and so we can mark this question as "Answered".
It depends on what you want to accomplish by sharing a controller; you can either share the same controller (though have different instances), or you can share the same controller instance.
Share a Controller
Two directives can use the same controller by passing the same method to two directives, like so:
app.controller( 'MyCtrl', function ( $scope ) {
// do stuff...
});
app.directive( 'directiveOne', function () {
return {
controller: 'MyCtrl'
};
});
app.directive( 'directiveTwo', function () {
return {
controller: 'MyCtrl'
};
});
Each directive will get its own instance of the controller, but this allows you to share the logic between as many components as you want.
Require a Controller
If you want to share the same instance of a controller, then you use require.
require ensures the presence of another directive and then includes its controller as a parameter to the link function. So if you have two directives on one element, your directive can require the presence of the other directive and gain access to its controller methods. A common use case for this is to require ngModel.
^require, with the addition of the caret, checks elements above directive in addition to the current element to try to find the other directive. This allows you to create complex components where "sub-components" can communicate with the parent component through its controller to great effect. Examples could include tabs, where each pane can communicate with the overall tabs to handle switching; an accordion set could ensure only one is open at a time; etc.
In either event, you have to use the two directives together for this to work. require is a way of communicating between components.
Check out the Guide page of directives for more info: http://docs.angularjs.org/guide/directive
There is a good stackoverflow answer here by Mark Rajcok:
AngularJS directive controllers requiring parent directive controllers?
with a link to this very clear jsFiddle: http://jsfiddle.net/mrajcok/StXFK/
<div ng-controller="MyCtrl">
<div screen>
<div component>
<div widget>
<button ng-click="widgetIt()">Woo Hoo</button>
</div>
</div>
</div>
</div>
JavaScript
var myApp = angular.module('myApp',[])
.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomethingScreeny = function() {
alert("screeny!");
}
}
}
})
.directive('component', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl) {
scope.screenCtrl = screenCtrl
}
}
})
.directive('widget', function() {
return {
scope: true,
require: "^component",
link: function(scope, element, attrs, componentCtrl) {
scope.widgetIt = function() {
componentCtrl.componentFunction();
};
}
}
})
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}

Categories