AngularJS directive DOM manipulation not executing link() - javascript

Trying to do some DOM manipulation, but link() is never called so nothing happens:
app.js
var app = angular.module('app', ['directives', ...]);
var directives = angular.module('directives', []);
...
directives.js
directives.directive('doIt', ['$window', function($window) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
console.log('Inside link()');
// Do stuff with $window
}
};
]});
HTML
<html ng-app="app">
<body>
<div ng-view>
<div do-it>
// ....
</div>
</div>
</body>
</html>
What am I missing?

Your square bracket is incorrect
directives.directive('doIt', ['$window', function($window) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
console.log('Inside link()');
// Do stuff with $window
}
};
}]);//Check this part. The square bracket was on the wrong side of }
Move it like like I have it above
You also have some extra periods in your module decleration
var app = angular.module('app', ['directives']); //remove the extra ....'s

is this plnkr help?plnkr
angular.module('app', [])
// .controller('Controller', ['$scope', function($scope) {
// $scope.customer = {
// name: 'Naomi',
// address: '1600 Amphitheatre'
// };
// }])
.directive('doIt', ['$window', function($window) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
console.log('Inside link()');
// Do stuff with $window
}
};
}]);

Related

How to attach event to dom created in link function

I know that dom created in template function can be attached to an event in controller.As:
angular.module('app', [])
.directive('appClick', function(){
return {
restrict: 'A',
scope: true,
template: '<button ng-click="click()">Click me</button> Clicked {{clicked}} times',
controller: function($scope, $element){
$scope.clicked = 0;
$scope.click = function(){
$scope.clicked++
}
}
}
});
As there is no scope in template so I have to use link function.
The same how can I achieve in link function. Like:
angular.module('app', [])
.directive('appClick', function(){
return {
restrict: 'A',
scope: true,
link:function(scope, element, attrs){
element.html('<button ng-click="click()">Click me</button> Clicked {{clicked}} times')
},
controller: function($scope, $element){
$scope.clicked = 0;
$scope.click = function(){
$scope.clicked++
}
}
}
});
How to attach event here.
In Angularjs when you want to attach some DOM to the existing DOM. You first have to compile it with the $compile service on the scope for which you want to use the template. This is because template contain Angular js template syntax. Below is the working snippets.
var myApp = angular.module('myApp', []);
myApp.controller('mainController', ['$scope', function($scope){
$scope.message = 'Welcome message';
}])
myApp.directive('myTemplate', ['$compile',function($compile) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attr) {
var el = $compile('<button ng-click="click()">Click me</button> Clicked {{clicked}} times')(scope);
element.append(el);
scope.clicked = 0;
scope.click = function(){
scope.clicked++
}
}
}
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="mainController">
{{message}}
<div my-template></div>
</div>
</div>
angular.module('app', [])
.directive('appClick', function(){
return {
restrict: 'A',
scope: true,
link:function(scope, element, attrs){
element.html('<button ng-click="click()">Click me</button> Clicked {{clicked}} times')
scope.clicked = 0;
scope.click = function(){
scope.clicked++
}
}
});
Achieve this without controller.

ng-bind-html and ng-controller

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 :)

Encapsulated Link and Controller in Directive

I wan't to program a flexible angular directive with it's properties defined in an own, simple object.
Angular:
contentFactory.directive("listViewDir", function ($compile) {
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link:
return $scope.config.link(scope, element, attrs);
}
}
});
Own Configuration Object:
contentFactory.controller("indexCtrl", function ($scope) {
$scope.config = oLiftTabs;})
var configurations = [{
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = "... myTemplate ..";
element.html(template);
$compile(element.contents())(scope);
},
}]
While my solution for the controller works well, it doesn't for the link.
Is there a more proper way for my approach? Can I realize access in my encapsulated method to the services (like $compile) without declaring it in the directive declaration?
Is this what you're trying to achieve? You didn't make it clear where you expect this object to live so I've assumed you want it a parent controller. This doesn't feel like a good idea but without knowing more about your use case it's hard to say.
DEMO
html
<body ng-controller="MainCtrl">
<list-view-dir config="config"></list-view-dir>
</body>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
$scope.config = {
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
},
};
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});
Update
From your comments it sounds like you need to use a factory. Maybe something like this? It feels pretty ugly but it could be what you're looking for.
DEMO2
var app = angular.module('plunker', []);
app.factory('directiveConfigurations', function($compile){
var configurations = {
'listViewDir': {
controller: function ($scope) {
$scope.ButtonClicked = function(){
alert('Button wurde geklickt!');
};
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
}
}
};
return {
get: get
};
////////////////////////
function get(key){
return configurations[key];
}
});
app.controller('MainCtrl', function($scope, directiveConfigurations) {
$scope.config = directiveConfigurations.get('listViewDir');
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});

How do I pass value directly into a directive?

I have a simple angular directive I would like to pass value to.
<div my-component binding-foo="foo">
<strong>get:</strong> {{isolatedBindingFoo}} // get it to output foo?
</div>
HTML
<div my-component binding='foo'> ... </div>
JS
yourApp.controller('yourController', ['$scope', function($scope) {
$scope.isolatedBindingFoo = '';
}])
.directive('myComponent', function() {
return {
controller: 'yourController',
scope: {
'binding': '=binding'
},
link: function($scope, $element, attrs) {
$scope.isolatedBindingFoo = attrs['binding'];
}
}
});
http://fdietz.github.io/recipes-with-angular-js/directives/passing-configuration-params-using-html-attributes.html
Cheers
AngularJS mangles HTML attributes to JS properties. eg, binding-foo in HTML will be mangled to bindingFoo in JS, and vice-versa
var myModule = angular.module('myModule', [])
.controller('yourController', ['$scope', function($scope) {
$scope.isolatedBindingFoo = '';
}])
.directive('myComponent', function() {
return {
restrict:'E,A',
controller: 'yourController',
scope: true,
link: function($scope, $element, attrs) {
$scope.isolatedBindingFoo = attrs['bindingFoo'];
}
}
});
http://jsfiddle.net/b2xo0o5u/3/
But in the example case this should be enough:
angular.module('myModule', [])
.directive('myComponent', function() {
return {
restrict:'EA',
scope: {
'isolatedBindingFoo': '#bindingFoo'
}
}
});
http://jsfiddle.net/b2xo0o5u/4/

Prevent angular directive from compiling more than once

I have a directive that I manually render once:
let html = '<div>'
let scope = $rootScope.$new(true)
scope.foo = 42
let element = $compile(html)($scope)
element.appendTo(container)
After that, I don't want it to ever re-render, even if there is a $digest on the $rootScope. Is that possible with Angular?
If you mean you wish to remove all bindings on your directive, you can call $destroy to remove any bindings on you have. Consider this example, where both directives bind message
<input ng-model="message" id="dirA" dir-a />
<input ng-model="message" id="dirB" dir-b />
<input ng-model="message" />
app.directive('dirA', [function () {
return {
scope: true,
restrict: 'A',
link: function (scope, elem, attrs) {
}
}
}]);
app.directive('dirB', [function () {
return {
scope: true,
restrict: 'A',
link: function (scope, elem, attrs) {
}
}
}]);
app.controller('ctrl', ['$scope', '$timeout', function($scope, $timeout) {
$scope.message = 'hello'
$timeout(function(){
// destroy scope for dirA
angular.element(document.getElementById('dirA')).scope().$destroy()
})
}]);
JSFiddle Link

Categories