Hi I want to make a validation directive. It basically will call a remote validation on the server. I would expect something like this:
<input type="text" id="nome" required ng-model="client.context" available="checkAvailableContexts">
and that should call a method on my ClientController like this:
$scope.checkAvailableContexts = function(contexto, callbacks) {
service.checkContextAvailability(contexto, callbacks);
}
and this is my service method:
this.checkContextAvailability = function(context, externalCallbacks) {
var url = angular.url("/clients/context/" + context + "/available"),
callback = {
success: function(){},
error: function(){}
};
$.extend(callback, externalCallbacks)
$.ajax({
url: url,
data: { context: context },
success: function(data){
$timeout(function(){
callback.success(data);
},100);
},
type: "GET",
dataType: "json",
contentType: "application/json;charset=UTF-8onte"
});
};
my directive is something like this:
.directive('available', function(){
return {
restrict: "A",
require: "ngModel",
replace: true,
link: function(scope, element, attrs, controller){
controller.$parsers.unshift(function (viewValue) {
//call the ClientsController method passing viewValue
//and callbacks that update the validity of the context
})
}
}
})
But I can't figure out how to call the clientController from inside the directive.
I know I have attrs.available as the name of the function. But I can't execute it on the controller scope passing my parameters;
Any help would be much appreciated!
You don't need to call the control, you just need to share variables with it.
What you can do is share an object with the directive, like:
<input type="text" id="nome"
required ng-model="client.context"
available="availableOpts">
At your scope, you add a variable with shared vars, like:
$scope.availableOpts = {
check: checkAvailableContexts,
context: ...;
callbacks: ...;
}
At you directive, you get it at the scope:
.directive('available', function(){
return {
restrict: "A",
require: "ngModel",
replace: true,
scope: {available: "="}
link: function(scope, element, attrs, controller){
// At this point, you have an variable at directive scope, that is shared
// with the controller, so you can do:
scope.available.check(scope.availabe.context, scope.available.callbacks);
// the controler will have now a var $scope.availableOpts.result
// with the return of the function tha you call
}
}
})
Related
I want one of the Radio Button to be selected once the page is loaded, from another question on stackoverflow i found that Radio Button will be check if the value of the input attribute is equal to the value of model applied on the Radio Button. But i am unable to access the model($parent.selectedItem) on Radio Button in link function inside child directive. Api i used in example is a placeholder but in realtime i will have a property selected which will be true/false which I want to bind to the $parent.selectedItem
var mainApp = angular.module('mainApp', []);
mainApp.factory('myFactory', function ($http) {
var myFactory = {
myMethod: function () {
var promise = $http.get('https://jsonplaceholder.typicode.com/users').then(function (response) {
return response.data;
});
return promise;
}
};
return myFactory;
});
Controller:
mainApp.controller('myController', function ($scope, myFactory) {
myFactory.myMethod().then(function (result) {
$scope.data = result
})
});
Directives:
mainApp.directive('parent', function (myFactory) {
return {
restrict: 'E',
replace: true,
scope: true,
templateUrl: 'parent.html',
link: function (scope, element, attrs, ctrl) {
myFactory.myMethod().then(function (result) {
scope.Model = result
})
}
}
});
mainApp.directive('child', function () {
return {
restrict: 'E',
scope: {
Model: '=ngModel'
},
replace: true,
require: 'ngModel',
templateUrl: 'child.html',
link: function (scope, element, attrs, ctrl) {
// unable to access scope.selectedItem
console.log(scope.selectedItem)
}
}
});
HTML:
// mainpage.html
<body ng-app="mainApp"><parent></parent></body>
//parent.html
<div><child ng-model = "Model"></child></div>
//child.html
<div ng-repeat="item in Model"><input type="radio" name="itemSelected"
ng-value="item" ng-model="$parent.selectedItem"/>{{item.name}}</div>
when you require ngModel in the child directive, what you're basically requiring is its controller, this controller is then injected into your link function as the 4th parameter, in your case the ctrl argument.
so right now your ngModel might work, but it is not in your link function because you're expecting it to exist on the scope as selectedItem, but on your scope you have declared it as Model (not selectedItem). However, you also have access to the ngModel controller, so you could ask for its value there through its controller: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController.
ex:
ctrl.$viewValue
// or
ctrl.$modelValue
//whichever serves your purpose
In my page controller I get data from an ajax call using ngResource:
clientResource.query(
{
searchText: vm.search.text,
pageSize: vm.pageSize
},
(data, headers) => {
vm.clients = data;
vm.headers = JSON.parse(headers("X-Pagination"))
// ...
I have a directive for the pagination which is simply:
<ix-pager headers="vm.headers"></ix-pager>
In the directive controller, I have:
function ixPagerController($scope) {
var vm = this;
vm.headers = $scope.headers;
}
Now when the directive renders and the directive controller fires, $scope.headers is undefined, which is because the AJAX call hasn't returned yet. But when it does and vm.headers is set, this doesn't update the model on the directive. So I can change my directive to use a link function with a watch statement, like so:
return {
templateUrl: "/app/partials/pager.html",
restrict: "E",
controller: ixPagerController,
controllerAs: "vm",
replace: true,
link: function (scope, element, attrs) {
scope.$watch("headers", function (newValue, oldValue) {
if (newValue) {
//scope.headers = newValue;
}
});
},
scope: {
headers:"="
}
}
The problem is, at the commented out line, if I set a breakpoint, the scope.headers value is ALREADY the correct value, i.e. it has already been set. However, on the directive, template:
<pre>{{vm.headers|json}}</pre>
Still shows nothing. It's almost as if there's a missing digest or something. How do I get the model on the directive to update the view correctly?
This is what seems to work:
link: function (scope, element, attrs, ctrl) {
var c = ctrl;
scope.$watch("headers", function (newValue, oldValue) {
if (newValue) {
c.headers = newValue;
}
});
},
I am stuck with this for quite a long time.
In a directive, I would like to create another directive on the fly, based on a function. Instead of having 4 directive declarations, I would prefer to create a new directive in each 'tab' directive, that is to say each time a tab attribute is set in a DOM element.
Here is a part of the code (config is a factory that is use to configure some stuff) :
.directive('tab', function(config) {
return {
require: '^panelHandler',
restrict: 'A',
scope: true,
link: function(scope, elem, attrs, ctrl) {
ctrl.addPane(scope);
scope.select = function() {
ctrl.select(scope);
};
},
};
})
.directive('page1', directiveConfigurer('page1.html'))
.directive('page2', directiveConfigurer('page2.html'))
.directive('page3', directiveConfigurer('page3.html'))
.directive('page4', directiveConfigurer('page4.html'));
function directiveConfigurer(fileName) {
newDirective.$inject = ['config'];
return newDirective;
function newDirective(config) {
var directive = {
restrict: 'E',
scope: true,
templateUrl: config.filesDirectory + fileName,
};
return directive;
}
}
Thanks for your help.
EDIT :
Config...
angular.module('appLogic', ['socket-factory', 'data-factory', 'panelHandler-module'])
.factory('config', function() {
return {
filesDirectory : '../../templates/pages/',
fieldsNumber : 5,
};
});
and what I need...
link: function(scope, elem, attrs, ctrl) {
ctrl.addPane(scope);
//.directive('page' + number, directiveConfigurer(name))
scope.select = function() {
ctrl.select(scope);
};
},
If the directives are essentially the same, except for the template url, then you can just create a single directive and provide the concrete url path as an attribute:
<page src="page1.html">
To do that, use a function for templateUrl property of the directive definition object:
.directive("page", function(){
return {
templateUrl: function(tElem, tAttr){
return "/base/path/" + tAttr.src;
},
//...
};
});
My goal is to output a value (from a service) through a element directive so that the html will look like this <msg msg="alertMsg"></msg> and out pops a value from the service.
Here is my code thus far:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: {//something here to pass MsgService to template },
template: 'Message:{{MsgService.getAlertMsg()}}'
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
HTML would parse/compile to...
<msg msg="alertMsg">Message: default</msg>
What other code do I need?
If a service wont work directly, Should I access it through a controller?
app.directive("msg", function() {
return {
restrict: "E",
scope: {
getMsg: '&msg'
},
controller: 'MsgController',
template:'Message:{{getMsg()}}'
};
}]);
app.controller('MsgController', ['MsgService' , function(MsgService){
this.getAlertMsg = function(){
return MsgService.getAlertMsg();
};
}]);
HTML would parse/compile to...
<msg msg="getAlertMsg()">Message: default</msg>
Sorry for any errors in code or function use, I'm fairly new to Angular.
You can use the link function of the directive. This function is called once for every rendered instance of your directive. It receives, among other things, the scope of your directive. You can extend your scope very easily with the result of calling the MsgSevice.getAlertMsg() service method:
var app = angular.module("app", []);
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: true,
template: 'Message:{{msg}}',
link: function (scope, $element, attrs) {
scope.msg = MsgService.getAlertMsg();
}
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
Later on, I presume you will want to just display the alert message from the msg DOM attribute of the msg directive. Achieving this is much more simple, since AngularJS is already prepared for this common use case. The solution involves creating an isolate scope. The isolate scope can be populated with properties from the parent environment. One possibility is to use the value of a DOM attribute from your directive's element using the "#" syntax. In this case you won't even need the entire MsgService service:
app.directive("msg", function () {
return {
restrict: "E",
scope: {
"msg": "#"
},
template: 'Message:{{msg}}'
};
});
Simplest would be to set the service on your scope and use that in your template:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: { },
template: 'Message:{{MsgService.getAlertMsg()}}',
link: function(scope, element, attrs) {
scope.MsgService = MsgService;
}
};
}]);
I have a custom directive that uploads a file to amazon and contains a callback(onComplete).
When the callback is complete, I would like to attach a value to the $scope of the controller in which the directive is created. In this case, the scope of Invite.
Both Invite and fineUploader extend the same angular module.
HTML(simplified):
<div ng-controller="Invite" class="apply">
<div fine-uploader ng-switch-when="file" upload-extensions="jpg,jpeg,png,gif"></div>
</div>
Directive:
directive('fineUploader', function() {
return {
restrict: 'A',
require: '?ngModel',
link: function($scope, element, attributes, ngModel) {
$scope.uploader = new qq.s3.FineUploader({
debug: true,
element: element[0],
request: {
endpoint: 'ballentines-bar-project.s3.amazonaws.com',
accessKey: 'AKIAIPT6J4T6XZXV3VWA'
},callbacks: {
onComplete: function(id, fileName, responseJSON) {
if (responseJSON.success === true) {
console.log(this.getKey(id));
console.log($scope);
$scope.test = this.getKey(id);
}
}
},
signature: {
endpoint: '/s3/'
},
iframeSupport: {
localBlankPagePath: '/success.html'
},
retry: {
enableAuto: true // defaults to false
},
deleteFile: {
enabled: false
},
text: {
uploadButton: '<p>Upload File</p>'
},
template:
'<div class="qq-uploader">' +
'<div class="qq-upload-button btn btn-info">{uploadButtonText}</div>' +
'<ul class="qq-upload-list" ><h2>Your files</h2></ul>' +
'</div>',
});
}
};
}).
Controller
controller('Invite', function(
$scope,
$localStorage,
$http
){
$scope.$storage = $localStorage.$default({
"formkey": "1MRSAWTRl5-PnVEoy3tD63BL3q_v2mnAhtqa9bdZk-zg",
"draftResponse": "[]",
"pageHistory": "0",
});
$scope.liking = liking;
$scope.post = function(){
$http.post('/signup.php', $scope.$storage).
success(function(data, status, headers, config){
console.log(data);
});
};
FB.Event.subscribe('edge.create',
function(href, widget) {
liking = true;
}
);
})
You either need to pass the items from the parent scope to the directive (through isolated scope). Or, do as #MaximShoustin says and remove the isolated scope from your directive.
So, option 1:
scope: { directiveProperty: '=nameOfAttributeThatContainsParentProperty' },
Or, option 2:
Remove the isolated scope declaration scope: {}, from the directive. This will allow the directive to extend the scope of it's containing scope.
I would try at least two options:
[1]
change scope: {}, in directive to:
`scope: { test: '#'},`
This makes the test method visible in the private scope.
[2]
The second option try removing the isolate scope a.e: scope: {},