How To Get Data Back From Angular Directive - angularjs

i hope somebody could help me with a small example, because angular is driving me crazy :(
I'm making by first time a Formular that should follow this structure:
Angular APP
mainController
---->smallController1
-------->otherElements
---->smallController2
-------->otherElements
---->Directive1 (instance 1)
---->anotherSmallController
---->Directive1 (instance 2)
The Directive1 receives many attributes, and each instance will allow the selection of many options, and the result of the user interaction should be stored in an object, and that object should be accessed from mainController for each instance separately.
Does anyone have an example that work like that?
Thanks in advance,
John

The best way to get data back from directive is to attach a model to directive's self scope.
var app = angular.module('app', []);
app.controller('mainController',
[
'$scope',
function($scope){
$scope.myObj = "Initial Value";
}
]);
app.directive('dirName', [
function(){
return {
restrict : 'A',
scope : {
obj : "=ngModel"
},
link : function(scope, element, attrs){
scope.obj = attrs.newValue;
}
};
}
]);
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body ng-app="app" ng-controller="mainController">
<input dir-name ng-model="myObj" new-value="Final Value">
</body>
</html>
You can check this bin as well : http://jsbin.com/fuqujo/1/edit?html,js,output

Use emit to send data to parent controller.
It may be receiver because listening to event.
Read about on emit and broadcast.
In your child controller or directive use:
$scope.$emit('myEvent', object);
This sends object to all parent controllers.
In parent controller use:
$scope.$on('myEvent', function(event, obj) { console.log(obj); });
To listen to emitted object.

Try this code out, it might help you:
http://plnkr.co/edit/L1NwUnNePofyRAQ6GVIg?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.masterData = [
{
entry: 'dunno'
},
{
entry: 'stuff'
}
]
})
app.directive('vessel', function() {
return {
replace: true,
scope: {
data: '=',
speciality: '#'
},
link: function(scope) {
scope.updateData = function() {
scope.data.entry = scope.speciality;
}
},
template: '<div>{{data.entry}} <button ng-click="updateData()">update</button></div>'
}
});
And the template:
<body ng-controller="MainCtrl">
<p>Master data {{masterData | json}}</p>
<div vessel data="masterData[0]" speciality="eggs"></div>
<div vessel data="masterData[1]" speciality="bacon"></div>
</body>
So, we have separate data models for each directive which are updated on user input, fitting your requirements. Right?

Related

Passing scope variable to directive's controller

My directive has separate controller in js file, which has a scope variable parameterDatabase which need to be populated from calling page. I am unable to find the way to pass value to it.
<body ng-app="testAPP" ng-controller="ctl">
Directive Here
<my-cust parameterDATABASE="dt"></my-cust>
<script >
APP = angular.module("testAPP",['schedule']);
APP.controller("ctl",function($scope)
{
$scope.dt = {date:"02-03-2017",sDay:"Thu",sTime:"01:00"};
}) // end of controller
APP.directive("myCust",function()
{
return{
scope:{
parameterDATABASE:'='
},
controller:"scheduleCtrl",
templateUrl:"templateForDirective.html"
}
})
</script>
The scheduleCtrl has a variable parameterDATABASE too.
part of Directive's contrller
var APP = angular.module('schedule',[]);
APP.controller('scheduleCtrl',function($scope,$filter)
{ $scope.parameterDATABASE=[]; // This is the variable I want to populate
..............
1) According to some angular naming conventions, the attribute name of a directive should be converted into camelCase.
So, parameterDATABASE in the html Directive should be parameter-database
So, inside the directive, you should use that as,
scope: {
parameterDatabase: '='
}
So, parameterDatabase maps to ==> parameter-database
2) you can also use, parameterdatabase directly in both places without capitalizing.
Eg: parameter-database="dt" in html directive
scope: {
parameterdatabase: '='
}
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app="myApp" ng-controller="ctl">
<isolate-scope-with-controller parameter-database="dt" add="addCustomer()"></isolate-scope-with-controller>
<script>
var app = angular.module("myApp", []);
app.controller("ctl",function($scope)
{
$scope.dt = {date:"02-03-2017",sDay:"Thu",sTime:"01:00"};
}) // end of
app.directive('isolateScopeWithController', function () {
var controller = ['$scope', function ($scope) {
console.log($scope.parameterDatabase)
}],
template = '<h1>I am from directive controller</h1><br><h2>{{parameterDatabase}}</h2>';
return {
restrict: 'EA', //Default in 1.3+
scope: {
parameterDatabase: '='
},
controller: controller,
template: template
};
});
</script>
</body>
</html>
PLEASE RUN THE ABOVE SNIPPET
Here is a working DEMO

Custom angular directive : how to watch for scope changes

I am writing a custom directive with a single field in its scope. This field is a dictionary with arrays in values.
I want the directive to react to any change made on this field : new entry, additional value in list, etc...
I'm just trying to figure out why :
my directive does not react when I change values in the dictionary.
directive is not even initialized with the initial dictionary.
Here is a simplified version of my script, where I only perform some logging in the sub-directive.Nothing happens when the button is clicked on the dictionary is modified :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module("myApp", [])
.controller("myCtrl", function($scope) {
$scope.dico = {};
$scope.dico["Paris"] = [];
$scope.dico["Paris"].push("Tour Eiffel");
$scope.dico["Paris"].push("Champs Elysees");
$scope.dico["London"] = [];
$scope.dico["London"].push("British Museum");
$scope.addPOI = function() {
$scope.dico["Wellington"] = [];
$scope.dico["Wellington"].push("Botanic Garden");
console.log($scope.dico);
};
})
.directive('subdirective', function() {
return {
restrict: 'E',
template: '<div><span ng-repeat="key in myDico">{{key}}</span></div>',
link: function(scope, element, iAttrs) {
console.log("test");
scope.$watch("myDico", function(newVal, oldVal) {
console.log("watch!");
console.log(newVal);
//update values...
}, true);
},
scope: {
myDico: '='
}
};
});
</script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<button ng-click="addPOI()">
Add POI
</button>
<div>
<subdirective myDico="dico"></subdirective>
</div>
</div>
</body>
</html>
I have tried to use $watch, $watchCollection, deep watch, but it does not seem to do the job.
You are missing scope binding definition in your Directive Definition Object.
scope: {
myDico: '='
}

How to update model asynchronously?

This code is invoking multiple get requests and updating model
from result of each get request.
http.get is asynchronous, does this extend to when the UI model is updated ?that if the get request returns data out of order (the 3'rd get request returns data before the 1'st request) then the third value for statusViewer directive
will be updated first on UI ? If not how can modify to update UI model when data is returned from get request ?
plnkr :
https://plnkr.co/edit/BjETLN7rvQ1hNRIm51zG?p=preview
plnkr src :
http-hello1.html:
{ "content" : "divContent" , "id" : "r1" }
http-hello2.html:
2. http-hello2.html
http-hello3.html:
3. http-hello3.html
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<status-viewer ng-repeat="sourceUrl in sourceUrls" url="sourceUrl"></status-viewer>
</div>
</body>
</html>
mytemplate.html:
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
$http.get(scope.url).success(function (data) {
scope.model = JSON.stringify(data);
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello1.html',
'http-hello2.html',
'http-hello3.html'
];
}
Update : The expected behavior is that the UI model will be updated in same order as 'success' callback is invoked, is my expected behavior assertion valid ?

Inspecting AngularJS scopes using the Batarang Chrome extension

I have an question about AngularJs scopes and especially the way those can be inspected with the Batarang Chrome extension.
I have the following html:
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
<div ng-controller="myCtrl">
<div enhanced-textarea ng-model="name"></div>
<div cmp>
<h3>{{name}}</h3>
<div notice></div>
</div>
</div>
<script src="lib/angular/angular.js"></script>
<script src="js/directives.js"></script>
<script src="js/controllers.js"></script>
<script src="js/app.js"></script>
</body>
</html>
Here are the directives:
'use strict';
angular.module('myApp.directives', [])
.directive('cmp', function () {
return {
restrict: 'A',
controller: 'cmpCtrl',
replace: true,
transclude: true,
scope: {
name: '='
},
template: '<div ng-transclude></div>'
};
})
.controller('cmpCtrl', ['$scope', '$element', '$attrs' , function ($scope, $element, $attrs) {
$scope.$parent.$watch('name', function (newVal) {
if (newVal) {
$scope.$parent.updatedSize = newVal.length;
}
}, true);
}])
.directive('enhancedTextarea', function () {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<textarea ng-transclude></textarea>'
};
})
.directive('notice', function () {
return {
restrict: 'A',
require: '^cmp',
replace: true,
scope: {
updatedSize: '='
},
template: '<div>{{size}}</div>',
link: function ($scope, $element, $attrs, cmpCtrl) {
$scope.$parent.$watch('updatedSize', function (newVal) {
if (newVal) {
$scope.size = newVal;
}
}, true);
}
};
});
and the controller:
'use strict';
angular.module('myApp.controllers', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.name = 'test';
}]);
When I inspect the scopes using batarang, I come up with the following conclusion:
Scope 002: ng-app
Scope 003: ng-controller (myCtrl)
Scope 004: ????
Scope 005: cmpCtrl (controller for cmp directive)
Scope 006: inside cmp (h3 and notice)
Scope 007: link function of notice directive
Is the above correct?
Also, my biggest interrogation is what does the 004 scope correspond to?
Full app is located on github here
See also screen capture below:
It's not that each $scope has to correspond to an element of your page. In fact in every AngularJS app there are a bunch of $scopes which aren't directly linked to any element.
In your case it's the ng-transclude which causes a child scope to be created.
Take a look at the implementation of AngularJS which causes the creation of your 004 $scope.
if (!transcludedScope) {
transcludedScope = scope.$new();
transcludedScope.$$transcluded = true;
scopeCreated = true;
}
https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L959
If you like to dig deeper yourself, go and set a breakpoint right here in your AngularJS file:
Then just use the call stack and follow the rabbit...
I also use this scenario to debug and inspect what is in the scope of an element, might be helpful:
You inspect the element with chrome dev tools
Once you select certain element you can get its scope by typing in console:
angular.element($0).scope()
You can get the controller in the same way just instead of scope() you can type controller()
In order to set a breakpoint in you code and look at in in chrome debugger (I sometimes find this easier than setting a breakpoint in dev tools) you can type:
debugger;
in your source and dev tool will stop there so you see the declared vars etc.

angularjs adding my own function to the existing ng-click

I have ng-click="foo()" which alert "foo"
In my own directive, if ng-click is found, I want to add another function to alert "bar"
I tried this
DEMO: http://plnkr.co/edit/1zYl0mSxeLoMU3yjoGBV?p=preview
and it did not work
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive("myAttr", function() {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', attrs.ngClick+';bar()');
}
}
})
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
I was also not able to another ng-* directive to this to make it work, i.e. el.attr('ng-focus', 'bar()');. It seems that I cannot change or add ng-* directive once it is rendered.
How can I achieve this, and what was I doing wrong?
app.directive("myAttr", function() {
return {
priority: 1,
compile: function(el, attrs) {
attrs.ngClick += ';bar()';
}
}
})
First of all you want a compile function, for when link is called, the ng-click directive is already set up.
The second important thing is to change the priority. You want to ensure that your directive is called before ng-click. ng-click has the default priority 0, so 1 is enough.
The last and important thing, which is not obvious, is that you don't want to change the element, but attrs itself. It is created only once per element. So when ng-click accesses it it would still contain the same value, if you changed the attribute on the element directly.
I think you can do what you want with ngTransclude.
app.directive("myAttr", function() {
return {
transclude:true,
template: '<span ng-click="bar()" ng-transclude></span>',
link: function(scope, el, attrs) {
}
}
});
Does that work?
EDIT
Okay what about this one?
app.directive("myAttr", function($compile) {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', 'bar()');
el.removeAttr('my-attr');
$compile(el)(scope);
}
}
});
While this could be done with compile as outlined above, that approach doesn't guarantee the order in which the ng-click items would be added to a DOM node (as you have already discovered), and is inherently slow (as has been pointed out by Words Like Jared.
Personally, I would just do something like this:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive('myAttr', function() {
return {
scope: true,
link: function(scope, el, attrs) {
if(attrs.hasOwnProperty('ngClick')){
scope.foo = function(){
scope.$parent.foo();
scope.$parent.bar();
}
}
}
};
});
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
Whats going on:
scope: true: By default directives do not create new scopes, simply sharing their parent scope. By setting scope: true, every instance of this directive will create a child scope, that will prototypically inherit from the parent scope.
Then you can simply override the method desired (foo()) and voila
Live demo:
http://plnkr.co/edit/8A8y96wAhqGEowFaRQUH?p=preview
I freely admit I may entirely misunderstand what you are trying to do. However, given the example you provided, I think you might be better served by separating concerns a little more.
It seems from your example that you are trying to trigger foo and bar together whenever your directive is present. If both foo and bar are concerns of the controller, then why not wrap them both up in another function and assign that function to the ng-click of your element. If foo is a concern of the controller, but bar is a concern of the directive, why not trigger the bar functionality directly from the directive code?
If the functionality wrapped up in 'foo' and 'bar' is suppose to be defined by the controller creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
$scope.pak = function() {
$scope.foo();
$scope.bar();
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="pak()" href="">click here!</a>
</body>
</html>
Or, if the functionality wrapped up in 'foo' is suppose to be defined by the controller creator, but the functionality wrapped up in 'bar' is suppose to be defined by the directive creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
});
app.directive('myAttr', function(){
return {
link: function(scope, element, attrs){
element.click(function(){
alert('bar');
});
}
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="foo()" href="">click here!</a>
</body>
</html>

Resources