Child Directive Model not Updating with Parent Changes Using AJAX call - angularjs

My directive model is not receiving parent model changes when using an AJAX call to populate model. It should be noted child changes are passed to parent. I have seen other questions close to this but none that seemed to successfully this issue of child not receiving parent updates, while being able to update parent.
The "ngModel" is annotated as required in directive, passed in through template and successfully populated with AJAX call. The "ngModel" is given bidirectional access in the directive scope using ngModel : '=' I included parent html, child html, and angular directive + controller used. I have annotated each to explain usage as well as included annotated log.
Thank you in advance for any advice/pointers/help.
define([ 'angular' ], function(angular) {
'use strict';
return angular.module('app', [])
.controller('addDataCtrl', ['$scope', 'Data', 'User', '$location', '$routeParams', function($scope, Dataset, User, $location, $routeParams) {
// log when data.testEnumeratedType is updated
$scope.$watch('data.testEnumeratedType', function() {
console.log("initialEnumeratedType has been updated to : ", $scope.data.testEnumeratedType);
});
// RESTful call for data
if($routeParams.id) {
$scope.data = Data.get({id: $routeParams.id});
}
}]);
.directive('ngEnumTypeSelect', function() {
return {
restrict : 'A',
require : 'ngModel',
scope : {
ngEnumDomain : '#', // bind scope's ngEnumDomain with ngEnumDomain
// value declared in enclosing template.
ngModel : '=',
},
templateUrl : 'selectEnumeratedTypes.html',
link : function(scope, element, attrs, ctrl) {
scope.getEnumTypes(attrs.ngEnumDomain);
// update this child scope model with parent value.
scope.$watch(ctrl, function() {
scope.currentEnumType = ctrl.$viewValue;
console.log(" Directive model changed to : ", scope.currentEnumType);
});
// update parent scope's model with selected value
scope.$watch('currentEnumType', function (value) {
console.log(" Setting parent model value to : ", value);
ctrl.$setViewValue(value);
console.log(" Parent model value is : ", ctrl.$viewValue);
});
},
controller : ['$scope', 'EnumeratedType', '$location', function($scope, EnumeratedType, location) {
$scope.getEnumTypes = function(enumDomain) {
$scope.enumTypes = [];
EnumeratedType.getEnumTypes( { type : enumDomain }, function(enumType) {
var enumeratedValues = enumType.enumeratedValues;
for (var index = 0 ; index < enumeratedValues.length ; index++) {
$scope.enumTypes.push(enumeratedValues[index].value);
}
});
};
$scope.updateCurrentEnum = function(enumType) {
$scope.currentEnumType = enumType;
};
}],
};
});
});
Annotated Log :
initialEnumeratedType has been updated to : undefined
Directive model changed to : undefined directives.js:140
// below AJAX call is made, parent model value is updated to Trials,
// but change is not communicated to child directive.
initialEnumeratedType has been updated to : Trials
// when value is selected, changed is propagated to parent.
Setting parent model value to : None Selected
Parent model value is : None Selected
HTML
<!-- Parent html -->
<!-- The value is populated below - meaning model is populated via RESTful call -->
<div class="form-group">
<label for="imageName" class="col-sm-2 control-label"></label>
<div class="col-sm-4"></div>
<label for="testEnumeratedType" class="col-sm-2 control-label">Type As Stored</label>
<div class="col-sm-4">
<input type="text" class="form-control" id="testEnumeratedType" placeholder="{{dataset.testEnumeratedType}}" ng-model="dataset.testEnumeratedType">
</div>
</div>
<!-- The value is not initially populated below - meaning the parent model is not updating child model -->
<div class="form-group">
<label for="imageName" class="col-sm-2 control-label">Name</label>
<div class="col-sm-4">
<input type="text" class="form-control" id="imageName" placeholder="{{dataset.name}}" ng-model="dataset.name">
</div>
<label for="enumTypeSelect" class="col-sm-2 control-label">Type</label>
<div style="padding-left: 30px" class="col-sm-4" id="enumTypeSelect" data-ng-enum-type-select="{{dataset.testEnumeratedType}}" data-ng-enum-domain="Type" ng-model="dataset.testEnumeratedType"></div>
</div>
<!-- Child html -->
<div class="row">
<!-- Create dropdown with button and list values -
Display value {{currentEnumType}} is not being initially updated-->
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
{{currentEnumType}} <span class="caret"/>
</button>
<ul class="dropdown-menu" role="menu">
<li><a ng-click="updateCurrentEnum(enumType)">{{enumType="None Selected"}}</a></li>
<li class="divider"></li>
<li ng-repeat="enumType in enumTypes">
<a ng-click="updateCurrentEnum(enumType)">{{enumType}}</a>
</li>
</ul>
</div>

Related

Angularjs parsing function to templateUrl directive

Is that possible to parsing function to angularjs directive that return a templateUrl? In my case, I have this directive
.directive('forumForm', function(){
return {
restrict : 'C',
scope : {
data : '=forum',
},
templateUrl : '/templates/forum_form.tpl.html'
}
});
This is my tempalteUrl
<input type="text" ng-model="data.Title" name="nameF" class="form-control" required="" ng-minlength="20" ng-maxlength="100">
<input type="" class="tagsinput" ng-model="data.tagIn" />
<button type="button" ng-click="fn(data)">Submit</button>
And, I call that via class like this
<div class="forumForm" forum="forum"></div>
Last, my controller have a function called fn
$scope.fn = function((){
alert('text')
})
You can see that I parsing a forum variable to my templateUrl via directive. My problem is, Is that possible to parsing a function in that directive? So if I create
<div class="forumForm" forum="forum" fn="action(forum)"></div>
And if I click the button (In my templateUrl), It's call a function that I have written in controller. Is that possible?
Yes, you can use & binding for this:
The & binding allows a directive to trigger evaluation of an
expression in the context of the original scope, at a specific time.
Any legal expression is allowed, including an expression which
contains a function call. Because of this, & bindings are ideal for
binding callback functions to directive behaviors.
Example:
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
var ctrl = this;
ctrl.forum = {}
ctrl.log = log;
function log(data){
console.log(data);
};
}])
.directive('forumForm', [function () {
var forumForm = {
restrict : 'EC',
scope : {
data : '=forum',
fn: '&'
},
templateUrl : 'forum_form.tpl.html'
}
return forumForm;
}]);
<script src="//code.angularjs.org/1.6.2/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl as $ctrl">
<forum-form forum="$ctrl.forum" fn="$ctrl.log(data)"></forum-form>
</div>
<script type="text/ng-template" id="forum_form.tpl.html">
<input type="text" ng-model="data.title" />
<input type="" class="tagsinput" ng-model="data.tagIn" />
<button type="button" ng-click="fn({data: data})">Submit</button>
</script>
</div>

Angular returns [object Object]

my Angular code returns [object Object]. I am calling 2 controllers on different pages. First one sets the data on ng-click and the second one gets (displays) the data. Here is the code:
Angular App code:
var careerApp = angular.module("careerApp", []);
careerApp.factory('myService', function () {
var savedData = {};
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
careerApp.controller("JobList", function ($scope,myService) {
myService.set(data);
});
careerApp.controller("JobSelection", function ($scope, myService) {
$scope.jobname = myService.get();
});
HTML on Page 1
<div class="center-details" ng-controller="JobList">
<div class="details" ng-click="set(data)" >
<h2 class="name" ng-model="jobtitle">
Winter
</h2>
<p><b>Job ID#</b> <span class="jobid">2017-01</span></p>
</div>
</div>
HTML on Page 2
<div ng-controller="JobSelection">
<label ng-bind="jobname"></label>
</div>
You are bringing the whole object in
<label ng-bind="jobname"></label>
If you intented to write the object with a better formatting try changing it to:
<label> {{ jobname | json }}</label>
This way it will be formatted and printed as a json object.
Use Angular expressions intead of ng-bind. Otherwise you will have to specify a specific property of your object.
page 1
<div class="center-details" ng-controller="JobList">
<div class="details" ng-click="set('Winter')" >
<h2 class="name">
Winter
</h2>
<p><b>Job ID#</b> <span class="jobid">2017-01</span></p>
</div>
</div>
Controller:
careerApp.controller("JobList", function ($scope,myService) {
$scope.set= function(data){
myService.set(data);
}
});
page 2
<label ng-bind="jobname"></label>
<label>{{jobname}}</label>
How to make it dynamically, based on the input
<input stype="text" ng-model="jobTitle" ng-change="set()" >
<h2 class="name">
{{jobTitle}}
</h2>
Controller:
careerApp.controller("JobList", function ($scope,myService) {
$scope.jobTitle = "";
//This function will be called every time that jobTitle change its value.
$scope.set= function(){
myService.set($scope.jobTitle);
}
});
Notes:
Take into account that ng-model directive binds an input, select, textarea value to a property on the scope.
Since you have this assignment in your controller definition
$scope.jobname = myService.get();
If you run this controller before the user make a click it will be empty. it wont be refreshed in every click.

Angular JS Directive loaded in expression

I'm new to Angular, and have found a ton of resources about directives and nesting, but can't seem to get this simple example to work. So basically I am working on a tabset, I have an HTML template:
tabset.html
<div>
<ul>
<li ng-repeat="tab in tabset.tabs" ng-class="{active:tabset.current()==$index}">
<a href ng-click="tabset.current($index)">{{tab}}</a>
</li>
</ul>
<div>
<div ng-repeat="pane in tabset.panes">
<div ng-show="tabset.current()==$index">
{{pane.contents}}
</div>
</div>
</div>
</div>
And a search form template:
search-form.html
<div>
<form name="ytSearch" ng-submit="YTCtrl.submit()" novalidate>
<label for="search_box">Search For: </label>
<input id="search_box" ng-model="YTCtrl.searchString"/>
<br>
<label for="location">Location: </label>
<input id="location" ng-model="YTCtrl.location"/>
within
<input type="numeric" value="100" ng-model="YTCtrl.locationRadius" />
<select ng-model="YTCtrl.locationUnit">
<option value="ft">Feet</option>
<option value="m">Meters</option>
<option value="mi">Miles</option>
<option value="km">Kilometers</option>
</select>
<br>
<label for="search_order">Sort By: </label>
<select id="search_order" ng-model="YTCtrl.order">
<option value="relevance">Relevance</option>
<option value="date">Date</option>
<option value="rating">Rating</option>
</select>
<br>
<button id="search">
Search
</button>
</form>
</div>
And a simple app file with 2 directives to handle each of the templates:
app.js
(function() {
angular.module("JHYT", [])
.directive("sidebarTabset", function($compile) {
return {
restrict : 'E',
templateUrl : 'tabset.html',
controller : function($scope, $compile, $http) {
this._current = 0;
this.current = function(i) {
if (i != null)
this._current = i;
return this._current;
};
this.tabs = ['Search', 'Favorite'];
this.panes = [{
contents : "<search-form></search-form>"
}, {
contents : "Favorite Pane"
}];
},
controllerAs : 'tabset',
};
}).
directive("searchForm", function() {
return {
restrict : 'E',
templateUrl : 'search-form.html',
controller : function($scope, $compile, $http) {
this.searchString = '';
this.location = '';
this.locationRadius = '';
this.locationUnit = 'mi';
this.order = 'relevance';
this.submit = function() {
console.log("Submit");
};
},
controllerAs : 'YTCtrl',
}
});
})();
So as you can probably tell, the idea is to be able to send a JSON object into the tabset (through a service probably) and have it build out a dynamic tabset, that actually works exactly as I expected it to. What isn't working is that in the first tab, the content, which is <search-form></search-form> is not processed, and the tag is rendered as plain text in the content area.
Since this is a tabset, the "child" doesn't need anything from the "parent", the search form and the tab itself have no scope dependencies. I tried playing with the link and compile functions after seeing some examples of nested structures, but can't seem tog et them to work.
How can I process the content of that variable so that element directives are rendered using their templates?
EDIT:
#sielakos Gave me exactly what I was hoping for, a reusable method for doing this.
I added a directive to my module called compile, which adds a wrapper to allow me to use plain text:
.directive("compile", function($compile){
return {
restrict: 'A',
link: function(scope, element, attr){
attr.$observe("compile", function(str){
var compiled = $compile("<div>"+str+"</div>")(scope);
jQuery(element).replaceWith(compiled);
})
}
}
})
And I changed my tabset to use this directive:
<div>
<ul>
<li ng-repeat="tab in tabset.tabs" ng-class="{active:tabset.current()==$index}">
<a href ng-click="tabset.current($index)">{{tab}}</a>
</li>
</ul>
<div>
<div ng-repeat="pane in tabset.panes">
<div ng-show="tabset.current()==$index">
<div compile="{{pane.contents}}"></div>
</div>
</div>
</div>
</div>
You will need to compile your string using $compile service if you wish to use it as you would use template. Otherwise it will be treated as normal string and displayed as it is.
Here is example how to use it inside directive:
var compiled = $compile(str)(scope);
element.empty();
element.append(compiled);
If you wish you can look at this fiddle for more complex example:
https://jsfiddle.net/x78uuwp2/
Here I created simple compile directive that takes string compiles it and puts as element body with current scope.

Use an Angular directive to generate html from an array

I'm trying to use an Angular directive to create a form where the user can specify the number of children and, for each child, an edit box appears allowing the childs date of birth to be entered.
Here's my HTML:
<div ng-app>
<div ng-controller="KidCtrl">
<form>
How many children:<input ng-model="numChildren" ng-change="onChange()"/><br/>
<ul>
<li ng-repeat="child in children">
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Here's the JS:
var app=angular.module('myApp', []);
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{dob: "1/1/90"}, {dob: "1/1/95"}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function() {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
};
});
And here's a jsFiddle
The problem is that it's just not working.
If I enter 1 in the numChildren field then it shows 1 bullet point for the list element but it doesn't show any of the HTML.
If I enter 2 in the numChildren field then it doesn't show any list elements.
Can anyone explain what I'm doing wrong?
Many thanks ...
Your main issue is that the directive childDOB is never rendered. Even though your controller works because 1.2.x version of angular has global controller discover on. It will look for any public constructors in the global scope to match the controller name in the ng-controller directive. It does not happen for directive. So the absence of ng-app="appname" there is no way the directive gets rendered. So add the appname ng-app="myApp" and see it working. It is also a good practice not to pollute global scope and register controller properly with controller() constructor. (Global look up has anyways been deprecated as of 1.3.x and can only be turned off at global level.)
You would also need to add track by in ng-repeat due to the repeater that can occur due to increasing the length of the array based on textbox value. It can result in multiple array values to be undefined resulting in duplicate. SO:-
ng-repeat="child in children track by $index"
Fiddle
Html
<div ng-app="myApp">
<div ng-controller="KidCtrl">
<form>How many children:
<input ng-model="numChildren" ng-change="onChange()" />
<br/>
<ul>
<li ng-repeat="child in children track by $index">{{$index}}
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Script
(function () {
var app = angular.module('myApp', []);
app.controller('KidCtrl', KidCtrl);
KidCtrl.$inject = ['$scope'];
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{
dob: "1/1/1990"
}, {
dob: "1/1/1995"
}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function () {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
}
});
})();

Why does this state get reset?

In the following code, why $scope.text get reset when a new area is switched in? I think its value should be persisted because its defined in the top level scope.
<div ng-controller="Ctrl">
<select ng-model="selection" ng-options="item for item in items">
</select>
<hr/>
<div ng-switch on="selection" >
<div ng-switch-when="settings" ng-controller="Ctrl1">
Enter val :
<input ng-model="text" />{{text}}
</div>
<span ng-switch-when="home" ng-controller="Ctrl2">Home Span</span>
<span ng-switch-default>default</span>
</div>
</div>
Controllers:
var myApp = angular.module('myApp',[]);
function Ctrl($scope) {
$scope.items = ['settings', 'home', 'other'];
$scope.selection = $scope.items[0];
$scope.text = "Enter val";
}
function Ctrl1($scope) {
console.log('hi')
}
function Ctrl2($scope) {
console.log('hi2')
}
http://jsfiddle.net/KDWh8/
When you are working with primitive values in angular scopes, you cannot overwrite a value in a parent scope from a child scope. This is because angular uses javascript prototypal inheritance.
What you could do in this case is create an object in the parent scope, then you can update the values on that in the child scope. Because you are not overwriting the object (only properties attached to it) the references work.
"the rule of thumb is, if you use ng-model there has to be a dot somewhere." Miško Hevery

Resources