I've created some basic directive. It works well if I use it with some objects in html file
<some-directive some-data="123"></some-directive>
But if I dynamically load this object to my webpage:
//getting html source as a string, then appending it to DOM:
elem.html("<some-directive some-data='123'></some-directive>");
The directive doesn't work (object is being added properly to DOM)
app.directive('someDirective', function (notes, parts) {
return {
restrict: 'AE',
scope: {
someData: '='
},
link: function (scope, elem, attrs) {
console.log("directive fired");
}
};
});
What can I do to make it work properly?
For dynamic directives, you have to use $compile service that compiles scope into template. Look at sample below, <some-directive-wrapper /> will add <some-directive /> element into itself and compile scope value
var app = angular.module('app', []);
app.directive('someDirective', function () {
return {
restrict: 'AE',
scope: {
someData: '='
},
template: '<h2>someDirective compiled, someData is {{someData}}</h2>',
link: function (scope, elem, attrs) {
console.log("directive fired");
}
};
});
app.directive('someDirectiveWrapper', function ($compile) {
return {
restrict: 'AE',
link: function (scope, elem, attrs) {
//get value from ajax maybe
//and set to scope
scope.data = 123;
//replace directive with another directive
elem.html('<h1>someDirectiveWrapper compiled </h1>\n<some-directive some-data="data"></some-directive>');
//compile scope value into template
$compile(elem.contents())(scope);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<some-directive-wrapper></some-directive-wrapper>
</div>
Related
How can I pass a child attribute directive's scope or attr value to a parent directive?
Given widget directive, with in-viewport attribute directive, I want to update the attribute inView each time the document is scrolled, and pass the updated value to the parent directive widget:
<widget in-viewport></widget>
In Viewport directive: passed in as an attribute of parent directive "widget"
angular.module('app').directive('inViewport', function() {
return {
restrict: 'A',
scope: false, // ensure scope is same as parents
link: function(scope, element, attr) {
angular.element(document).on('scroll', function() {
// I've tried binding it to attr and parent scope of "widget" directive
attr.inView = isElementInViewport(element);
scope.inView = isElementInViewport(element);
});
}
};
});
Widget Directive:
angular.module('app').directive('widget', function() {
return {
restrict: 'AE',
scope: {
inView: '='
},
transclude: false,
templateUrl: 'directives/widgets/widgets.tpl.html',
link: function(scope) {
console.log('In Viewport: ', scope.inView); // Null
Here is the way you can access parent directive variables,
angular.module('myApp', []).directive('widget', function() {
return {
restrict: 'E',
template: '<viewport in-view="variable"></viewport> <h1>{{variable}}</h1>',
link: function(scope, iAttrs) {
scope.variable = 10;
}
}
}).directive('viewport', function() {
return {
restrict: 'E',
scope: {
inView: "=",
},
template: '<button ng-click="click()">Directive</button>',
link: function(scope, iElement, iAttrs) {
scope.click = function() {
scope.inView++;
}
}
}
});
HTML
<div ng-app="myApp" ng-controller="Ctrl1">
<widget></widget>
</div>
Here is the working jsfiddle
http://jsfiddle.net/p75DS/784/
If you have any question, ask in the comment box
Here is a working fiddle using your directive structure:
http://jsfiddle.net/ADukg/9591/
Markup is like this:
<div ng-controller="MyCtrl" style="height: 1200px;">
{{name}}
<hr>
<widget in-viewport></widget>
</div>
Just scroll the window to trigger the event. Note that the parent directive has a watch just to prove that the var gets updated...
var myApp = angular.module('myApp',[]);
myApp.directive('inViewport', function($timeout) {
return {
restrict: 'A',
scope: false, // ensure scope is same as parents
link: function(scope, element, attr) {
angular.element(window).bind('scroll', function() {
console.log('Called');
$timeout(function() {
scope.inView++;
}, 0);
});
}
};
});
myApp.directive('widget', function() {
return {
restrict: 'AE',
transclude: false,
template: '<p>This is a widget</p>',
link: function(scope) {
scope.inView = 0;
console.log('In Viewport: ', scope.inView); // Null
scope.$watch('inView', function(newVal, oldVal) {
console.log('Updated by the child directive: ', scope.inView);
});
}
}
});
function MyCtrl($scope) {
$scope.name = 'Angular Directive Stuff';
}
You can expose an API on your parent directive and use isolateScope() to access it.
Here's a working fiddle.
var app = angular.module("app",[]);
app.directive("widget", function($rootScope){
return {
template: "<div>Scroll this page and widget will update. Scroll Y: {{scrollPosY}}</div>",
scope: {}, // <-- Creating isolate scope on <widget>. This is REQUIRED.
controller: ['$scope', function DirContainerController($scope) {
$scope.scrollPosY = 0;
// Creating an update function.
$scope.update = function(position) {
$scope.scrollPosY = position;
$scope.$digest();
};
}],
}
});
app.directive("inViewport", function($window, $timeout, $rootScope){
return {
restrict: 'A',
link:function(scope, element, attrs, parentCtrl){
// Get the scope. This can be any directive.
var parentScope = element.isolateScope();
angular.element(document).on('scroll', function() {
// As long as the parent directive implements an 'update()' function this will work.
parentScope.update($window.scrollY);
console.log('parentScope: ', parentScope);
});
}
}
});
I'm injecting insecure html into some <div>, like this:
<div class="category-wrapper" ng-bind-html="content"></div>
this html has angularjs "code" ($scope.content is loaded with something like this):
<script type='text/javascript' src='giveus.js'></script>
<div class="giveus-wrapper" ng-controller="GiveUsController">{{variable1}}</div>
Note that this snippet has ng-controller. GiveUsController is lazy loaded at the same time that the embedded html (not in head). There is no error declaring this controller because It has been already tested.
My controller is as easy as:
angular.module("tf").controller('GiveUsController', function ($scope, $http)
{
console.debug("GiveUsController loaded");
$scope.variable1 = "hi!";
}
there is no console debug nor variable1 assignment
It looks like there is no controller binding to that <div>.
I don't know how I can inject html with angular controller and make it work...
Any idea?
You could do what you are wanting with a bit of manual html compilation. Here is an approach that is essentially a directive wrapper for the $compile service. Observe the following example and usage...
<div class="category-wrapper" ng-html="content"></div>
.controller('ctrl', function($scope) {
$scope.content = '<div class="giveus-wrapper" ng-controller="GiveUsController">{{variable1}}</div>'
})
.controller('GiveUsController', function($scope) {
console.log('hello from GiveUsController')
$scope.variable1 = 'I am variable 1'
})
.directive('ngHtml', ['$compile', function ($compile) {
return function (scope, elem, attrs) {
if (attrs.ngHtml) {
elem.html(scope.$eval(attrs.ngHtml));
$compile(elem.contents())(scope);
}
scope.$watch(attrs.ngHtml, function (newValue, oldValue) {
if (newValue && newValue !== oldValue) {
elem.html(newValue);
$compile(elem.contents())(scope);
}
});
};
}]);
JSFiddle Link - demo
Angular for itself don't bind the ng-directives that are added into the DOM.
The $sce.compile or $compile helps angular to read which elements are added into the actual DOM, also for use the $compile you must use a directive.
Should be like that:
var m = angular.module(...);
m.directive('directiveName', function factory(injectables) {
return = {
priority: 0,
template: '<div></div>', // or // function(tElement, tAttrs) { ... },
transclude: false,
restrict: 'A',
templateNamespace: 'html',
scope: false,
controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
controllerAs: 'stringIdentifier',
bindToController: false,
require: 'siblingDirectiveName', 'optionalDirectiveName', '?^optionalParent'],
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
},
};
});
and where you want
$compileProvider.directive('compile', function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
You have to compile the HTML content, i got this using a directive:
.directive('comunBindHtml', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}])
Hope it helps :)
I want to have an attribute directive a bit similar to ng-model. I just want to additionally bind an input fields value to a scope variable (just in one direction input field -> scope variable). So I have just tried this directive but I can not get the directive called anyway.
script:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
passivemodel: '='
},
link: function (scope, element, attrs) {
scope.$watch('passivemodel', function(newPassivemodel, oldPassivemodel) {
console.log("passive model", newPassivemodel);
});
}
};
})
html:
<input data-passivemodel="keyword">
Edit:
Hmmm .. based on vilo20 answer I am running into a very strange behavior.
while this code is working very well:
<input data-ng-model="keyword" data-passivemodel="keyword">
this one does not (note the value of passivemodel):
<input data-ng-model="keyword" data-passivemodel="keyword2">. Sure I have defined the variable in the controller.
Controller:
.controller('SearchController', function($scope, $routeParams, $search) {
$scope.search = new $search();
$scope.keyword = "";
$scope.keyword2 = "";
})
Edit2: here is a fiddle http://jsfiddle.net/HB7LU/12107/
try this:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
passivemodel: '='
},
link: function (scope, element, attrs) {
console.log("passive model", scope.passivemodel);
$scope.$watch('passivemodel', function(newPassivemodel, oldPassivemodel) {
//put your logic when passivemodel changed
});
}
};
})
Hope it helps
Edit: Here is a plunker http://plnkr.co/edit/T039I02ai5rBbiTAHfzv?p=preview
Use the scope attribute:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
"passivemodel": "="
},
link: function (scope, element, attrs) {
console.log("access passivemodel: ", scope.passivemodel);
}
};
})
Finally it was as simple as that:
.directive('modelRed', [function(){
return {
require: 'ngModel',
restrict: 'A',
scope: {},
link: function (scope, element, attrs, ngModel) {
scope.$watch(function () {
return ngModel.$modelValue;
}, function(newValue) {
scope.$parent[attrs.modelRed] = newValue;
//console.log(attrs.modelRed, newValue, scope);
});
}
}
}])
And in the html:
<p><input type="text" data-ng-model="testInput" data-model-red="redInput">{{redInput}}</p>
Of course you have to define testInput and redInput in the $scope object.
I have a directive with a bound doc model from controller. But the directive does not show the document?
HTML:
<div ng-app="myApp">TestApp:
<div ng-controller="TestController as Test">Testdoc controller: {{Test.doc}}
<br>
<test-me ng-init="abc=123" my-model="Test.doc">Testdoc directive: {{myDoc}} | {{abc}}</test-me>
</div>
</div>
JS:
var app = angular.module("myApp", []);
app.controller("TestController", function () {
this.doc = {
name: 'testname',
id: null
};
});
app.directive("testMe", function () {
return {
scope: {
myDoc: '=myModel'
},
link: function(scope, elem, attrs){
}
}
});
output HTML is still:
TestApp:
Testdoc controller: {"name":"testname","id":null}
Testdoc directive: <missing output of doc here>| 123
jsfiddle: http://jsfiddle.net/kx97y93k/14/
What i am doing wrong?
Solution was not to call {{myDoc}} in html directly but inside a template.
app.directive("testMe", function () {
return {
restrict: 'E',
template: '<div>Testdirective: {{myDoc}}<div>',
scope: {
myDoc: '=myModel'
},
link: function(scope, elem, attrs){
}
}
});
See updated jsfiddle: http://jsfiddle.net/kx97y93k/18/
Many thanks to #Mr_Green for that hint
add restrict : 'E' to the DDO, see below. Angular 1.2 uses restrict : 'A' if not explicitly set otherwise. For Angular 1.3 that became 'EA' and your directive would have worked. To make it work in your fiddle, change it as follows:
app.directive("testMe", function () {
return {
restrict : 'E',
scope: {
myDoc: '=myModel'
},
link: function(scope, elem, attrs){
}
}
});
I'm using Scope Isolation in one of my directives. However, this doesn't seem to work:
<div ng-controller="MyCtrl">
Hello, {{name}}!
<dir info='spec'>
{{ data }}
</dir>
</div>
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.spec = 'Super';
}
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
}
}
});
Fiddle: enter link description here
Here is a fiddle: http://jsfiddle.net/WA5t5/
Since this commit(1.2) Child elements that are defined either in the application template or in some other
directives template do not get the isolate scope.
You can do this instead:
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
},
template: "{{ data }}"
}
});
If you want to alter this behavior check my other answer: Why I can't access the right scope?
Try:
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
},
template:"<div>{{data}}</div>"
}
});
DEMO
Another solution using transclusion to bind the scope yourself:
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
},
transclude:true,
compile: function (element, attr, linker) {
return function (scope, element, attr) {
linker(scope, function(clone){
element.append(clone); // add to DOM
});
};
}
}
});
You can still use the same html as before:
<div ng-controller="MyCtrl">
Hello, {{name}}!
<dir info='spec'>
{{data}}
</dir>
</div>
DEMO
You should have a template defined in your directive where you show the data scope variable. The html code does not know what the data scope variable is, it's only known in the directive's template. See this demo
myApp.directive('dir', function () {
return {
restrict: 'AE',
scope: {
data: '=info'
},
link: function (scope, element, attrs) {
console.log(scope.data);
},
template: 'Hello {{data}}'
}
});