Execution flow of a directive in angular js and using its attributes - angularjs

hello i would like to know the steps of execution of directives and some fundamental details about how the processing happens this would be very helpful for more customization of directives and exploit its features...
i have a sample template with a directive and the attributes needed and few html tags as shown
template1:-
<directive1 s-web-service-path="object.WebServicePath" >
<h1>any html content</h1>
</directive1>
my directive is i.e. it calls a web service to get its content
widgetsModule.directive("directive1", function ($http) {
try {
return {
restrict: "E",
replace: true,
transclude: true,
scope: {
sWebServicePath: "="
},
template: '<div ng-transclude><h1>My content {{result }}</h1> </div> ',
link: function (scope, element, attrs) {
var request = '<request struct>';
$http({ method: "POST", url: scope.sWebServicePath, data: request }).
success(function (data, status) {
scope.result = data;
}).
error(function (data, status) {
alert("Service Call Error");
})
}
}
}
catch (e) {
alert(e);
}
});
What is the difference between attrs and $scope in the link function... in above case
$scope.sWebServicePath gives me the value of object.WebServicePath i.e. something like "http://anypath.asmx"
but
attrs.sWebServicePath give me object.WebServicePath... why is this difference and how is it useful?
I know that putting "ng-transclude" would enable me to have
<h1>any html content</h1>
in the specified div of my template with in the directive but how does the execution happens?
and why is that return is to be written in a directive it returns a link function alright and it can be used for DOM manipulation but any example of when do i use it?
i know these might sound very fundamental but please do throw some light on the execution flow of the directive...or some good source of reference...thank you! and advice/tips on the usage of the parameters of the directive would be very helpful

this link has the best documentation on usage of directives they should have put this link under directive definition ... js came across this page while reading the angular documentation
http://docs.angularjs.org/api/ng.$compile

if you use http request in directive you dont forget for $watch because http calls are asynchronous.
Diference between attr and scope is:
scope - is own scope of directive
attr - are attribs of directive
i think so data passed through attr not have to always same as your own scope, because you can make your new own scope for directive but cant change attr.
may be helpful for you
about-those-directives
egghead.io
onehungrymind.com/angularjs-directives-basics/
2013 - angularjs-directives-using-isolated-scope-with-attributes
many sources
AngularJS-Learning

Related

Not able to access angular directive's isolated scope

First Directive:
app.directive("myDirectiveOne", function($rootScope){
return {
templateUrl : "/custom-one-html.html",
restrict: "AE",
replace:true,
scope: {
somedata: "=",
flags: "=",
functionone: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.firstFunction = function(){
console.log("First function is getting called")
}
$scope.$on('firstBroadcast',function(event, data){
$rootScope.$broadcast('secondBroadcast', data)
});
}
Second Directive:
app.directive("myDirectiveTwo", function($rootScope){
return {
templateUrl : "/custom-two-html.html",
restrict: "AE",
replace:true,
scope: {
data: "=",
functiontwo: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.secondFunction = function(){
console.log("Second function is getting called")
$rootScope.$broadcast('firstBroadcast', {})
}
$scope.$on('secondBroadcast',function(event, data){
$scope.callSomeFunctionWithData(data);
});
$scope.secondFunction();
$scope.editFunction = function(x){
console.log("This is the edit function", x);
}
Parent Controller:
$scope.parentFuntion = function(){
console.log("No trouble in calling this function")
}
So, the problem is when I try calling a function from a myDirectiveTwo html template, the controller which is active is the parent controller and not the isolated one.
May be it has something to do with the broadcasts I am using?
Html Code:
<div ng-repeat="x in data">
<h5>{{x.name}}</h5>
<button ng-click="editFunction(x)">Edit</button>
</div>
The strange thing is I get data values and ng-repeat works fine on load. But, when I click on the button it doesnt do anything. And if I add the same function in the parent controller, it gets called.. :(
How do I make the isolated scope controller active again..?
The problem is that ng-repeat creates a child scope, therefore the editFunction ends up being on the parent scope.
From docs
... Each template instance gets its own scope, where the given loop variable is set to the current collection item ...
Docs here
You can verify that this is the issue by getting your button element's scope and checking the $parent, as such angular.element(document.getElementsByTagName("button")).scope()
Although considered code smell, you can append $parent to your function call in order to access it, though keep in mind this now places a dependency on your HTML structure.
<button ng-click="$parent.editFunction(x)">Edit</button>
The issue was that I was using a deprecated method replace:true. This caused the unexpected scenarios.. As #Protozoid suggested, I looked at his link and found the issue.. To quote from the official documentation:
When the replace template has a directive at the root node that uses transclude: element, e.g. ngIf or ngRepeat, the DOM structure or scope inheritance can be incorrect. See the following issues: Incorrect scope on replaced element: #9837 Different DOM between template and templateUrl: #10612
I removed replace:true and its fine now :)
This is the link:
Here

get scope in directive

everybody. I am new to AngularJS and find it very interesting, but I am a bit unclear about the following situation.
app.controller("myCtrl", ['$scope', '$http', '$filter', function ($scope, http, filter)
{
$http({
method: CTHocUri,
url: 'get',
async: true,
}).then(function (response) {
$scope.CTH = response.data; //response.data=two Object
})
}])
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
link: function (scope, element, attr) {
console.log(scope.CTH); // I can't get... scope.CTH=undefined
}
}])
I can't get value scope.CTH. ??
There is a VERY simple way to SEE what the issue is:
In your html, merely surround your directive with an ng-if conditional based on CTH:
<span ng-if="CTH">
<my-custom-1></my-custom-1>
</span>
That's it.
What this does is that your directive will only be born/instantiated when CTH is set to non-null/non-undefined, i.e. when $http returns asynchronously. With this, your code will work. As such, there is no need for watching or broadcasting for this type of simple serialization of asynchronous events when you can simply leverage Angular's built-in '$watch's.
NOTE 1: I do not know what your architecture is and am not suggesting what you need to do. I am merely showing you why your code won't work and how you have been caught in a simple asynchronicity trap.
NOTE 2: I assume your directive is 'as -is'. In other words you have access to the parent's scope (i.e. the controller's scope). If your directive's scope were isolated (i.e. you had a scope:{..(attrs)..} defined in the directive) you will not have 'simple' access to the parent scope. Your code will be different--eg you can pass bits and pieces of your scope to the directive attrs. However, the ng-if will still work since it is on the controller's scope.
I hope this helps.
The directive and the controller are two completely different entities. If it helps you can think of them as different classes. They will not share the same scope.
You could create an isolated scope on the directive and pass the CTH variable into it. Conceptually something like this:
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
scope { cth : "=" },
link: function (scope, element, attr) {
console.log(scope.cth);
}
Then in your HTML, do something like this:
<div ng-controller="myCtrl">
<my-Custom1 cth="CTH">
</div>
when the directive initializes, the scope.CTH is still not initialized since its initialization accurses inside an $http call.
one way to overcome this is to broadcast and event from the controller and catch it from inside the directive. see this plnkr and angularjs scope's docs
app.controller('MainCtrl', function($scope, $timeout) {
$scope.name = 'World';
$timeout(function() {
$scope.test = "test";
$scope.$broadcast('MyEvent')
}, 500);
});
app.directive('test', function() {
return {
link: function(scope, elm, attr) {
scope.$on('MyEvent', function() {
console.log(scope.test);
})
}
}
})

How do you require angular template html?

When creating custom directives, if you want to put your view/template html in separate files, Angular seems to load the template from a public URL, making an HTTP request for it.
How do you include this template HTML inline while keeping it in a separate file?
With ES6 nothing is impossible:
import yourTemplate from 'path/to/file';
// inject $templateProvider in start point of your application
$templateProvider.put('path/to/file', yourTemplate);
$templateProvider at its own is simple $cacheFactory instance, where you can put any html, by any key, that can be used in ng-include or simply used in your directive as shown below:
//Directive
import yourTemplate from 'path/to/file';
that is used within directive configuration:
...,
controller: xxx,
template: yourTemplate,
link: () => { ... }
...
Use directive to fix this
HTML
<div custom-include url="{{url}}"></div>
Directive
app.directive('customInclude', ['$http', '$compile', '$timeout', customInclude]);
function customInclude($http, $compile, $timeout) {
return {
restrict: 'A',
link: function link($scope, elem, attrs) {
//if url is not empty
if (attrs.url) {
$http({ method: 'GET', url: attrs.url, cache: true }).then(function (result) {
elem.append($compile(angular.element(result.data))($scope));
//after sometime we add width and height of modal
$timeout(function () {
//write your own code
}, 1, false);
});
}
}
};
}
"inline" in a separate file is contradicting itself.
Since angular is client side code, loading a template that is stored in it's own separate file will always require a http request.
The only way to avoid these requests is to add them inline with your other code, so not in a separate file.

AngularJS passing json to directive

I'm still new in Angular but i'm doing some progress.. i think :)
I have problem passing json file from controller to directive using isolated scope.
This is my controller which talk to factory "dataArchive":
.controller('graphCtrl', ['$scope', 'dataArchive', function($scope, dataArchive){
dataArchive.get().then(function(data){
$scope.getData = data;
});
}]);
Then i have directive which is using isolated scope.
.directive('dataGraph', function($compile){
return {
restrict: 'AE',
replace: true,
scope: {
getArchiveData: '='
},
template: '<div id="chartdiv"></div>',
link: function (scope, element, attrs){
var dati = scope.getArchiveData;
console.log(dati);
};
};
});
And this is my HTML:
<div ng-controller="graphCtrl">
<data-graph get-archive-data="getData"></data-graph>
</div>
In console i always get 'undefined'.
Where i am wrong and is this a good way?
Thanks you all.
Since this code is async:
dataArchive.get().then(function(data){
$scope.getData = data;
});
The link function will run before the data is set on getData, and therefore the isolated scope variable will not be set at this time. So, I believe you are seeing a timing issue.
To make sure that your directive binding is correct. Try to set $scope.getData to a static value (e.g. $scope.getData = [{ data: 'value'}]). This should work.
Also, Angular checks for changes (to rebind) based on object reference. So, you might need to define $scope.getData in the controller (outside of the async call). Then you might want to push all the data in (instead of replace the entire object with the assignment).
Hope this helps.

Writing an angular directive with asynchronous data

I'm writing a small app that uses the Instagram API. I'm trying to write a directive to display a list of the logged in users images. I have a back end api that calls the Instagram API and returns the results.
.directive('userPhotos', function($http)
{
return {
restrict: 'E',
template: '<ul class="user-photos">'
+'<li ng-repeat="image in images">'
+'<img src="{{image.images.low_resolution.url}}">'
+'</li>'
+'</ul>',
replace: true,
controller: function($scope, $element, $attrs){
$scope.images;
$scope.serviceURL = "/api/photos/"+$attrs.photosPerLoad;
$scope.loadPhotos = function()
{
$http.get(this.serviceURL)
.success(function(response){
$scope.images = response.data;
})
.error(function(response){
// show error
});
}
},
link: function($scope, element, attribute){
$scope.loadPhotos();
}
}
})
This works in that it displays the images as desired once the result of the API call is complete. However, as I have defined a src attribute for the img tag in the template during the initial phase of the Angular process this is called and I get the following.
GET http://myapp.dev/%7B%7Bimage.images.low_resolution.url%7D%7D 500 (Internal Server Error)
I'm still a bit vague on the process Angular goes through as it's putting a directive together but I think I understand that initially, for the ng-repeat region there is a copy of the contents of this region made to be filled once the images binding is populated.
Anyhow, can anyone advise how I could get around this?
So to clarify... this was solved by changing the template part of the directive to...
template: '<ul class="user-photos">'
+'<li ng-repeat="image in images">'
+'<img ng-src="{{image.images.low_resolution.url}}">'
+'</li>'
+'</ul>',
Thanks! #madhead.

Resources