Building an Angular Directive with an ng-if - angularjs

I want to use an Angular Directive for my Forms and based on the Attributes it should use an Input Tag or a Select Tag. I tried to do this with an ng-if:
-> If the select-options Attribute is set use a Select Tag
By using the ng-if in the Template of the Directive the Databinding to the model becomes somehow one-way. (i think the whole approach doing this by ng-if is wrong?)
Here is an Example:
<!DOCTYPE html>
<html>
<head>
</head>
<body ng-app="myApp" ng-controller="AppController">
Parent: <input type="number" ng-model="data.a"/><br>
Data.a: {{data.a}}<br/>
Data.b: {{data.b}}<br/>
InputDirective: <my-directive ng-model="data.a"></my-directive>
SelectDirective: <my-directive ng-model="data.b" select-options="{'aa':'Two As','bb':'Two Bs'}"></my-directive>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.3/angular.min.js"></script>
<script>
var app = angular.module("myApp",[]);
app.directive('myDirective', function()
{
return {
restrict: 'E',
scope: {
value: '=ngModel',
selectOptions: '='
},
template: '<input ng-if="!selectOptions" type="number" ng-model="value"/><select ng-if="selectOptions" ng-model="value" ng-options="k as v for (k,v) in selectOptions"></select>'
};
});
app.controller("AppController", function( $scope )
{
$scope.data = {
a: 54,
b: 'aa'
};
});
</script>
</body>
</html>

I found a solution: in the template change
ng-model="value"
to
ng-model="$parent.value"
ng-if is creating a child scope and it is creating this kind of problem https://github.com/angular/angular.js/wiki/Understanding-Scopes
i still dont know if ng-if is the right aproach to this.

Related

How to control invoking of directive

In this plnkr :
https://plnkr.co/edit/F0XsOPZKq5HArFo9vtFs?p=preview
I'm attempting to prevent a custom directive being invoked by the use of ng-show. But if check console output when the directive is invoked 4 times : console.log('invoked') But ng-show shows/hides html elements it does not control what is rendered within the custom directive itself.
Is there a mechanism to pass the ng-show to the custom directive and if it's false then do call the directive ? I think could pass a new variable to the directive which contains same value as ng-show and then wrap the body of the directive in a conditional ?
src :
goob.html :
goob
http-hello2.html:
2. http-hello2.html
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<label>Filter: <input ng-model="search"></label>
<div ng-show="false">
<div ng-repeat="sourceUrl in sourceUrls | filter:search track by $index ">
<status-viewer url="sourceUrl"> </status-viewer>
</div>
</div>
</div>
</body>
</html>
mytemplate.html :
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http , $interval) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
console.log('invoked')
scope.isFinishedLoading = false;
$http.get(scope.url).success(function (data) {
scope.model = data;
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello2.html'
,'http-hello2.html'
,'test.html'
,'goob.html'];
}
test.html :
test
Instead of ng-show you should use ng-if directive to avoid directive linking before show
Forked plunker example

How to get ng-repeat item inside transcluded template?

How I can use ngRepeat item inside transcluded template? Is it possible?
Directive template:
<ng-transclude ng-repeat="record in records | filter1 | filter2"></ng-transclude>
Directive:
app.directive('myDirective', function () {
return {
templateUrl: '/views/directives/mydirective.html',
restrict: 'A',
transclude: true,
scope: {
records: '='
}
};
});
Controller view:
<div my-directive records="myRecords">
{{ myDirective.record }}
</div>
Doesn't look like it from the way you're doing it.
But you can $compile the template in the directive to achieve this.
http://jsbin.com/mirisixodo/edit?html,js,console,output
(Realizing this is almost certainly too late for you to use...)
Looks like this was discussed in detail in this AngluarJS GitHub issue, and there is, thanks to moneytree-doug, a way to solve your issue without resorting to compile.
The deal seems to be that, at least starting at AngularJS 1.2.18, transcluding creates a child scope of its own, so your iteration variable is no longer accessible.
$parent is accessible, however, and we can use that to access the iteration variable and accomplish what you're looking to do.
Building off of Micah's jsbin...
HTML:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div ng-controller="TestCtrl">
<my-directive records="myRecords">
?: {{$parent.record}}
</my-directive>
</div>
</body>
</html>
JavaScript
var app = angular.module('app', [])
.controller('TestCtrl', function($scope) {
$scope.myRecords = ['foo', 'bar', 'baz'];
});
app.directive('myDirective', function () {
return {
scope: {
records: '='
},
transclude: true,
template: 'HELLO' +
'<div id="inner-transclude"' +
' ng-transclude' +
' ng-repeat="record in records">X</div>',
controller: function ($scope) {
console.log($scope.records);
},
restrict: 'E'
};
});
Result
HELLO
?: foo
?: bar
?: baz
JsBin of reasonable success.
So, again, the crux of this is changing the line if your controller from {{ myDirective.record }} to ?: {{$parent.record}}.

AngularJS: How do I access ng-model values injected from custom directive template?

I ran into a problem with AngularJS concerning directives and ng-model.
Assume the following example:
Within my HTML file:
<div ng-controller="MyCtrl">
<div ng-repeat="item in data">
<directive-item data="item"/>
</div>
<div>
<span>This is some input: {{ myinput }} </span>
</div>
</div>
...
My app.js looks like this (stripped for readability):
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.data = [
{ value: 'something' }
];
}]);
app.directive('directiveItem', function($compile) {
var template = '<div>'
+ '<label for="myinput">{{ data.value }}</label>'
+ '<input type="text" ng-model="myinput" />'
+ '</div>';
var linker = function(scope, element, attrs) {
element.html(template).show();
$compile(element.contents())(scope);
};
return {
restrict: 'E',
link: linker,
scope: {
data: '='
}
};
});
Maybe you can see my problem.
Everything works fine, except the display of {{ myinput }} outside of my directive.
It works perfect, if I display it within the injected template, but not outside of it. I did a LOT of google-research, but didn't find anything to help me out.
To clear some things out in front: $scope.data contains multiple objects with different data sets in my real application. So please look at this only as a quick example.
Also I do inject some more templates from my directive depending on a given $scope.data.object.type. The given code is only a rough example of what I have. As mentioned, everything else works without flaws.
Anyone here got an idea?
Regards!
€dit:
#Zeeshan did come up with a good way. Not yet 100% what I am looking for, but it pushes my thinking in another direction.
If anyone has the perfect solution, I am free for ideas! Thanks!
Angular Best Practice: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app. I have tried a few cases to build understanding, with object (reference | alias behavior), with plain string. Following snippet simulates:
(function(angular) {
'use strict';
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function($scope) {
$scope.data = [{ value: 'something' }];
$scope.bar = {value:'barInitValueAsObject'};
$scope.tar = 'tarInitValueAsNonObject';
}])
.directive('oneItem', function($compile) {
return {
restrict: 'E',
scope: {
foo: '=',
bar:'=',
tar:'=',
},
template: '<div><label for="bar">{{ foo }} : </label> <input type="text" ng-model="bar.value" /></div>'
+ '<div><label for="bar">{{ foo }}</label> : <input type="text" ng-model="tar" /></div>'
}
})
})(window.angular);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-example15-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<div ng-repeat="item in data">
<one-item foo="item.value" bar="bar" tar="tar"></one-item>
</div>
<div>
<br><br>
<span>This is bar # Parent : {{ bar.value }} </span>
<br><br>
<span>This is tar # Parent : {{ tar }} </span>
</div>
</div>
</body>
</html>
Plnkr here
Happy Helping!
You can use another two-way binding in the directive's isolate scope. You already are passing in the data variable, so just add another variable to the scope that will bind with your myInput. Since this is a two-way binding, updating the value in one way will update the value elsewhere too. You'll probably just want to let the directive (and its HTML input) handle the input.
...
return {
restrict: 'E',
link: linker,
scope: {
data: '=',
myInput: '='
}
Finally, your scopes are not lining up properly because of your ng-repeat. It's not clear whether you want your display within the ng-repeat or not, so I just put the display also within the ng-repeat. In your controller's HTML:
<div ng-repeat="item in data">
<directive-item data="item" my-input="myInput"></directive-item>
<span>This is some input: {{ myinput }} </span>
</div>
<div>
</div>
Check out this plunker.

How do I use getterSetter in an angular directive with an isolated scope?

I'm having trouble getting a getterSetter to work in a ng-model directive.
Please, look at this plunker: http://plnkr.co/edit/Tx0nyvvbuKqf1ZpsRTPu?p=preview
If you use the uncommented template, the example behaves as if it didn't understand that getSet() is a function it should call.
If you uncomment the other template (and comment out the first one), the input is connected to the {{ }} as expected.
Why is the getterSetter not working?
Thank you for help!
This is the code:
javascript:
var app = angular.module('app', [])
.controller('ctrl', ['$scope', function($scope) {
$scope.something = "hahaha";
}])
/*
* attributes:
* - value - a variable to store the input value in
*
*/
.directive('inputNumber', ['$log', function( $log ) {
var linker = function( scope, element, attrs) {
$log.info('inputNumber linker called! value = "' + scope.value + '".');
scope.getSet = function( newValue ) {
if( angular.isDefined( newValue ) ) {
scope.value = newValue;
}
return scope.value;
}
}
return {
restrict : 'E',
// template: '<p>Say something! {{ value }}</p><input ng-model="value" ng-model-options="{ getterSetter: false }"></input>',
template: '<p>Say something! {{ getSet() }}</p><input ng-model="getSet" ng-model-options="{ getterSetter: true }"></input>',
link: linker,
scope: {
value: '='
}
};
}])
html:
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
<div ng-app="app" ng-controller="ctrl">
<input-number value="something" ></input-number>
</div>
</body>
</html>
The problem here is ngModelOptions is only available in angular 1.3, but you're using angular 1.2.x. Try to modify your script tag to use 1.3.0-rc.0/angular.min.js and it should work.
Your directive's template should be set to the model (in this case 'value').
template: '<p>Say something! {{ getSet() }}</p><input ng-model="value"
ng-model-options="{ getterSetter: true }"></input>'
Demo Plunker

AngularJS: Access formController of a form placed inside transcluded directive from parent controller

I've created a simple "modal dialog" directive, which uses transclude. I would like to place a form () inside the "modal dialog" directive. I would expect that formController of a form placed inside the directive, is going to be accessible in parent controller's scope, however it isn't. Take a look at the following fiddle, please: http://jsfiddle.net/milmly/f2WMT/1/
Complete code:
<!DOCTYPE html>
<html>
<head>
<title>AngJS test</title>
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/foundation/4.0.9/css/foundation.min.css">
<style>
.reveal-modal {
display: block;
visibility: visible;
}
</style>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('app', []);
app.controller('appCtrl', function ($scope) {
$scope.model = {
id: 1, name: 'John'
};
$scope.modal = {
show: false
};
});
app.directive('modal', function () {
return {
scope: {
show: '='
},
transclude: true,
replace: true,
template: '<div class="reveal-modal small" ng-show="show"><div class="panel" ng-transclude></div></div>'
}
});
</script>
</head>
<body ng-app="app">
<div ng-controller="appCtrl">
<div class="panel">
Id: {{ model.id }}<br>
Name: {{ model.name }}<br>
Controller formController: {{ form }}<br>
Directive formController: {{ myForm }}<br>
</div>
<form name="form" class="panel">
<input type="text" ng-model="model.name">
</form>
<a ng-click="modal.show=!modal.show">toggle dialog</a>
<div modal show="modal.show">
<form name="myForm">
<input type="text" ng-model="model.name">
</form>
</div>
</div>
</body>
</html>
So my question is how to access or is it possible to access directive's formController from parent controller?
Thank you for answers.
-Milan
Because you are using transclude, the directive will create a child transcluded scope. There is no easy path from the controller scope (003) to the directive's transcluded scope (005):
(The hard/not recommended path is to go via private property $$childHead on the controller scope, find the isolate scope, then use $$nextSibling to get to the transcluded scope.)
Update:
From insights from this answer, I think we can get the formController inside the directive, then use = to get it to the parent.
scope: { show: '=', formCtrl: '=' },
...
link: function(scope, element) {
var input1 = element.find('input').eq(0);
scope.formCtrl = input1.controller('form');
}
HTML:
<div modal show="modal.show" form-ctrl="formCtrl">
Fiddle
Here is my solution:
I create such method in parent controller :
$scope.saveForm = function(form) {
$scope.myForm = form;
};
Then I call it in transcluded content:
<my-directive>
<form name="myForm">
<div ng-init="saveForm(myForm)"></div>
</form>
</my-directive>
After creating directive instance i have form controller instance in parent scope.

Resources