What does attrs.$observe do in Angular Directive - angularjs

Simple question, what does attrs.$observe do in Angularjs
Does it observer any changes in the html directive.
I have looked inline but the Angularjs documentation is woful
attrs.$observe('scroller', function() {
$scope.init();
});

If you are not using isolated scope and you want to observe a attribute which has interpolation then you use attrs.$observe. Like
<div my-directive my-attribute="{{i}}">
$attrs.$observe("myAttribute",function(newValue) {
//called when myAttribute value(i) changes
});

Related

Transclusion in Angular UI Modal not working

The objective of this plunk is to transclude elements into an Angular UI Modal from a controller, where the Modal is wrapped by a directive. The solution should follow these premises:
The directive declares the transclusion of fields. These fields are included in the directive declaration in the controller HTML markup.
These fields declared in the controller should show up in the Modal.
The scope of these fields should be accessible in the controller (see that I declared an input1 variable in the controller that should set a value in the Modal).
I defined a content element to transclude the fields. This element is in the modal's template. I'm not sure when this template is available to transclude it.
To summarize, the objective is to have a set of fields declared in the controller HTML markup and available in the modal, where the modal is wrapped in a directive and the scope is managed in the controller. Any ideas will be greatly appreciated.
HTML
<div the-modal control="modalCtl">
<p>some text</p>
<input type="text" ng-model="input1" />
</div>
<button type="button" ng-click="open()">Open me!</button>
Javascript
var app = angular.module("app", ['ui.bootstrap']);
app.controller("ctl", function($scope,$timeout) {
$scope.modalCtl = {};
$scope.input1 = "abc";
$scope.open = function(){
$scope.modalCtl.openModal();
};
});
app.directive("theModal", function($uibModal) {
return {
restrict: "AE",
scope: {
control: "="
},
transclude: true,
link: function (scope, element, attrs, ctrl, transclude) {
scope.control = scope.control || {}
scope.control.openModal = function () {
scope.instance = $uibModal.open({
animation: false,
scope: scope,
template: '<div>in the template</div><div class="content"></div>'
});
element.find('.content').append(transclude());
};
}
}
});
You have come close enough to achieving your objective with transclusion but, there are a few things you need to consider:
First of all, according to UI Bootstrap docs, there is an appendTo property in the options for the $uibModal.open() method which defaults to body.
If appendTo is not specified, the modal will be appended to the body of your page and becomes a direct child of the body. Therefore querying .content in your directive via element.find('.content') won't work because it doesn't exist there.
Secondly, AngularJS comes with jQLite, a lightweight version of jQuery. This implies that there is limited support for most of jQuery's functionalities. One such case is with the .find() method which only works with tag names.
To make it work how it does with jQuery (although you don't really have to because you could still use .children() in a chain to query nested DOM elements), you'll have to load jQuery before Angular (which I suppose you have already).
Refer AngularJS docs on angular.element for more info.
Rendering DOM takes a little bit of time for Angular since it needs to make the correct bindings related to scopes and the views, to complete a digest cycle, and so on.
Therefore you may end up instantly querying a DOM element which in fact might not have been rendered yet.
The trick to wait for DOM rendering and completion of a digest cycle is to wrap your DOM related code into $timeout wrapper.
Taking the above points into account, the openModal method in the link function of your custom directive theModal should look like the following:
scope.control.openModal = function () {
scope.instance = $uibModal.open({
animation: false,
scope: scope,
template: '<div>in the template</div><div class="content"></div>',
/**
* Make sure the modal is appended to your directive and NOT `body`
*/
appendTo: element
});
/**
* Give Angular some time to render your DOM
*/
$timeout(function (){
/**
* In case jQuery is not available
*/
// var content = element.children('.modal').children('.modal-dialog').children('.modal-content').children('.content');
/**
* Since you have jQuery loaded already
*/
var content = element.find('.content');
/**
* Finally, append the transcluded element to the correct position,
* while also making sure that the cloned DOM is bound to the parent scope (i.e. ctl)
*/
transclude(scope.$parent, function(clonedContent){
content.append(clonedContent);
});
});
};
Note how the transclude function gives you control over how you want to bind some transcluded DOM to a custom scope and NOT the default directive's scope. The plain transclude() call will take the current available scope object into account - i.e. the directive's scope - for binding the transcluded DOM.
Demo
As the previous answers suggest, you can use the appendTo property to provide the element of your directive as the parent of the modal.
You can "wait for the modal's template to be rendered" by using the rendered promise in the UibModalIstance. (Documentation).
scope.control.openModal = function () {
scope.instance = $uibModal.open({
animation: false,
scope: scope,
template: '<div>in the template</div><div class="content"></div>',
appendTo: element
});
// We use the redered promise to make sure
// the modal's template has been loaded.
scope.instance.rendered.then(function (){
// You'll most likely want to pass the `$parent` scope as the first
// parameter for proper scope binding with your controller.
element.find('.content').append(transclude(scope.$parent));
});
};
Here's the modified plunker.
transclude: true,
Doesn't work like that, it will insert any markup defined inside the scope of your directive, and will put that markup inside your directive template (where you will put ngTransclude). That's not what you are (seemingly) attempting to do.
What you are looking is to define a template, give it a url and provide it to the modal using the templateUrl property.
HTML
<script type="text/ng-template" id="/some-tpl.html">
<p>some text</p>
<input type="text" value="1234" />
</script>
JS
$uibModal.open({
animation: false,
scope: scope,
templateUrl: "/some-tpl.html" // link to template url
})
Then you can place your directive/your logic inside the controller you provide the modal.
Here is an updated plunk

Override controller functions in directive is a good idea?

I have a generic functionality implemented inside a controller. When i write a directive is it good idea to extend those controller functions inside the directive ?
Like in below implementation inside the link function.
var superCancel = scope.cancel;
// Overriding the cancel function from the controller
scope.cancel = function() {
if(element.hasClass('ng-dirty')){
element.removeClass("ng-dirty");
}
// Calling controller cancel
superCancel();
};
If your directive html is coming inside the controller in html then you can use $parent instead of rewriting
in directive:
$scope.$parent.cancel(); // only if controller coming as parent
If the controller is not coming as parent it's better to use a service or factory to implement that
Read here for more
It is better to have the directive use an attribute to set a parent scope value.
For example:
JS
app.directive("setModelApi", function() {
return {
require: "ngModel",
link: function(scope,elem,attrs, ngModelCtrl) {
scope.$eval(attrs.setModelApi, {$api: ngModelCtrl})
}
}
});
In the above example, the setModelApi directive evaluates the Angular Expression defined by the set-model-api attribute with $api exposing the ngModelController.
HTML
<input ng-model="x" set-model-api="xmodel=$api">
<button ng-click="xmodel.$setPristine()">Set Pristine</button>
The setModelApi directive sets the xmodel scope variable to the ng-model-controller API.
The button invokes the $setPristine method of the ng-model-controller API.
From the Docs:
$setPristine();
Sets the control to its pristine state.
This method can be called to remove the ng-dirty class and set the control to its pristine state (ng-pristine class). A model is considered to be pristine when the control has not been changed from when first compiled.
-- AngularJS ngModelController API Reference -- $setPristine
By using an HTML directive attribute to define the scope variable to which the API attaches, different inputs can use the directive and the connections are exposed in the HTML.
The DEMO on JSFiddle

angularjs controller access directive scope value

I have this controller with directive inside my html code. The directive have scope value I would like access from the parent controller and show in the html. Hope my example code give you a simple overview of what I'm trying to achieve.
I know this work using $rootScope.showvalue = 'pass text'; in the directive works but don't know if this is the best solution.
app.directive('customdir', [function() {
return {
restrict: "E",
scope: {
showvalue: "="
},
link: function(scope, el, attrs) {
scope.showvalue = 'pass text';
}
};
}]);
app.controller('mycontroller', function($scope) {
alert($scope.showvalue);
});
<html>
<div ng-controller="mycontroller">
<custom-dir></custom-dir>
/* Show scope value from custom dir */
{{showvalue}}
</div>
</html>
Since you're binding the directive's isolate scope showvalue property via bidirectional binding, you can simple pass the parent scope property in as an attribute
<customdir showvalue="showvalue"></customdir>
<p>{{showvalue}}</p>
Demo ~ http://plnkr.co/edit/W5k0hgDElklseOvjwRsS?p=preview
If you want the HTML to be <custom-dir>, you'll need to rename your directive to 'customDir'.
You can pass "showvalue" to the directive via attributes.
You can set value through $parent property of directive scope. eg. scope.$parent.showvalue = 'pass text';
You can use emit event to pass data from child (directive) to parent (controller).
You can directly set value to the controller scope using shared scope. (Do not specify scope property in directive)

angularjs-- declenching events

Please, how can I change this code into angularJs
$('a.product_add').on('click', function(event){
event.preventDefault();
var collectionHolder = $('#task_tags');
var prototype = collectionHolder.attr('data-prototype');
form = prototype.replace(/__name__/g, collectionHolder.children().length);
collectionHolder.append(form);
});
First of all you need to show us what you've tried, but I'll write something here to help you
You should make a directive because you're using jquery code. Read more about directives here
AngularJS directives are extended HTML attributes with the prefix ng-.
The ng-app directive initializes an AngularJS application.
The ng-init directive initializes application data.
The ng-model directive binds the value of HTML controls (input,
select, textarea) to application data.
Example of a directive
app.directive('myDirective', function(){
function link($scope,$elem,$attrs){
$elem.on('click', function(event){
// click event code here
});
}
return {
link:link,
scope:{},
restrict:'A'
}
})
Example of usage for myDirective:
<a class='product_add' my-directive>link</a>
We can use angular custom directives.
Now you can access the element in the directive and do the same operations in the directive.
<directive-element ng-click=appendFunction()></directive-element>

css testing in angularjs

I am using controller to change the class of an object in angularjs
$scope.$watch('sideQuery',function(){
if($scope.sideQuery==""){
$(".indicator").removeClass('glyphicon-minus');
$(".indicator").addClass('glyphicon-plus');
}
else{
$(".indicator").removeClass('glyphicon-plus');
$(".indicator").addClass('glyphicon-minus');
}
});
How to test using karma? a function like
expect(scope.elem('.indicator').hasClass("glyphicon-plus")).toBe(true);
Please, do not use jQuery to toggle classes in Angular, it defeats the purpose of it. Use ng-class and apply your classes based on flags, like so:
<div class="indicator" ng-class="{'glyphicon-minus' : sideQuery != '', 'glyphicon-plus':sideQuery == ''} ></div>
Then in testing check the value of sideQuery and know that you'll have classes based off that.
If you are testing a directive, then you can compile a sample element and test the result object.
var $element;
beforeEach(inject(function ($compile) {
$element = $compile('<div data-my-directive></div>')($scope);
}));
it('should have the class "someClass"', function(){
expect($element.hasClass('someClass')).toBe(true);
});
However, be aware that tymeJV is right, you should use the ng-class directive and test your scope's values. If the scope value is right, then the class will be applied (you don't have to test the ng-class directive, that's something done in Angular's unit tests).

Resources