Angularjs generic directive and DOM manipulation - angularjs

I have a Directive that I want it to be generic to the application.
And I'd like to know if it is a good practice to do DOM manipulation inside the Controller if not please could you explain where I'am supposed to do it.
Should I create a service like MyDirectiveUIService or something similar to externalize all DOM manipulation..?
Thank you
My app looks like this
In the template I call myDirective and myFunction
<div my-directive my-function="myFunction()"></div>
Then myDirective call myFunction in the link function
myApp.directive( 'myDirective', function() {
function link( scope, element, attributes ) {
scope.myFunction(element)
}
return {
scope:{
myFunction:'&'
},
link:link
}
}
In all the controllers where I use the directive I have the function definition as
$scope.myFunction = function(element){
//DIFFERENT DOM MANIPULATION FOR EACH CONTROLLERS
}

Related

Dynamically update a directive attribute using vanilla JavaScript

I have an Angular JS directive that looks something like this:
function ($sce) {
'use strict';
return {
restrict: 'E',
templateUrl: $sce.trustAsResourceUrl('...'),
scope: {
serviceName: '#?',
},
controller: 'MyController',
link: function () {}
};
}
The directive is instantiated like so:
<my-directive service="My Cool Service"></my-directive>
Recently, some consumers of this directive would like the ability to modify the service attribute after the directive has been instantiated and have the directive reflect the change. Here is an example of what a specific consumer is doing:
const directive = document.querySelector('my-directive');
directive.setAttribute('service', 'Another Service Name');
This makes sense; however, the directive does not reflect the change once they set the attribute. I am figuring out a way to accomplish this. I have tried using scope.$watch and $observe to no avail; example:
link: function (_, _, attrs) {
attrs.$observe("serviceName", newValue => updateServiceName(newValue));
}
Any insights on how to accomplish this? Thanks!
The serviceName property inside your directive's scope definition should be bound with = instead of #. Then, from the outer code, just pass in the service name with a variable.
$scope.myServiceName = "My Cool Service";
<my-directive service="{{myServiceName}}"></my-directive>
Now if you change the myServiceName value, the change will be reflected in the child directive.

Communication between directive function and transcluded controller

I have a directive that "manages" all the instances of the associated component. I would like the directive function to be able to call a function in the transcluded controller, and vice versa. An isolate scope in the link function does not provide this capability. Is this even possible in Angular 1.x? I have everything working, but only by using $broadcast() and hard-coding names into all the child controllers. It feels like a hack.
The directive looks like this:
app.directive('myDirective', function () {
return {
transclude: true,
template: '...<ng-transclude />...',
link: function (scope, elem, attrs) {
...
}
};
});
And the transcluded content looks like this:
<div ng-controller="MyController">
...
</div>
Ideally, I would like to be able to "inject" an object from the directive function into the transcluded controller that contains data and callbacks.

AngularJS: How to create public API on directive to be called from controller?

I have an attribute directive and I'd like to expose an API on that directive to be called on the controller.
I followed the top answer from this question but this no longer seems to work anymore, or at least it doesn't work for me. Since it's been almost three years, how can I do this now?
In my "myDirective" directive I have:
return {
restrict: 'A',
scope: {
api: '='
},
link: function(scope, element){
scope.api = {
someFunction: function(){...}
}
...
}
In my markup I have:
<div myDirective api="b"></div>
And finally in my controller, I try to call:
$scope.b.someFunction() --> undefined is not an object
The way to do so, is to create a service that is used as an API for both the directive and the controller.
Inject the service into your directive controller to use it in the directive, and inject it into the controller.
All the API logic should be in the service, so calling the functions from either the directive or the controller will have the same result.

Angular : check in controller if directives link function is called

So, we have a directive grid, which exposes directive controller to the angular controller, so that controller can call functions on directive (just like a form controller).
Now, in my angular controller every thing works as long as I access the directive controller from a callback action eg. some $scope.xx which are called on some event like click or any thing.
But when i try to access the directive controller at controller initialization time, the directive controller is undefined, that means, directives link function is not called yet.
Here's some code
function controller() {
$scope.init = function() {
$scope.grid.search(xx)
}
$scope.init() // this will fail, because $scope.grid is undefined.
$scope.onClick = function() {
$scope.grid.search(xx) // this will work
}
}
is there any other way, other then watching the $scope.grid, to have the $scope.grid.search called on controller initialization
You can just broadcast event from link function in your directive.
<div ng-controller="MyCtrl">
<div abc></div>
</div>
myApp.directive('abc', function($rootScope) {
return {
restrict: "EA",
link: function(scope, element, attrs){
$rootScope.$broadcast("initialize");
}
}
});
function MyCtrl($scope) {
$scope.$on("initialize", function(){
alert("Link function has been initialized!");
});
}
I've created JSFiddle for you.
The problem is that parent controllers are always called before child controllers.
If you want to run code after your child directive is initialized, you can create a directive and put the initialization code inside of its link function.
This works because parent link functions are always called after child link (and controller) functions.

AngularJS DOM Access On Init

I wrote this simple jsfiddle in which I do (successfully) some basic addClass on a directive in a ng-repeat.
http://jsfiddle.net/rv6u2/5/
Now, my question is: which is the best (or intended) place to do such DOM manipulations:
A. In the directive?
B. In the controller?
Both possibilities are shown in my example.
Code:
var TestApp = angular.module("TestApp", ['ngResource']);
TestApp.directive('onLoad', function() {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
elm.addClass('loaded'); // A: DOM manipulation in directive
scope.initMe(scope.$eval(attrs.onLoad2), elm); // B: DOM manipulation handled in controller
}
};
});
thanks in advance :)
NEVER manipulate the dom inside of controllers.
Controllers should just use services and update attributes of $scope. All DOM manipulation should be made by directives and(in some cases) services(e.g. $anchorScroll)
See the concepts of angularjs here
UPDATE: Example of the correct way here
A more "Angular way" of setting class loaded2 would be as follows (which avoids DOM manipulation inside the controller):
In the HTML, declare a model for the class (myClass):
<div ng-repeat="item in items" ng-model="item" on-load="initMe(item)" ng-class="myClass">
In the link function, just call the controller method:
scope.initMe()
In the controller, manipulate the model/$scope property:
$scope.initMe = function() {
$scope.myClass = "loaded2";
}
Changing the model/scope will automatically update the view.
This method is useful if you want to declare in the HTML that a class is being controlled by $scope property myClass. However, using elm.addClass() inside the linking function is more self-contained and easier to maintain (and I like that approach better).

Resources