*Please note: there is a Plunker link:
https://plnkr.co/edit/PAINmQUHSjgPTkXoYAxf?p=preview
At first I wanted to pass an object as parameter on directive click event,
(it was too complex for me), so i decide to simplify it by sending the event and the object separately.
In my program the object is always undefined in the view-controller and the view itself in oppose to the Plunker example.
In the Plunker example it's undefined on the controller only on the first passing event (the second directive click event works fine).
I don't know why I get 2 different results in the simple Plunker simulation and my massive code, I hope both cases are 2 different results of the same logic issue.
A solution with passing an object as parameter from directive by event function will be welcome as well.
HTML
<pick-er get-obj-d="getObj()" obj-d="obj"></pick-er>
View-Controller
function mainController($scope)
{
$scope.test = "work";
$scope.getObj = function(){
$scope.test = $scope.obj;
}
}
Directive:
function PickerDirective()
{
return {
restrict: 'E',
scope: // isolated scope
{
obj : '=objD',
getObj: '&getObjD'
},
controller: DirectiveController,
template:`<div ng-repeat="item in many">
<button ng-click="sendObj()">
Click on me to send Object {{item.num}}
</button>
</div>`
};
function DirectiveController($scope, $element)
{
$scope.many =[{"num":1,}];
$scope.sendObj = function() {
$scope.obj = {"a":1,"b":2, "c":3};
$scope.getObj();
}
}
}
I your case, will be more simple to use events, take a look at this Plunker:
https://plnkr.co/edit/bFYDfhTqaUo8xhzSz0qH?p=preview
Main controller
function mainController($scope)
{
console.log("mainCTRL ran")
$scope.test = "work";
$scope.$on('newObj', function (event, obj) {
$scope.obj = obj;
$scope.test = obj;
});
}
Directive controller
function DirectiveController($scope, $element)
{
$scope.many =[{"num":1,}]
$scope.sendObj = function() {
$scope.$emit('newObj', {"a":1,"b":2, "c":3} )
}
}
return {
restrict: 'E',
controller: DirectiveController,
template:'<div ng-repeat="item in many"><button ng-click="sendObj()">Click on me to send Object {{item.num}}</button></div>'
}
Related
I am loading the template from angular-service but that's not updating the template unless i use the $rootScope.$appy(). but my question is, doing this way this the correct approach to update the templates?
here is my code :
var app = angular.module('plunker', []);
app.service('modalService', function( $rootScope ) {
this.hide = function () {
this.show = false;
}
this.showIt = function () {
this.show = true;
}
this.setCategory = function ( category ) {
return this.showPath = category+'.html'
}
this.showCategory = function (category) {
this.setCategory( category )
$rootScope.$apply(); //is this correct?
}
})
app.controller('header', function($scope) {
$scope.view = "home view";
});
app.controller('home', function($scope, modalService) {
$scope.name = 'World';
$scope.service = modalService;
});
//header directive
app.directive('headerDir', function( modalService) {
return {
restrict : "E",
replace:true,
templateUrl:'header.html',
scope:{},
link : function (scope, element, attrs) {
element.on('click', '.edit', function () {
modalService.showIt();
modalService.showCategory('edit');
});
element.on('click', '.service', function () {
modalService.showIt();
modalService.showCategory('service');
})
}
}
});
app.directive('popUpDir', function () {
return {
replace:true,
restrict:"E",
templateUrl : "popup.html"
}
})
Any one please advice me if i am wrong here? or can any one show me the correct way to do this?
click on links on top to get appropriate template to load. and click on the background screen to close.
Live Demo
If you don't use Angular's error handling, and you know your changes shouldn't propagate to any other scopes (root, controllers or directives), and you need to optimize for performance, you could call $digest on specifically your controller's $scope. This way the dirty-checking doesn't propagate. Otherwise, if you don't want errors to be caught by Angular, but need the dirty-checking to propagate to other controllers/directives/rootScope, you can, instead of wrapping with $apply, just calling $rootScope.$apply() after you made your changes.
Refer this link also Angular - Websocket and $rootScope.apply()
Use ng-click for handling the click events.
Template:
<div ng-repeat="item in items">
<div ng-click="showEdit(item)">Edit</div>
<div ng-click="delete(item)">Edit</div>
</div>
Controller:
....
$scope.showEdit = function(item){
....
}
$scope.delete = function(item){
....
}
If you use jquery or any other external library and modify the $scope, angular has no way of knowing if something has changed. Instead if you use ng-click, you let angular track/detect change after you ng-click handler completes.
Also it is the angular way of doing it. Use jquery only if there is no other way to save the world.
In the interest of abstraction, I'm trying to pass a scope between directives with little success... Basically, this is a modal type scenario:
Directive A - handles click function of on screen element:
.directive('myElement', ['pane', function(pane){
return {
restrict: 'A',
scope: {},
link: function(scope,elem,attrs){
//im going to try and call the form.cancel function from a template compiled in another directive
scope.form = {
cancel: function(){
pane.close();
}
};
scope.$watch(function(){
var w = elem.parent()[0].clientWidth;
elem.css('height',(w*5)/4+'px');
});
elem.on('click', function(){
//this calls my service which communicates with my other directive to 1) display the pane, and 2) pass a template compiled with this directive's scope
pane.open({
templateUrl: 'views/forms/edit.html',
scope: scope //I pass the scope to the service API here
});
});
}
}
}])
I have a service called 'Pane' to handle the API:
.service('pane',['$rootScope', function($rootScope){
var open = function(data){
$rootScope.$broadcast('openPane',data); //this broadcasts my call to open the pane with the template url and the scope object
};
var close = function(){
$rootScope.$broadcast('closePane');
};
return {
open: open,
close: close
}
}]);
Finally, directive B is lying in wait for the 'openPane' broadcast which includes the template url and the scope:
.directive('pane',['$compile','$templateRequest','$rootScope', function($compile,$templateRequest,$rootScope){
return {
restrict: 'A',
link: function(scope,elem,attrs){
var t;
scope.$on('openPane', function(e,data){ //broadcast is received and pane is displayed with template that gets retrieved
if(data.templateUrl){
$templateRequest(data.templateUrl).then(function(template){
//this is where the problem seems to be. it works just fine, and the data.scope object does include my form object, but calling it from the template that opens does nothing
t = $compile(template)(data.scope);
elem.addClass('open');
elem.append(t);
}, function(err){
console.log(JSON.stringify(err));
});
}
else if(data.template){
t = $compile(angular.element(data.template))(data.scope);
elem.addClass('open');
elem.append(t);
}
else console.log("Can't open pane. No templateUrl or template was specified.")
});
scope.$on('closePane', function(e,data){
elem.removeClass('open');
t.remove();
});
}
}
}])
The problem is that when the last directive, 'pane', receives the 'openPane' broadcast, it opens and appends the template just fine, but when i call the function 'form.cancel()' defined in my original directive like so:
<button type="button" ng-click="form.cancel()">Cancel</button>
... nothing happens. Truth is, I'm not sure what I'm doing is legit at all, but i want to understand why it isn't working. The ultimate goal here is to be able to pass the scope of one directive, along with a form template, to the Pane directive, so all my forms (which are controlled by their own directives) can be 'injected' into the pane.
Without a running example I'm suspecting the likely cause to be the scope of your scope when passed to your pane template. The scope itself does get passed and used when you compile your pane template, but its closure is lost along the way, so you likely can't see pane service which is part of the directive factory closure and form.cancel uses.
I've written a simplified example that does work and doesn't rely on closures bt rather on local variables. You could accomplish a similar thing if you called .bind(pane) on your scope.form.cancel function and within replace pane by this.
So here's a working example and this is its code:
/* ************ */
/* Pane service */
class PaneService {
constructor($rootScope) {
console.log('pane service instantiated.', this);
this.$rootScope = $rootScope;
}
open(template, scope) {
this.$rootScope.$emit('OpenPane', template, scope);
}
close(message) {
this.$rootScope.$emit('ClosePane', message);
}
}
PaneService.$inject = ['$rootScope'];
/* ************************* */
/* Pane directive controller */
class PaneController {
constructor($rootScope, $compile, $element) {
console.log('pane directive instantiated.', this);
this.$compile = $compile;
this.$element = $element;
$rootScope.$on('OpenPane', this.open.bind(this));
$rootScope.$on('ClosePane', this.close.bind(this));
}
open(event, template, scope) {
console.log('pane directive opening', template, scope);
var t = this.$compile(template)(scope);
this.$element.empty().append(t);
}
close(evet, message) {
console.log('pane directive closing', message);
this.$element.empty().append('<strong>' + message + '</strong>');
}
}
PaneController.$inject = ['$rootScope', '$compile', '$element'];
var PaneDirective = {
restrict: 'A',
controller: PaneController,
controllerAs: 'pane',
bindToController: true
}
/* *************** */
/* Page controller */
class PageController {
constructor(paneService, $scope) {
console.log('page controller instantiated.', this);
this.paneService = paneService;
this.$scope = $scope;
}
open() {
console.log('page controller open', this);
this.paneService.open('<button ng-click="page.close(\'Closed from pane\')">Close from pane</button>', this.$scope);
}
close(message) {
console.log('page controller close');
this.paneService.close(message);
}
}
PageController.$inject = ['paneService', '$scope'];
angular
.module('App', [])
.service('paneService', PaneService)
.directive('pane', () => PaneDirective)
.controller('PageController', PageController);
And page template is very simple:
<body ng-app="App">
<h1>Hello Plunker!</h1>
<div ng-controller="PageController as page">
<button ng-click="page.open()">Open pane</button>
<button ng-click="page.close('Closed from page')">Close pane</button>
</div>
<div pane></div>
</body>
I have a AngularJs directive that creates a property and callback function on its isolated scope:
.directive('testButton', [function () {
return {
restrict: 'A',
controller: 'TestDirectiveController as vmDirective',
scope: {
myCallBack:'&myCallBack',
myVariable: '=myVariable'
},
template: function (element, attrs) {
return '<button data-ng-click="vmDirective.onButtonClicked(2)">Set myVariable = 2</button>';
}
};}])
In the directive a button gets clicked and it executes the onButtonClicked function. This then sets a scope variable and calls the $scope.myCallBack function.
The callBack function gets executed and does the following:
console.log($scope.linkedVariable);
The problem is the $scope.linkedVariable has not yet been updated and at that stage the $scope.linkedVariable is still the previous value.
When I wrap the above code in a setTimeout the correct value is retrieved: setTimeout(function(){console.log($scope.linkedVariable)}, 2000);
My Question is, how to properly pass the value to the onCallBack function.
Please see full code example below:
angular.module('application',[])
.directive('testButton', [function () {
return {
restrict: 'A',
controller: 'TestDirectiveController as vmDirective',
scope: {
myCallBack:'&myCallBack',
myVariable: '=myVariable'
},
template: function (element, attrs) {
return '<button data-ng-click="vmDirective.onButtonClicked(2)">Set myVariable = 2</button>';
}
};
}])
.controller("TestDirectiveController", ['$scope', function($scope){
var self = this;
self.onButtonClicked = function(value){
$scope.myVariable = value;
$scope.myCallBack();
};
}])
.controller("TestController", ['$scope', function($scope){
var self = this;
$scope.linkedVariable = null;
self.onCallBack = function(){
console.log($scope.linkedVariable);
setTimeout(function(){console.log($scope.linkedVariable)}, 2000);
};
}])
HTML:
<div data-ng-controller="TestController as vm">
<div data-test-button="" data-my-call-back="vm.onCallBack()" data-my-variable="linkedVariable"></div>
</div>
jsfiddle: http://jsfiddle.net/ff5ck0da/1/
I found a more acceptable/correct way of overcoming my problem thanks to http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters.
Instead of accessing the $scope.linkedVariable in the controller, I now accept the value as a parameter to the function.
To get this to work I had to change the function declaration in the HTML to:
data-my-call-back="vm.onCallBack"
The controller function declaration:
self.onCallBack = function(myVariable){
console.log(myVariable);
};
the directive can then call the function like:
self.onButtonClicked = function(value){
$scope.myCallBack()(value);
};
Please see a updated JSFiddle: http://jsfiddle.net/ff5ck0da/9/
You can even change the settimeout to
setTimeout(function(){console.log($scope.linkedVariable)}, 0);
this will push the resolution of the variable to the bottom of the async stack.
And thus evaluate after the angular digest loop is done ( in essence the variable value is set)
If you dont want to use settimeout you can use this:
self.onCallBack = function(){
var accessor = $parse($scope.linkedVariable);
$scope.value = angular.copy(accessor($scope.$parent));
console.log($scope.linkedVariable);
};
here you are essentially telling angular to not use a copy but the actual parent variable.
I have a cancel function in my controller that I want to pass or bind to a directive. This function essentially clears the form. Like this:
app.controller('MyCtrl', ['$scope', function($scope){
var self = this;
self.cancel = function(){...
$scope.formName.$setPristine();
};
}]);
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
cancel : '&onCancel'
},
templateUrl: 'form.html'
};
});
form.html
<div>
<form name="formName">
</form>
</div>
However, the $setPristine() don't work as the controller don't have access on the form DOM. Is it possible to extend the functionality of controller's cancel within the directive so that I will add $setPristine()?
Some suggested using jQuery to select the form DOM, (if it's the only way) how to do that exactly? Is there a more Angular way of doing this?
Since the <form> is inside the directive, the controller should have nothing to do with it. Knowing it would break encapsulation, i.e. leak implementation details from the directive to the controller.
A possible solution would be to pass an empty "holder" object to the directive and let the directive fill it with callback functions. I.e.:
app.controller('MyCtrl', ['$scope', function($scope) {
var self = this;
$scope.callbacks = {};
self.cancel = function() {
if( angular.isFunction($scope.callbacks.cancel) ) {
$scope.callbacks.cancel();
}
};
});
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
callbacks: '='
},
templateUrl: 'form.html',
link: function(scope) {
scope.callbacks.cancel = function() {
scope.formName.$setPristine();
};
scope.$on('$destroy', function() {
delete scope.callbacks.cancel;
});
}
};
});
Use it as:
<custom-directive callbacks="callbacks"></custom-directive>
I'm not sure I am OK with this either though...
I want to compile a third-party api (uploadcare) to a directive.
The api will return the data info after uploaded in async then I want to do something with the return data in my controller but I have to idea how to pass the return data from directive to controller. Below is my code.
in js
link: function (scope, element, attrs) {
//var fileEl = document.getElementById('testing');
var a = function() {
var file = uploadcare.fileFrom('event', {target: fileEl});
file.done(function(fileInfo) {
//scope.$apply(attrs.directUpload)
//HERE IS MY PROBLEM.
//How can I get the fileInfo then pass and run it at attrs.directUpload
}).fail(function(error, fileInfo) {
}).progress(function(uploadInfo) {
//Show progress bar then update to node
console.log(uploadInfo);
});
};
element.bind('change', function() {a()});
}
in html
<input type="file" direct-upload="doSomething()">
in controller
$scope.doSomething = function() {alert(fileInfo)};
AngularJS allows to execute expression in $parent context with specified values, in your case doSomething().
Here's what you need to do that:
In directive definition, mark directUpload as expression:
scope: {
directUpload: "&"
}
In done callback, call:
scope.directUpload({fileInfo: fileInfo})
Update markup:
<input type="file" direct-upload="doSomething(fileInfo)">
To summorize: scope.directUpload is now a callback, which executes expression inside attribute with specifeid values. This way you can pass anything into controller's doSomething.
Read $compile docs for detailed explanation and examples.
Example you might find useful:
angular
.module("app", [])
.directive("onDone", function ($timeout) {
function link (scope, el, attr) {
$timeout(function () {
scope.onDone({
value: "something"
});
}, 3000)
}
return {
link: link,
scope: {
onDone: "&"
}
}
})
.controller("ctrl", function ($scope) {
$scope.doneValue = "nothing";
$scope.done = function (value) {
$scope.doneValue = value;
};
})
<body ng-controller="ctrl">
Waiting 3000ms
<br>
<div on-done="done(value)">
Done: {{doneValue}}
</div>
</body>
You can pass through an object to the scope of the directive using = within the directive to do two way data binding. This way you can make updates to the data within the directive on the object and it will be reflected in it's original location in the controller. In the controller you can then use $scope.watch to see when the data is changed by the directive.
Something like
http://plnkr.co/edit/gQeGzkedu5kObsmFISoH
// Code goes here
angular.module("myApp",[]).controller("MyCtrl", function($scope){
$scope.something = {value:"some string"}
}).directive("simpleDirective", function(){
return {
restrict:"E",
scope:{someData:"="},
template:"<button ng-click='changeData()'>this is something different</button>",
link: function(scope, iElem, iAttrs){
scope.changeData=function(){
scope.someData.value = "something else";
}
}
}
});