How to pass ng-options comprehension_expression to AngularJS component? - angularjs

I am trying to add a new binding to existing AngularJS component that should take in value of type comprehension_expression as explained in the ng-options Directive API Reference.
Please check the code at the bottom to understand the situation. Note that the top <select> control comes through component named selectField. It does not show any select-options. The bottom control is added directly to index.html and works properly.
I would appreciate if someone can tell me if there is a bug in my script, any alternate approaches to pass value to ng-options attribute to the template, or let me know that there is no way for a component or directive to have such bindings.
angular.module('myApp', [])
.controller('MainController', function MainController() {
this.colors = ['red', 'blue', 'green'];
this.myColor = this.colors[1]; // blue
}).component('selectField', {
template: `
<select ng-model="$ctrl.inputModel"
ng-options="{{::$ctrl.inputOptionsExpression}}">
</select>
Selected: {{$ctrl.inputModel}}</span>
`,
bindings: {
inputModel: '=',
inputOptionsExpression: '#'
}
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="MainController as vm">
<div>
<select-field input-model="vm.myColor"
input-options-expression="color for color in vm.colors">
</select-field>
</div>
<div>
<select ng-model="vm.myColor"
ng-options="color for color in vm.colors">
</select>
Selected: {{vm.myColor}}
</div>
</div>
</body>
</html>

See Why mixing interpolation and expressions is bad practice.
In this case the ng-options directive will parse the comprehension expression before the interpolation directive renders the desired expression.
Re-write the component to input the choices:
app.component('selectField', {
require: {ngModelCtrl: 'ngModel'},
bindings: {
ngModel: '<',
choices: '<'
},
template: `
<select ng-model="$ctrl.ngModel"
ng-change="$ctrl.render($ctrl.ngModel)"
̶n̶g̶-̶o̶p̶t̶i̶o̶n̶s̶=̶"̶{̶{̶:̶:̶$̶c̶t̶r̶l̶.̶i̶n̶p̶u̶t̶O̶p̶t̶i̶o̶n̶s̶E̶x̶p̶r̶e̶s̶s̶i̶o̶n̶}̶}̶"̶ ̶
ng-options="c for c in choices">
</select>
Selected: {{$ctrl.ngModel}}</span>
`,
controller: function() {
this.render = (value) => {
this.ngModelCtrl.$setViewValue(value);
};
}
})
Usage:
<select-field ng-model="vm.myColor" choices="vm.colors">
</select-field>
The DEMO
angular.module('myApp', [])
.controller('MainController', function MainController() {
this.colors = ['red', 'blue', 'green'];
this.myColor = this.colors[1]; // blue
})
.component('selectField', {
require: {ngModelCtrl: 'ngModel'},
bindings: {
ngModel: '<',
choices: '<'
},
template: `
<fieldset>Select field
<select ng-model="$ctrl.ngModel"
ng-change="$ctrl.render($ctrl.ngModel)"
ng-options="c for c in $ctrl.choices">
</select>
Selected: {{$ctrl.ngModel}}
</fieldset>
`,
controller: function() {
this.render = (value) => {
this.ngModelCtrl.$setViewValue(value);
};
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="MainController as vm">
<div>
<select-field ng-model="vm.myColor"
choices="vm.colors">
</select-field>
</div>
<div>
<select ng-model="vm.myColor"
ng-options="color for color in vm.colors">
</select>
Selected: {{vm.myColor}}
</div>
</body>

Related

Parent scope not updating from select with ng-change from directive

I've seen a couple of articles on this but still can't figure it out.
I am unable to update the parent scope from the within a directive. I have read the articles saying the scope value should not be primitive and instead it should be an object but still can't figure out why this is not working.
angular.module('moduleMihai').controller('someController',
['$scope', '$http', function ($scope, $http) {
$scope.durations = [{
field: 'yearly',
title: 'Yearly'
}, {
field: 'monthly',
title: 'Monthly'
}, {
field: 'weekly',
title: 'Weekly'
}];
$scope.selectedDuration = $scope.durations[0];
$scope.handleDurationSelection = function () {
console.log($scope.selectedDuration.field + ' selected');
$scope.someData.title[0] = "SOMETHING ELSE!!";
};
$scope.someData= {
title: ['Value1', 'Value2', 'Value3'] };
}]);
the directive doesn't have any stuff in it:
angular.module("awaCommon").directive("durationSelection", [
function () {
return {
scope: {}, // tried removing this as well as seen in some articles
restrict: "E",
templateUrl: "duration-selection.html",
link: function ($scope, $element, $attr) {
}
}
}
]);
below the duration-selection.html which contains the select:
<div ng-controller="someController">
<div>
Child: {{someData.title[0]}}
<select
ng-options="item.title for item in durations"
ng-model="selectedDuration"
ng-change="handleDurationSelection()">
</select>
</div>
So this value above in the Child: {{someData.title[0]}} - gets updated properly when value is selected. But the one in here - Parent: {{someData.title[0]}}, in the main route is not:
<div ng-controller="someController">
<div>
Parent: {{someData.title[0]}}
<duration-selection></duration-selection>
</div>
I need the parent scope to be updated in order to update different directives
The way to interact and update your parent scope from your directive is to use
event handling (emit and broadcast) Todd about events $emit and $broadcast : so here we alert the parent when there is a change from the child directive, then the parent listens for the event. I suggest minimal usage due to some bad sides
directive attribute to pass the function: we pass our function to be processed to our directive to handle or call it when needed from the directive ( for me the best method)
inside the directive to update the $scope.$parent.lngBusinessUnit, no need to pass the function to the directive again, not necessary. since the directive is the one handling the logic. we just directly update the parent straight up.
the use of $watch on the parent directive to help check for changes of the selectedDuration $watch read more: this is quite easy since we map the ngModel to the passed param of our directive using two way binding in our return->scope "=" from directive setup
Example For each of the above possibilities
Event Handling
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.valueToPass = ["Male", "Female"];
//let's catch the updated content
$scope.$on('childUpdated', function(event, value){
$scope.parentValue = value;
console.log("updated from child directive", value);
});
})
.directive("child", function(){
return {
restrict:'E',
scope: {
valueToPass:"="
},
templateUrl:"child.html",
controller: function ($scope){
//this is method is triggered when the select of our valueToPass is changed
$scope.childChanges = function (value){
$scope.$emit('childUpdated', value);
console.log("child emitted this:", value);
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>Event Test Just for your case, advise you read up</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child value-to-pass="valueToPass"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{menu}}<b> <br>
<select ng-model="menu" ng-change="childChanges(menu)">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
directive attribute , using function
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.primaryVariable = "Male";
$scope.onChange = function (){
$scope.parentValue = $scope.primaryVariable;
}
})
.directive("child", function(){
return {
restrict:'E',
scope: {
primaryVariable:"=",
callMe:"&"//note this syntax, check angular directive doc
},
templateUrl:"child.html",
controller: function ($scope){
$scope.valueToPass = ["Male", "Female"];
//this is method is triggered when the select of our primaryVarible is changed
$scope.childChanges = function (){
$scope.callMe();
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>Directive Function Passing</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child primary-variable="primaryVariable" call-me="onChange()"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{primaryVariable}}<b> <br>
<select ng-model="primaryVariable" ng-change="childChanges()">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
using scope.$parent
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.primaryVariable = "Male";
})
.directive("child", function(){
return {
restrict:'E',
scope: {
primaryVariable:"="
},
templateUrl:"child.html",
controller: function ($scope){
$scope.valueToPass = ["Male", "Female"];
//this is method is triggered when the select of our primaryVarible is changed
$scope.childChanges = function (){
$scope.$parent.parentValue = $scope.primaryVariable;
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>Using $parent</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child primary-variable="primaryVariable"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{primaryVariable}}<b> <br>
<select ng-model="primaryVariable" ng-change="childChanges()">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
Using the $watch
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.primaryVariable = "Male";
$scope.$watch('primaryVariable', function(){
$scope.parentValue = $scope.primaryVariable;
});
})
.directive("child", function(){
return {
restrict:'E',
scope: {
primaryVariable:"="
},
templateUrl:"child.html",
controller: function ($scope){
$scope.valueToPass = ["Male", "Female"];
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>using $watch</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child primary-variable="primaryVariable"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{primaryVariable}}<b> <br>
<select ng-model="primaryVariable" ng-change="childChanges()">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
Hope this helps

Can't access options for ng-options inside an ng-repeat

I would like to be able to access a set of select options within an ng-options inside an ng-repeat, but the ng-repeat creates a new scope where I can no longer access this. Is there a way around this without adding it to the repeating data?
<body ng-controller="mainCtrl" class="container">
<test repeater="repeater"></test>
</body>
Template:
<div ng-repeat="repeat in repeater">
<span>{{repeat.type}}</span>
<select ng-options="value for value in options">
<option value="">Choose one:</option>
</select>
</div>
JS:
angular.module('app').controller('mainCtrl', function($scope) {
$scope.repeater = [
{
type: 'best'
},
{
type: 'worst'
},
{
type: 'ok'
}
];
});
angular.module('app').directive('test', function() {
return {
scope: {
repeater: '='
},
templateUrl: 'test.html',
controller: function($scope) {
$scope.options = ['Nope', 'Yup', 'Sure'];
}
};
});
Plunker
You're close, but missing 1 thing. ng-model
<select ng-model="selectedOption" ng-options="value for value in options">
<option value="">Choose one:</option>
</select>

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 should i propertly use angular templates (ng-include)?

I made html template and want to use it with angular via ng-include.
<input class="k-textbox" ng-model="entity.Name">
i want to use it twice, so i added it into page like this
<div ng-include="'/template.html'" ng-init="entity=firstEntity"></div>
<div ng-include="'/template.html'" ng-init="entity=secondEntity"></div>
But as a result i getting 2 templates with 'secondEntity' as data, what is proper usage?
This could be a possible usage:
JS:
app.controller("myCtrl1", function($scope) {
$scope.entity = firstEntity;
}
app.controller("myCtrl2", function($scope) {
$scope.entity = secondEndity;
}
HTML:
<div ng-controller="myCtrl1">
<div ng-include="'/template.html'"></div>
</div>
<div ng-controller="myCtrl2">
<div ng-include="'/template.html'"></div>
</div>
Although, reusable templates that require passing different models or configuration are achieved using directives.
EDIT: Directive approach:
JS:
app.controller("myCtrl1", function($scope) {
$scope.entities = {entity1: {}, entity2: {}};
}
app.directive('myDirective', function() {
return {
scope: {
entity: '='
},
template: '<input class="k-textbox" ng-model="entity.Name">'
}
});
HTML:
<div ng-controller="myCtrl1">
<myDirective entity="entities.entity1"></myDirective>
<myDirective entity="entities.entity2"></myDirective>
</div>

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