I love angularjs, but I get so confused with directives lol.
I have the following:
//postcode grabber
app.directive('postcodes',
function ($rootScope, $http) {
return function (scope, element, attrs) {
element.bind('change', function () {
var targetSuburb = scope.$eval(attrs.targetSuburb);
alert(targetSuburb);
var modal_element = angular.element('#myModal');
var ctrl = modal_element.controller();
var url = '/postage/postcodes/?suburb=' + element.val();
ctrl.setModal(url);
modal_element.modal('show');
});
};
});
This my HTML:
<input type="text" class='form-control' ng-model="model.suburb" postcodes id='ca_suburb' target-suburb='ca_suburb' target-state='ca_state' target-postcode='ca_postcode'>
The alert is always "undefined" - Is there something I'm missing to be able to access the attributes correctly?
If you want to access the value of the model then
app.directive('postcodes', function ($rootScope, $http) {
return {
require: 'ngModel',
link: function (scope, element, attrs, controller) {
element.bind('change', function () {
console.log(controller.$viewVaue)
var modal_element = angular.element('#myModal');
var ctrl = modal_element.controller();
var url = '/postage/postcodes/?suburb=' + element.val();
ctrl.setModal(url);
modal_element.modal('show');
});
}
};
});
Demo: Fiddle
If you are looking to alert ca_suberb then just use
alert(attrs.targetSuburb);
Demo: Fiddle
If ca_suberb is a scope property then your code is working fine
Demo: Fiddle
If you want to get what is typed in, you should access via scope like this
alert(scope.model[attrs.targetSuburb]);
where attrs.targetSuburb should be the field name and it should be set like this since you define the model to be model.suburb
target-suburb='suburb'
Related
I am using the example for ngMessages from this post:
How to add custom validation to an AngularJS form?
It works OK as long as the blacklist is a static list of items.
I would like to dynamically generate the blacklist but the directive seems to render before the blacklist is populated.
This is the directive:
.directive('blacklist', function () {
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel) {
var blacklist = attr.blacklist.split(',');
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
return value;
});
}
};
});
This is the input where the directive is used:
<input type="text" id="DocumentName" name="DocumentName" class="form-control"
ng-model="$ctrl.document.DocumentName" ng-required="true"
blacklist="{{$ctrl.DocumentNames}}" />
In the controller when the blacklist is specified with static values it works as expected.
.component('documentDetail', {
templateUrl: '/app/document-detail/document-detail.template.html',
controller: ['Document',
function DocumentDetailController(Document) {
var self = this;
self.DocumentNames = "Install Direct Bill Invoice,Order Preacknowledgement,Order Acknowledgement"; });
When this is changed to get the DocumentNames with a service call it seems like the directive is rendered before the blacklist values are populated.
component('documentDetail', {
templateUrl: '/app/document-detail/document-detail.template.html',
controller: ['Document',
function DocumentDetailController(Document) {
var self = this;
var documentProfiles = Document.query();
documentProfiles.$promise.then(function () {
var bl = [];
for (var i = 0; i < documentProfiles.length; i++) {
bl.push(documentProfiles[i].DocumentName);
}
self.DocumentNames = bl.join(',');
});
When I inspect the element I can see the data has been populated:
But the validation acts like it is an empty string:
I tried wrapping it in a $timeout but the result was the same.
component('documentDetail', {
templateUrl: '/app/document-detail/document-detail.template.html',
controller: ['Document', '$timeout',
function DocumentDetailController(Document, $timeout) {
var self = this;
var documentProfiles = Document.query();
$timeout(function () {
documentProfiles.$promise.then(function () {
var bl = [];
for (var i = 0; i < documentProfiles.length; i++) {
bl.push(documentProfiles[i].DocumentName);
}
self.DocumentNames = bl.join(',');
});
});
How can I get these values to populate before the directive or input renders so that the blacklist can be dynamic? Thanks in advance for your help.
Use attr.$observe:
app.directive('blacklist', function () {
return {
require: 'ngModel',
link: function (scope, elem, attrs, ngModel) {
var blacklist = attrs.blacklist.split(',');
attr.$observe("blacklist",function(newValue) {
blacklist = newValue.split(',');
});
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
return value;
});
}
};
});
The observer function is invoked whenever the interpolated value changes.
For more information, see AngularJS attr.$observe API Reference
Update
Using the $validators API1
The accepted answer to the referenced question uses the $parsers and $formatters pipelines to add a custom synchronous validator. AngularJS 1.3+ added a $validators API so there is no need to put validators in the $parsers and $formatters pipelines:
app.directive('blacklist', function (){
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$validators.blacklist = function(modelValue, viewValue) {
var blacklist = attrs.blacklist.split(',');
var value = modelValue || viewValue;
var valid = blacklist.indexOf(value) === -1;
return valid;
});
}
};
});
Notice that since the blacklist is re-computed everytime the ngModelController does a validation, there is no need to add an $observe function.
For more information, see AngularJS ngModelController API Reference - $validators.
I'm trying to convert a string date so that it works on a html input with the type set to 'date'.
So, I have the following angular app:
(function() {
var app = angular.module('test', []);
app.controller('MainCtrl', function($scope) {
$scope.load = function() {
$scope.message='2017-12-23T00:00:00Z';
};
});
app.directive('convertDate', function() {
return {
restrict: 'A',
scope: {
ngModel: '='
},
link: function (scope) {
console.log(scope);
console.log(scope.ngModel);
if (scope.ngModel) scope.ngModel = new Date(scope.ngModel);
}
};
});
})();
Then my html is as follows:
<div ng-controller='MainCtrl'>
<input type="date" convert-date ng-model="message">
<button ng-click="load()">load</button>
</div>
When I click on the load button I get the following error:
Error: [ngModel:datefmt] http://errors.angularjs.org/1.6.4/ngModel/datefmt?p0=2017-12-23T00%3A00%3A00Z
I understand the error is because it's a string and I need a date, which its the reason for my directive.
But even with the directive I still get the error.
What am I missing?
Thanks
Colin
You can change your directive to following:
angular.module('app').directive('convertDate', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
if (!ctrl) return;
ctrl.$parsers.push(function(date) {
if (angular.isDate(date))
return new Date(date);
})
}
}
})
take a look at this working plunkr without error
https://plnkr.co/edit/8aSR1dlsRfDMrM7GfQQa?p=preview
It is because you are using same variable in ng-model for converting. So it encounters an error before your directive converts it.
According to me, you should convert it first and then assign to the ng-model variable in your controller.
Like this,
(function() {
var app = angular.module('test', []);
app.controller('MainCtrl', function($scope) {
$scope.load = function() {
var dateString = '2017-12-23T00:00:00Z';
$scope.message=new Date(dateString);
};
});
})();
No need to use directive
This is part of my AngularJS application
.controller('Littlebear',function($scope) {
$scope.spread='<h1>this is the</h1> Littlebear spread.'+
'<img ng-src="src/images/retro.png" alt="picture" ng-click="click()">';
})
.directive('spread', function($compile) {
var templateTemp='<p> try again </p>';
var directive = {};
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, atttributes) {
// bind element to data in $scope
templateTemp=$scope.spread;
return templateTemp;
};
return linkFunction;
};
directive.restrict = 'E'; /* restrict this directive to elements */
directive.template = templateTemp;
return directive;
})
I would like to set template = $scope.spread inside the directory.
If I console.log the templateTemp inside the linkFunction the value of templateTemp is exacly what I am looking for but ouside of that function is templateTemp=' try again ';
can anyone suggest any solution?
(PS: as you might imagine I am quite new to AngularJS)
Thanks Vincent
In link function you can do something as below.
In link function, I have compiled the desired html and replaced the directive element with the same.
.controller('Littlebear',function($scope) {
$scope.spread='<h1>this is the</h1> Littlebear spread.'+
'<img ng-src="src/images/retro.png" alt="picture" ng-click="click()">';
})
.directive('spread', function($compile) {
var templateTemp='<p> try again </p>';
var directive = {};
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, atttributes) {
// you can change and compile the html like this
//element.replaceWith($compile($scope.spread)); //Please Check this line<-----
//Please try this.
var html =$scope.spread;
var e =$compile(html)($scope);
element.replaceWith(e);
};
return linkFunction;
};
directive.restrict = 'E'; /* restrict this directive to elements */
directive.template = templateTemp;
return directive;
})
I'm trying to access the scope of the parent directive from a child directive.
Please see jsbin
http://jsbin.com/tipekajegage/3/edit
when I click the button I would like to the get value of prod.
var cpApp = angular.module('cpApp', []);
cpApp.directive('cpProduct', function(){
var link = function (scope, element, attr) {
console.log('scope.prod '+ scope.prod);
};
return {
link: link,
restrict: 'EA',
scope: {
prod: '='
}
};
});
cpApp.directive('mediaButtons', [function(){
var template =
'<button ng-click="addToFavoriteList()">get prod from parent</div>' +
'</button>';
var controller = function($scope){
var vm = this;
$scope.addToFavoriteList = function(event){
console.log($scope.$parent.prod);
//GET PROD?
};
};
return {
template: template,
restrict: 'E',
controller: controller,
scope: true
};
}]);
You need to pass in the parent function as a require and assign it to local scope:
var linkFunction = function (scope, element, attrs, cpProductCtrl) {
scope.prod=cpProductCtrl.prod;
You also need to define a controller on the parent function like so:
controller: function($scope){
this.prod=$scope.prod;
}
Then you can call your clickHandler method like so:
console.log($scope.prod);
Here is the JS Bin In case I missed describing anything.
I restructured your code a bit, created a controller in your cpProduct directive and required it in the mediaButton to access its scope.
See this plunker
I need to pass an Id defined in the directive to the associated controller such that it can be used in a HTTP Get to retrieve some data.
The code works correctly in its current state however when trying to bind the Id dynamically, as shown in other questions, the 'undefined' error occurs.
The Id needs to be defined with the directive in HTML to meet a requirement. Code follows;
Container.html
<div ng-controller="IntroSlideController as intro">
<div intro-slide slide-id="{54BCE6D9-8710-45DD-A6E4-620563364C17}"></div>
</div>
eLearning.js
var app = angular.module('eLearning', ['ngSanitize']);
app.controller('IntroSlideController', ['$http', function ($http, $scope, $attrs) {
var eLearning = this;
this.Slide = [];
var introSlideId = '{54BCE6D9-8710-45DD-A6E4-620563364C17}'; //Id to replace
$http.get('/api/IntroSlide/getslide/', { params: { id: introSlideId } }).success(function (data) {
eLearning.Slide = data;
});
}])
.directive('introSlide', function () {
return {
restrict: 'EA',
templateUrl: '/Modules/IntroSlide.html',
controller: 'IntroSlideController',
link: function (scope, el, attrs, ctrl) {
console.log(attrs.slideId); //Id from html present here
}
};
});
Instead of defining a controller div that wraps around a directive, a more appropriate approach is to define a controller within the directive itself. Also, by defining an isolated scope for your directive, that slide-id will be available for use automatically within directive's controller (since Angular will inject $scope values for you):
.directive('introSlide', function () {
// here we define directive as 'A' (attribute only)
// and 'slideId' in a scope which links to 'slide-id' in HTML
return {
restrict: 'A',
scope: {
slideId: '#'
},
templateUrl: '/Modules/IntroSlide.html',
controller: function ($http, $scope, $attrs) {
var eLearning = this;
this.Slide = [];
// now $scope.slideId is available for use
$http.get('/api/IntroSlide/getslide/', { params: { id: $scope.slideId } }).success(function (data) {
eLearning.Slide = data;
});
}
};
});
Now your HTML is free from wrapping div:
<div intro-slide slide-id="{54BCE6D9-8710-45DD-A6E4-620563364C17}"></div>
In your IntroSlide.html, you probably have references that look like intro.* (since your original code use intro as a reference to controller's $scope). You will probably need to remove the intro. prefix to get this working.
Require your controller inside your directive, like this:
app.directive( 'directiveOne', function () {
return {
controller: 'MyCtrl',
link: function(scope, el, attr, ctrl){
ctrl.myMethodToUpdateSomething();//use this to send/get some msg from your controller
}
};
});