angular directive scope not binding data - javascript

I have a question, code like this:
HTML:
<div class="overflow-hidden ag-center" world-data info="target"></div>
js:
.directive('worldData', ['$interval', function($interval) {
return {
scope: {
chart: '=info'
},
template: '<div>{{chart.aaa}}</div>',
link: function($scope, element, attrs) {
$scope.target = {'aaa': 'aaa'};
aaa = $scope.chart;
}
}
}])
The chart value is undefined, and template no value, but when I declare $scope.target within controller, the code works, why?

This should be generally the pattern:
.controller('myController', function($scope){
$scope.target = {'aaa': 'aaa'}; //In reality, you'd normally load this up via some other method, like $http.
})
.directive('worldData', [function() {
return {
scope: {
chart: '=info'
},
template: '<div>{{chart.aaa}}</div>'
}
}])
--
<div ng-controller="myController">
<div class="overflow-hidden ag-center" world-data info="target"></div>
</div>
Alternatively, the directive could be responsible for going and fetching the data, and not pass in anything to it. You'd only want to consider that if you don't need the data in multiple places.

Related

Defining my own 'require' on a directive, but throw error - AngularJs

Let us say I have this html:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name="name">Hello, {{name}}!</my-directive>
</div>
with this simple controller:
myApp.controller('MyCtrl', function ($scope) {
$scope.name = 'Superhero';
});
And I have a directive in which I want to change the 'name' using require like this:
myApp.directive('myDirective', function($timeout) {
var controller = ['$scope', function ($scope) {
$scope.name = "Steve";
}];
return {
restrict: 'EA',
require: 'myName',
controller: controller,
link: function(scope, element, attrs, TheCtrl) {
TheCtrl.$render = function() {
$timeout(function() {
TheCtrl.$setViewValue('StackOverflow');
}, 2000);
};
}
};
});
But throws:
Error: No controller: myName
Here is the fiddle
But if I implement it using ng-model, works. Look here in this other fiddle
I have read that if you use 'require' in a directive, you need to have a controller for it.
So:
What I'm doing is wrong? It is not in this way? I need to do any other thing?
Well finally I got it.
Essencially what I'm trying to do is something called: 'Communication between directives using controllers'. I have found an article explaining this, and helped me a lot:
The view:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name>Hello, {{name}}!</my-directive>
</div>
As you see above, there are two directives: my-directive and my-name. I will call inside my-directive a function from the controller of my-name directive using require.
myDirective:
myApp.directive('myDirective', function($timeout) {
return {
require: 'myName',
link: function(scope, element, attrs, myNameCtrl) {
$timeout(function() {
myNameCtrl.setName("Steve");
}, 9000);
} // End of link
}; // return
});
myName:
myApp.directive('myName', function($timeout) {
var controller = ['$scope', function ($scope) {
// As I tried, this function can be only accessed from 'link' inside this directive
$scope.setName = function(name) {
$scope.name = name;
console.log("Inside $scope.setName defined in the directive myName");
};
// As I tried, this function can accessed from inside/outside of this directive
this.setName = function(name) {
$scope.name = name;
console.log("Inside this.setName defined in the directive myName");
};
}];
return {
controller: controller,
link: function(scope, element, attrs, localCtrl) {
$timeout(function() {
localCtrl.setName("Charles");
}, 3000);
$timeout(function() {
scope.setName("David");
}, 6000);
} // End of link function
};
});
Interesting and works like a charm. Here is the fiddle if you want to try it out.
Also, you can get communication between directives using events. Read this answer here on SO.

Data is not being transferred from one custom directive to another if we use $http to fetch data

I have two different custom directive and wanted to pass data from one directive to another. The data is coming from server call. Problem is http being a asyn call doesn't return data upfront and controller of another widget doesnt receive it and it renders it's html without having it. here is the complete code (I have removed some code that might not make sense in the problem)-
The service which hits server is -
angular.module('myModule')
.service('MyService', [
'$http',
function($http) {
this.getData = (someId) => {
var url = someUrl + '/' + someId;
return $http.get(url);
};
}
]);
and the first directive that calls service and set "anotherData" in the scope to be transferred to another directive is -
angular.module('myModule')
.directive('myDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
scope: {
data: '='
},
templateUrl: 'my-template.html',
controller: ['$scope', function($scope) {
MyService.getData ($scope.data.id).then((response) => {
$scope.anotherData = response.data;
});
}]
}
}]);
and my-template.html from which i am calling another directive is (notice anotherData is passed here -
<other-directive mode="display" data="data" anotherData="anotherData" ></other-directive>
The other directive that should receive "anotherData" but giving me no result is -
angular.module('otherModule')
.directive('otherDirective', [function() {
return {
restrict: 'E',
scope: {
id: '#',
data: '=',
mode: '#',
anotherData: '#'
},
templateUrl: 'other-template.html',
controller: ['$scope', '$element', function ($scope, $element) {
console.log("other data in widget after server call:");
///THIS IS UNDEFINED.
console.log($scope.anotherData);
}],
link: function ($scope, $element) {
}
}
}]);
and other-template.html has iframe to display youtube widget -
<iframe width="{{anotherData.videoWidth}}"
height="{{anotherData.videoHeight}}"
src="{{anotherData.videoURL}}" frameborder="0" allowfullscreen></iframe>
You should use dashes instead of camel case, like this:
<other-directive mode="display" data="data" another-data="anotherData" ></other-directive>
Also, you are binding for text instead of two-way binding an object, so change the definition in your directive to this:
anotherData: '='
This should do the trick
<other-directive another-data=anotherData></other-directive>
You need to be caarefull with the camel case in html!

How to specify which $scope a directive uses?

I have a directive that is watching something on $scope, and it is getting the wrong $scope.
so I have a state setup that specifies a controller:
.state('app.mystate', {
url: 'search/:searchText',
views: {
'mainPane#': {
templateUrl: 'views/content/search.html',
controller: 'ABCController'
}
},
resolve: {
searchPromise: ['$http', '$stateParams', function ($http, $stateParams) {
console.log($stateParams.searchText);
return $http.get(...blah...blah).then(blahblah);
}]
}
})
When this state gets activated, it goes to this view:
<ul ...>
<div ng-repeat="...">
<li ...>
<div ng-include="'views/widgets/somewidget.html'"></div>
</li>
</div>
</ul>
the ng-include loads this, which has a specific controller specified. And a directive.
<div ng-controller="XYZController">
<my-chart chart="chart" ...></my-chart>
</div>
Here is my directive:
angular.module('app.directive')
.directive('myChart', function () {
return {
template: '<div></div>',
scope: {
chart: '='
},
// wrong scope!
scope.$watch('chart', function (chart) { }
I figured that the <div> that contains the my-chart directive, that directive would get the $scope that goes into XYZController.
But the directive is getting the scope that is injected into ABCController, not XYZController as I want/expected. I can see XYZContoller getting activated.
How do I get the $scope that is injected into XYZController be the same scope that the my-chart directive sees?
The sample code for you directive seems to have been truncated/broken during copy/paste so it's hard to see where that scope.$watch call is at. If it is in the directive definition, after the return block, then it's never getting called but I would expect it to be in the controller.
Having said that, you can specify the controller to be used by the directive in the directive attributes which would seem appropriate here, unless you want to use the directive with different scopes..
<div>
<my-chart chart="chart"></my-chart>
</div>
angular.module('app.directive')
.directive('myChart', function () {
return {
controller: "XYZController",
bindToController: true
template: '<div></div>',
scope: {
chart: '='
}
})
.controller('XYZController', function($scope){
$scope.$watch('chart', function (chart) { });
});
scope: true will create a scope that is prototypically inerited from the parent scope, so you should be able to $watch a property on the parent scope.
angular.module('app.directive')
.directive('myChart', function () {
return {
template: '<div></div>',
scope: true,
link: function(scope){
scope.$watch('chart', function (chart) { });
});

AngularJS How: Scope hierarchy for Directive

Directive in AngularJS: I find out that the elements inside an element with the directive do not inherit its "scope".
For example:
app
.controller('xxx', function($scope) {})
.directive('yyy', function() {
return {
scope: {},
link: function(scope,elem,attrs) {}
};
});
When we use it in the HTML:
<body ng-controller="xxx">
<div id='withD' yyy>
<div id='inside'>Inside the element with a directive</div>
</div>
</body>
"body" will have a scope whose $id may be 003;
then "#withD" will have an isolate scope $id=004;
the "#inside" will have the scope $id=003, which means the "#inside" inherits "body"'s scope.
If I use "transinclude" for the directive "yyy"; then "body" scope.$id=003, "#withD" scope.$id=004, "#inside" scope.$id=005; moreover, 003 has two children 004 and 005. However, I wanna make the element with the directive has an isolate scope and its child elements inherit the scope.
I read over "ui.bootstrap.tabs" source code but I do not like the style, for it is strange and also not make the parent element share its scope with child elements'; it looks like this:
app
.directive('xitem', function() {
scope: {},
controller: function($scope) {
$scope.subitem = [];
return {
add: function(xsubitem) {$scope.subitem.push(xsubitem);}
}
},
link: function(scope,elem,attrs) {}
})
.directive('xsubitem', function() {
require: '^xitem',
link: function(scope,elem,attrs,ctrl) {ctrl.add(elem);}
});
My expectation is that:
<div ng-controller="xxx">
<div yyy>
<button ng-click="sayHi()">Hi</button>
<div>
</div>
when you click the "Hi" button, the alert dialog will pop up with the message "Hello World" not "Error: Scope".
app
.controller('xxx', function($scope) {
$scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
return {
scope: {},
link: function(scope,elem,attrs) {
scope.sayHi = function(){alert('Hello World');};
}
};
});
Moreover, I tried this:
app
.controller('xxx', function($scope) {
$scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
return {
scope: {},
controller: function($scope, $compile) {$scope._compile = $compile;}
link: function(scope,elem,attrs) {
elem.children().forEach(function(one) {
scope._compile(one)(scope);
});
scope.sayHi = function(){alert('Hello World');};
}
};
});
Then it will pop up two alert dialogs with the message "Error: Scope" and "Hello World" respectively.
Now I found the solution - load template dynamically and use $compile to specify scope:
.controller('main', function($scope) {
$scope.sayHi = function() {alert('scope error');};}
)
.directive('scopeInherit', ['$http', '$compile', function($http, $compile) {
return {
scope: {},
link: function(scope, elem, attrs) {
scope.sayHi = function() {alert('hello world');};
scope.contents = angular.element('<div>');
$http.get(elem.attr('contentsURL'))
.success(function (contents) {
scope.contents.html(contents);
$compile(scope.contents)(scope);
});
},
};
}]);
Then we write HTML:
<div ng-controller="main">
<div scope-inherit contents="test.html"></div>
</div>
where there is a test.html:
<button ng-click="sayHi()">speak</button>
Then click on the "speak" button, it will pop up the alert dialog with "hello world"
To do what you want, you need to use a template (either as a string or a templateUrl). If angularjs would work how you expect it in this case then a lot of the angular directives wouldn't work right (such as ng-show, ng-click, etc).
So to work how you want it, change your html to this:
<script type="text/ng-template" id="zzz.html">
<button ng-click="sayHi()">Hi 2</button>
</script>
<div ng-controller="xxx">
<button ng-click="sayHi()">Hi 1</button>
<div yyy></div>
</div>
And update your directive definition to use a templateUrl (or you can provide the string as a template property)
app
.controller('xxx', function($scope) {
$scope.sayHi = function() {
console.error('Error: Scope in xxx', new Date());
};
})
.directive('yyy', function() {
return {
scope: {},
templateUrl: 'zzz.html',
link: function(scope, elem, attrs) {
scope.sayHi = function() {
console.log('Hello World in zzz', new Date());
};
}
};
});
Here's a plunker with this code: http://plnkr.co/edit/nDathkanbULyHHzuI2Rf?p=preview
Update to use multiple templates
Your latest comment was a question about what if you wanted to use different templates on the same page. In that case we can use ng-include.
html:
<div yyy contents="template1.html"></div>
<div yyy contents="template2.html"></div>
<div yyy contents="template3.html"></div>
js:
app
.controller('xxx', ...)
.directive('yyy', function() {
return {
scope: {
theTemplateUrl: '#contents'
},
template: '<ng-include src="theTemplateUrl"></ng-include>',
link: function(scope, elem, attrs) {
scope.sayHi = function() {
console.log('Hello World in yyy', new Date());
};
}
};
});
The benefit of using ng-include is that this is already built into angularjs and is well tested. Plus it supports loading template either inline in a script tag or from an actual url or even pre-loaded into the angular module cache.
And again, here is a plunker with a working sample: http://plnkr.co/edit/uaC4Vcs3IgirChSOrfSL?p=preview

$scope.item in directive is undefined

I have a problem with my directive and controller. The variable item in scope is undefined in directive, even though I passed it in html. This is my code:
app.js:
var app = angular.module("app", ["ngRoute"]);
app.config(["$routeProvider", function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "views/main.html",
controller: "MainCtrl"
});
}]);
controller.js:
app.controller("MainCtrl", function($scope) {
$scope.item = "x";
});
directive.js:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
templateUrl: "views/item.html"
};
});
index.html:
<div ng-view></div>
main.html:
<div example-directive item="item"></div>
item.html:
<div>{{ item }}</div>
UPDATE
I changed my code to:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: true,
templateUrl: "views/item.html"
};
});
and now there is "x" in scope.$parent.item. But why it isn't present inside directive?
Although it seems to work just fine with latest Angular stable http://plnkr.co/edit/6oXDIF6P04FXZB335voR?p=preview maybe you are trying to use templateUrl thats pointing to somewhere it doesn't exist.
Another thing, use primitives only when strictly needed. In case you ever need to modify value of item, you won't be able to do so since you are using a primitive. Plus, if you need "more info" to go inside your isolated scopes, and to avoid attribute soup (attr-this="that", attr-that="boop", my-otherstuff="anotheritem.member", etc) you can pass the two-way bind of an object that handle more data.
Or if you need to share state through multiple controllers, directives, etc, use a service instead and use dependency injection, and there's no need to pass in objects/primitives to your isolated scope, and you can assure state, and that's best practice "the Angular way".
This fiddle works: http://jsfiddle.net/HB7LU/2844/ which is essentially the same thing just without the route information.
var myApp = angular.module('myApp',[]);
myApp.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
template: "<div>{{ item }}</div>"
};
});
function MyCtrl($scope) {
$scope.item = 'Superhero';
}
With the view:
<div ng-controller="MyCtrl">
<div example-directive item="item"></div>
</div>
This leads me to believe it could be a scope issue. So try encapsulating the controller scope variable in a container:
$scope.cont = { item: 'x' };
And in the view
<div example-directive item="cont.item"></div>

Categories

Resources