I have a main form with the following markup
<tabset vertical="true" type="pills">
<tab ng-repeat="tab in tabsViews" select="selectView(tab.name, tab.index)"
ng-show="tab.isVisible"
class=" {{tabsViews[tab.index-1]['invalid'] ? 'invalid-tab': 'valid-tab' }}">
<tab-heading>{{tab.title}}</tab-heading>
</tab>
</tabset>
and the selectView method in my controller is the following:
$scope.previousIndex = null;
$scope.selectView = function (viewName, viewIndex) {
$scope.selections.showInvoicesDetailView = false;
$scope.selections.showInvoicesView = false;
$scope.selections.showPassesView = false;
if (viewIndex) {
if ($scope.previousIndex != null && $scope.form) {
$scope.tabsViews[$scope.previousIndex - 1]["invalid"] = $scope.form.$invalid;
}
$scope.previousIndex = viewIndex;
}
if (viewName.substring(0, 9) != 'invoices.')
$scope.selections.lastSelectedView = viewName;
else
$scope.selections.showInvoicesDetailView = true;
if (viewName == 'guestPasses')
$scope.selections.showPassesView = true;
if (viewName == 'invoices')
$scope.selections.showInvoicesView = true;
if ($scope.selections.isNew) {
window.console && console.log('SelectView called with the new.' + viewName + ' view...');
$state.go('new.' + viewName);
}
else {
window.console && console.log('SelectView called with the edit.' + viewName + ' view...');
$state.go('edit.' + viewName);
}
};
The main form has a directive to detect its dirty state and ask for saving the changes. The problem is that when I change anything in my current view, that form's dirty state is not propagated into that main form. Is there a way to set main form dirty state based on the particular tab (defined as the view) dirty state?
To understand the problem better, here is the main form markup (the one that has tabs):
div ng-class="{'col-md-7': $parent.showSearch, 'col-md-11': !$parent.showSearch}">
#Html.Partial("_EditFormHeader")
<div class="fourpanel">
<div data-sm:collapse="$parent.showForm" id="hideFrm" class="pull-left col-sm-3 sm-search-list">
<form name="guestMainForm" novalidate role="form" data-sm:dirty-check data-server:error
ng-show="$parent.showForm && !selections.justDeleted" class="ng-cloak">
<div id="guestEditForm" class="widget" data-resize:container>
<div class="widget-head">
<div class="row">
<div class="clearfix"></div>
#Labels.guest: {{currentGuest.contactPerson.firstName + ' ' + currentGuest.contactPerson.lastName}} {{ !isNew ? '(' + currentGuest.guestNo + ')' : '' }}
<div class="pull-right text-right col-lg-1" style="padding-right:5px">
<i class="fa fa-angle-double-left sm-slider-button" ng-click="toggleFormVisibility()"
alt="#String.Format(Labels.hideX, Labels.account)" id="angle-left"></i>
</div>
</div>
</div>
<div class="widget-content">
<div class="scrollable widget-resize">
<div class="padd">
#Html.Partial("_EditFormAlerts")
</div>
<div class="col-lg-2 col-md-2 panel-container">
<tabset vertical="true" type="pills">
<tab ng-repeat="tab in tabsViews" select="selectView(tab.name, tab.index)"
ng-show="tab.isVisible"
class=" {{tabsViews[tab.index-1]['invalid'] ? 'invalid-tab': 'valid-tab' }}">
<tab-heading>{{tab.title}}</tab-heading>
</tab>
</tabset>
</div>
<div class="col-lg-8 col-md-4 panel-container">
<div data-ui-view data-autoscroll="false"></div>
<div data-ui-view="guestPasses" ng-show="selections.showPassesView"></div>
<div data-ui-view="invoices" data-autoscroll="false" ng-show="selections.showInvoicesView"></div>
</div>
</div>
</div>
<div class="widget-foot">
<div ng-show="!isNew">
<button class="btn btn-primary" ng-click="save(currentGuest)"
ng-disabled="form.$invalid || disableAction">
#Labels.save
</button>
<data-delete:button title="{{ '#Labels.delete: ' + currentGuest.contactPerson.firstName.trim() + ' ' + currentGuest.contactPerson.lastName.trim() + ' (' + currentGuest.guestNo +')' }}"
message="#String.Format(Messages.confirmDelete, Labels.guest)"
disable-action="disableAction"
delete="delete()">
</data-delete:button>
<data-cancel:button title="#Labels.unsavedChanges"
message="#Messages.unsavedChanges"
cancel="cancel()"
disable-action="disableAction"
dirty="form.$dirty">
</data-cancel:button>
</div>
<div ng-show="isNew">
<button id="btnAdd" class="btn btn-primary" ng-click="new(currentGuest)"
ng-disabled="form.$invalid || disableAction">
#Labels.add
</button>
<data-cancel:button title="#Labels.unsavedChanges"
message="#Messages.unsavedChanges"
cancel="cancel()"
disable-action="disableAction"
dirty="form.$dirty">
</data-cancel:button>
</div>
</div>
</div>
</form>
</div>
<div id="showFrm" class="sm-form-expand-button text-center col-sm-1"
ng-show="!$parent.showForm"
ng-click="toggleFormVisibility()">
<i class="fa fa-angle-double-right"></i>
<div class="panel2Label">#Labels.guest: {{ currentGuest.contact.firstName.trim() + ' ' + currentGuest.contact.lastName.trim() }} {{ !isNew ? '(' + currentGuest.guestNo + ')' : '' }}</div>
</div>
<div class="col-sm-5 panel-container">
<div data-ui-view="detail" data-autoscroll="true" ng-show="selections.showInvoicesDetailView"></div>
<div data-ui-view="passDetail"></div>
</div>
</div>
</div>
I created a new directive and added it to my view forms:
function ($modal, $rootScope, $location, $state) {
return {
restrict: 'A',
require: ['^form'],
//scope: {
// onOk: '&',
// onCancel: '&'
//},
link: function (scope, element, attrs, controllers) {
var form = controllers[0];
window.console && console.log(form);
window.onbeforeunload = function () {
if ((form && form.$dirty) || element.hasClass('ng-dirty')) {
// return resourceFactory.getResource('Messages', 'unsavedChanges');
if (scope.form)
{
scope.form.$setDirty();
}
}
};
}
};
I am debugging and I can see that form is correctly set to my view's form and I can access the parent form using form.$$parentForm property. However, I don't know to which event should I hook to set form.$$parentForm.$setDirty when my form becomes dirty. If you can help me figure this out, then it will work for me, I guess.
Quote from Angular documentation on the form directive:
In Angular, forms can be nested. This means that the outer form is valid when all of the child forms are valid as well. However, browsers do not allow nesting of elements, so Angular provides the ngForm directive which behaves identically to but can be nested. This allows you to have nested forms, which is very useful when using Angular validation directives in forms that are dynamically generated using the ngRepeat directive. Since you cannot dynamically generate the name attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an ngForm directive and nest these in an outer form element.
Maybe this also works for the $dirty state so that if a child form is $dirty the parent form will also be $dirty. I'm not sure that in your case you'll be able to nest the forms. I don't have enough context to visualise what you want to achieve.
Alternatively, you can manually set the main form to dirty when one of those other forms becomes dirty. Because you added the code from your main form, I can see you're not using the built in dirty checker from angular. Maybe you have a good reason for this, but perhaps you didn't know of it's existence. You'll have to use the angular form directive then. The FormController has the following method: $setDirty();.
FormController documentation
Form Directive documentation
Related
I have a list of custom directives which are different widgets on my dashboard. The directives are defined as follows:
angular.module('core').directive('graphCardWidget', function() {
return {
restrict: 'E',
replace: true,
scope: {
target: '=target'
},
templateUrl: 'modules/core/widgets/graph-card.client.widget.html'
};
});
angular.module('core').directive('pieChartWidget', function() {
return {
restrict: 'E',
replace: true,
scope: {
target: '=target'
},
templateUrl: 'modules/core/widgets/pie-chart.client.widget.html'
};
});
In my controller, I have a list of widgets to be displayed. The list is as follows:
$scope.dashboardWidgets = [
{
directive : 'graph-card-widget',
target : 'widgets.dashboards.activeDevicesCard'
},
{
directive : 'graph-card-widget',
target : 'widgets.dashboards.activeSessionsCard'
},
{
directive : 'pie-chart-widget',
target : 'widgets.dashboards.devices'
},
{
directive : 'pie-chart-widget',
target : 'widgets.dashboards.sessions'
}
];
Now in my view, I use ng-repeat to iterate this array and display the items. Here is the code of my view:
<div layout="row" layout-wrap layout-align="center" layout-xs="column" ng-drop="true">
<div ng-repeat='widget in dashboardWidgets'>
<{{widget.directive}} ng-drag="true" flex='45' target='{{widget.target}}'>
</{{widget.directive}}>
<span flex='5'></span>
</div>
</div>
But the browser renders this as text. Here is what I get in my DOM:
<div layout="row" layout-wrap="" layout-align="center" layout-xs="column" ng-drop="true" class="layout-wrap layout-xs-column layout-align-center-stretch layout-row">
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<graph-card-widget ng-drag="true" flex='45' target='widgets.dashboards.activeDevicesCard’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<graph-card-widget ng-drag="true" flex='45' target='widgets.dashboards.activeSessionsCard’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<pie-chart-widget ng-drag="true" flex='45' target='widgets.dashboards.devices’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<pie-chart-widget ng-drag="true" flex='45' target='widgets.dashboards.sessions’>”
<span flex="5" class="flex-5"></span>
</div>
</div>
So what can I do to make the directive rendered as a tag and not plain text?
I saw a couple of questions similar to these such as, question 1, question 2 but they all are adding the dynamic directives in standard HTML tags. What I need is a dynamic tag.
Update:
After following #cnexans answer, I got it working partially. The widget is drawn but the target attribute is not evaluated which leads to a blank widget.
Here's the plunkr with the issue: https://plnkr.co/edit/BGN6C4LAHguWthU4fGy0?p=preview
You can use ng-if inside ng-for in order to switch the name "directive" within each iteration to show one or another directive.
<div ng-repeat="data in dashboardWidgets">
<div ng-if="data.directive == 'graph-card-widget'">
<graph-card-widget ng-drag="true" flex='45' target=data.target>
</graph-card-widget>
<span flex='5'></span>
</div>
<div ng-if="data.directive == 'pie-chart-widget'">
<pie-chart-widget ng-drag="true" flex='45' target=data.target>
</pie-chart-widget>
<span flex='5'></span>
</div>
</div>
You can create a container directive to take care of this logic too, so you can share this functionality to other pages if needed.
<widgets-container widgets-list=data></widgets-container>
Working example: https://codepen.io/anon/pen/jBzEpe?editors=1010#0
Edit:
Checking the example you gave, you need to pass a Widget object to the directive, and you are passing a string. Here is a working example forked from the plunkr given
https://plnkr.co/edit/3Oxxmp?p=preview
It has a function namespaceToObject which transform the string into the desired object from $scope.
don't know whether this will match your requirement. but i did some modification to your array.
I did this using ng-bind-html and when your are binding custom element like directive you need to compile it again. for that i create(actually borrow) this directive.
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I changed the ng-repeat like this
<div ng-repeat='widget in dashboardWidgets' compile-template ng-bind-html="trust(widget.directive)">
<span flex='5'></span>
</div>
trust function will return trusted html
$scope.trust = function(someHTML){
return $sce.trustAsHtml(someHTML);
}
modify the array like this
$scope.dashboardWidgets = [
{
directive : '<graph-card-widget ng-drag="true" flex="45" target="widget.target"></ graph-card-widget>',
target : 'widgets.dashboards.activeDevicesCard'
},
{
directive : '<graph-card-widget ng-drag="true" flex="45" target="widget.target"></ graph-card-widget>',
target : 'widgets.dashboards.activeSessionsCard'
},
{
directive : '<pie-chart-widget ng-drag="true" flex="45" target="widget.target"></ pie-chart-widget>',
target : 'widgets.dashboards.devices'
},
{
directive : '<pie-chart-widget ng-drag="true" flex="45" target="widget.target"></ pie-chart-widget>',
target : 'widgets.dashboards.sessions'
}
];
Demo
I'm using AngularJS + ui-router to make some wizard with nested forms and routes.
here is the main form:
<div id="form-container-wizard">
<div class="form-horizontal" role="form">
<form name="addItem_form" ng-submit="submitForm()">
<div class="page-header text-center">
<h2>Post Your Item</h2>
<!-- the links to our nested states using relative paths -->
<!-- add the active class if the state matches our ui-sref -->
<div id="status-buttons-wizard" class="text-center">
<a ng-class="{ disabled: ItemCheckPass }" ui-sref-active="active" ui-sref=".item"> <span></span>Item</a>
<a ng-class="{ disabled: !ItemCheckPass || LocationCheckPass}" ui-sref-active="active" ui-sref=".location"><span></span>Location</a>
<a ng-class="{ disabled: !ItemCheckPass || !LocationCheckPass || AccountCheckPass}"ng-show="!IsAuthenticated" ui-sref-active="active" ui-sref=".account"><span></span>Account</a>
<a ng-class="{ disabled: !ItemCheckPass || !LocationCheckPass || !IsAuthenticated && !AccountCheckPass }"ui-sref-active="active" ui-sref=".social"><span></span>Social</a>
</div>
</div>
<div class="panel panel-default">
<div id="form-views" ui-view></div>
</div>
</form>
</div>
</div> <!-- wizard container -->
here is the routing for the form:
.state('post_add', {
url: '/post_add',
templateUrl: '/view/post_wizard/form.html',
controller: 'postWizardMainController',
abstract:true
})
.state('post_add.item', {
url: '',
templateUrl: '/view/post_wizard/form-item.html',
controller: 'postWizardController'
})
.state('post_add.location', {
url: '/location',
templateUrl: '/view/post_wizard/form-location.html',
controller: 'postWizardController'
})
.state('post_add.account', {
url: '/account',
templateUrl: '/view/post_wizard/form-account.html',
controller: 'postWizardController'
})
.state('post_add.social', {
url: '/social',
templateUrl: '/view/post_wizard/form-social.html',
controller: 'postWizardController'
});
each view contains a partial form and store the form elements values to $scope.AddItem object by using ng-models like <input type="email" class="form-control" name="email" ng-model="AddItem.email" ng-minlength=3 ng-maxlength=30 required>
postWizardMainController used to keep the validation variables and methods.
So, the problem is:
option 1: the code is as listed here, <form name="addItem_form" ng-submit="submitForm()"> is not getting submitted by <input type="submit" ng-disabled="addItem_form.$invalid" class="btn btn-orange" value="Post An Ad"> located in the last of form views.
option 2: i put the submitForm() into the ng-click in the last form, and locate submitForm() function in the postWizardMainController. In this option, the submitForm() function is called, bit no objects are passed into it. $scope.AddItem is undefined.
So, the question is:
How can i submit the form and pass $scope.AddItem object into submission, which must contain the data from all the nested forms.
The solution was to declare $scope.AddItem = {} in postWizardMainController
I have an Angular app that takes a large complex data set, and allows the user to filter it down using select lists. When data is filtered, a Google Map object is updated to display associated locations with the items that have been filtered.
I am trying to work it so that when the user clicks on a map pin, the filtered list of data is updated with only the items that are associated with that location.
Some snippets of the code:
$scope.showInfoWindow = function(event, site) {
var infowindow = new google.maps.InfoWindow();
var center = new google.maps.LatLng(site.Latitude, site.Longitude);
infowindow.setContent(
'<h3>' + site.SiteName + '</h3>' + '<p>' + site.Address1 + '<br/>' + site.Address2 + '<br/>' + site.City + ', ' + site.State + ' ' + site.ZipCode + '</p>');
infowindow.setPosition(center);
infowindow.open($scope.map);
$scope.findSelectedLocations(site.SiteName);
};
$scope.findSelectedLocations = function(SiteName) {
$scope.filteredData.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.SiteName == SiteName) {
console.log('match!');
console.log('itemIndex: ' + itemIndex);
console.table(itemElement);
$scope.$apply(function() {
$scope.filteredData == itemElement;
});
return false;
}
});
});
});
<div class="trials-item-outer-wrapper">
<div class="trials-item-wrapper" du-scroll-container>
<!-- start of row -->
<div class="trials-item {{class}}" ng-repeat="data in (filteredData = (dataObject | byCountry : selectRegion | byRegion : selectState | byCity : selectCity | filterBy:['Phase']: selectPhase | filterBy:['Compound']: selectCompound | filterBy:['TherapeuticArea', 'TherapeuticArea_2',]: selectTherapy : 'strict' | unique: 'Id' | orderBy:'Phase' : reverse)) track by $index "
ng-class="{'open':$index == selectedRow}" id="anchor-{{$index}}">
<div class="trials-item-cell-wrapper">
<div class="trials-item-cell">
<img ng-click="toggleOpen($index, data.Compound,data.Number)" ng-src="assets/img/phase{{ data.Phase}}.png" width="54" height="61" alt="Phase {{ data.Phase}}" class="trials-item-phase-icon">
</div>
<div class="trials-item-cell"><a ng-click="toggleOpen($index, data.Compound,data.Number)"><span class="compound">{{ data.Compound }}</span><br/>{{ data.Compound_2 }}</a>
</div>
<div class="trials-item-cell">
<a href="" ng-click="toggleOpen($index, data.Compound,data.Number)">
<p class="trial-title">{{ data.TitleShort }}</p>
<p>{{ data.Number }} {{ data.Status }}</p>
</a>
</div>
<div class="trials-item-cell">
<a href="" ng-click="toggleOpen($index, data.Compound,data.Number)">
{{ data.TherapeuticArea }}<span ng-if="data.TherapeuticArea_2">, {{ data.TherapeuticArea_2 }}</span>
</a>
</div>
<div class="trials-item-cell location-cell">
<span ng-if="data.Disclaimer">
<p>{{data.Disclaimer}}</p></span>
</div>
<div class="trials-item-cell cart-actions">
<div ng-if="!isInCart(data.Id)" class="add-to-cart">
<a href="" ng-click="addToCartModalOpen($index, data, data.Id)">
<img src="assets/img/cart-add.png" width="73" height="65" alt="Cart Add">
<span>Add to Cart</span>
</a>
</div>
<div ng-if="isInCart(data.Id)" class="remove-from-cart">
<a href="" ng-click="removeFromCart(data.Id)">
<img src="assets/img/cart-added.png" width="73" height="65" alt="Cart Add">
<span>Remove from Cart</span>
</a>
</div>
</div>
<div class="trials-item-cell">
<a ng-click="toggleOpen($index, data.Compound,data.Number)">
<p class="more-text"></p>
<div class="more glyphicon"></div>
</a>
</div>
</div>
<!-- trials-item-cell-wrapper -->
<div class="trials-tabs" ng-include="data.Url"></div>
</div>
<!-- end of ng-repeat -->
So the view uses the $scope.filteredData obj to display the list of filtered data. In $scope.showInfoWindow, I'm displaying an infowindow for a map pin that the user clicks on. I then pass the 'site.SiteName' value to the $scope.findSelectedLocations method to loop thru the $scope.filteredData object to find any matches to that location. This part works fine. I can console.table the data and see that itemElement is indeed showing the correct data when the map pin is click (via $scope.showInfoWindow() ).
When I'm having the issue is getting that updated data back in to the view.
In $scope.findSelectedLocations, I'm updating $scope.filteredData with the updated items in itemElement, but the view that displays $scope.filteredData isn't updating. As you can see below, I've tried wrapping that assignment in a $scope.$apply, but that doesn't work either.
No need for apply(), that's needed when something out of Angular's context needs to be rendered explicitly in a digest cycle.
May be there's a typo:
Instead of:
$scope.filteredData == itemElement;
Use:
$scope.filteredData = itemElement;
And try now.
Angular has two-way data binding. So whenever your model updates, views will automatically be get rendered. Nothing needs to be done explicitly. We have to have a watcher if we want to update the view/logic based on some change detection.
I have encountered an error which I'm unable to debug.
form-field.html
<div class='row form-group' ng-form="{{field}}" ng-class="{ 'has-error': {{field}}.$dirty && {{field}}.$invalid }">
<label class='col-sm-2 control-label'> {{ field | labelCase }} <span ng-if='required'>*</span></label>
<div class='col-sm-6' ng-switch='required'>
<input ng-switch-when='true' ng-model='record[field][0]' type='{{record[field][1]}}' class='form-control' required ng-change='update()' ng-blur='blurUpdate()' />
<div class='input-group' ng-switch-default>
<input ng-model='record[field][0]' type='{{record[field][1]}}' class='form-control' ng-change='update()' ng-blur='blurUpdate()' />
<span class='input-group-btn'>
<button class='btn btn-default' ng-click='remove(field)'><span class='glyphicon glyphicon-remove-circle'></span></button>
</span>
</div>
</div>
<div class='col-sm-4 has-error' ng-show='{{field}}.$dirty && {{field}}.$invalid' ng-messages='{{field}}.$error'>
<p class='control-label' ng-message='required'> {{ field | labelCase }} is required. </p>
<p class='control-label' ng-repeat='(k, v) in types' ng-message='{{k}}'> {{ field | labelCase }} {{v[1]}}</p>
</div>
</div>
new.html
<h2> New Contact </h2>
<form name='newContact' novalidate class='form-horizontal'>
<form-field record='contact' field='firstName' live='false' required='true'></form-field>
<div class='row form-group'>
<div class='col-sm-offset-2'>
<button class='btn btn-primary' ng-click='save()'> Create Contact </button>
</div>
</div>
</form>
I'm getting the following error:
In the browser:
Error: [$parse:syntax]
http://errors.angularjs.org/1.4.1/$parse/syntax?p0=%7B&p1=invalid%20key&p2=2&p3=%7B%7Bfield%7D%7D.%24error&p4=%7Bfield%7D%7D.%24error
On angular site:
Error: $parse:syntax Syntax Error Syntax Error: Token '{' invalid key
at column 2 of the expression [{{field}}.$error] starting at
[{field}}.$error].
Does someone know why? Thanks!
I notice that this error also happens when binding data to an attribute on a custom directive. Where
$scope.myData.value = "Hello!";
This causes the error:
<my-custom-directive my-attr="{{myData.value}}"></my-custom-directive>
But this works fine:
<my-custom-directive my-attr="myData.value"></my-custom-directive>
Your problem is here:
ng-class="{ 'has-error': {{field}}.$dirty && {{field}}.$invalid }"
Remove {{ }}:
ng-class="{ 'has-error': field.$dirty && field.$invalid }"
Also you have the same issue here:
ng-messages='{{field}}.$error'
Replace with:
ng-messages='field.$error'
However fixing those will most likely also cause an error for this line:
ng-message='{{k}}'
So you have to change it to:
ng-message='k'
This problem happened to me when i was following the same tutorial , and i discovered that the solution in my case is that i was using a newer version of ngMessages so i have to update my bower.json file with the same version in the videos (starting from version 1.4 the example doesn't work), then every thing works fine and here is my dependencies section:
"dependencies": {
"angular": "1.3.9",
"angular-route": "1.3.9",
"angular-resource": "1.3.9",
"angular-messages": "1.3.9",
"bootstrap": "^3.3.6"}
Lets supppose this is my html
<div ng-controller='MyCtrl' ng-init="array=[{id:1}, {id:2}]">Hi, it's {{name}}.
<div ng-repeat='obj in array'>
The current time is <display-time data="{{array}}"/>.
</div>
</div>
Here display-time is the custom directive, whose definition is as follows
var demo = angular.module('demo', []);
demo.directive('displayTime', function($parse) {
return {
restrict: 'E',
replace: true,
scope: {
data: '='
},
transclude: false,
template: '<span class="currentTime"></span>',
link: function (scope, element, attrs, controller) {
var currentDate = new Date();
console.log(scope.data);
element.text(currentDate.toTimeString());
}
}});
Observe carefully, the syntax used for data="{{array}}".
Since i am using data in the scope of custom directive (with the statement
scope: {
data: '='
},
),
i will get parse error
But if i use the syntax data="array", and i use the following code snippet inside the link function
scope: {
//data: '='
},
then i will not get a parse error.
So you should use the syntax data="{{array}}" only if you want to access it as part of attrs parameter inside link function.
I have a controller (called "catalogueController") that manages my search box and my search page. I have the controller initially set the page to automatically call the search function defined in "catalogueController" when the app loads to pre-load my array of items (called Inventory) to be repeated via ng-repeat in the page.
The process runs like this:
1. I submit the search form.
2. "catalogueController" will send the search term to my factory (called "Search").
3. "Search" will have a function which will make a server call to query my database for that particular search.
4. The database will send the results of the search to the "Search" factory.
5. The "Search" factory will send the results to the "catalogueController" controller.
6. "catalogueController" will update the $scope.Inventory to be equal to the new result that I was received.
My problem is that ng-repeat does not refresh itself to display my new and updated $scope.Inventory array. $scope.Inventory definitely is updated (I have made sure of this through various console logs).
I have also tried to use $scope.$apply(). It did not work for me.
Thank you in advance for your help!
Here is my code:
HTML Template
<form role="search" class="navbar-form navbar-left" ng-controller="catalogueController" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<main ng-view></main>
catalogue.html partial
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
"app.js" Angular App
var myApp = angular.module('qcbApp', ['ngRoute', 'ngCookies', 'appControllers']);
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'html/partials/login.html',
controller: 'registrationController'
}).
when('/sign-up', {
templateUrl: 'html/partials/sign-up.html',
controller: 'registrationController'
}).
when('/catalogue', {
templateUrl: 'html/partials/catalogue.html',
controller: 'catalogueController'
}).
when('/', {
templateUrl: 'html/partials/qcbhome.html'
}).
otherwise({
redirectTo: '/'
});
}]);
"catalogueController" Controller
myApp.controller('catalogueController', ['$scope', 'Search', function($scope, Search) {
var time = 0;
var searchCatalogue = function(search) {
$scope.inventory = null;
console.log("Controller -- "+search);
Search.searchCatalogue(search)
.then(function(results) {
console.log(results);
$scope.inventory = results;
});
};
if(time == 0)
{
searchCatalogue('');
time++;
}
$scope.search = function(term) {
searchCatalogue(term);
}
}]);
"Search" Factory
myApp.factory('Search', ['$http', '$q', function($http, $q) {
function searchCatalogue(term) {
var deferred = $q.defer();
console.log("Factory -- "+term);
$http.post('/catalogue_data', {term: term}, {headers: {'Content-Type': 'application/json'}})
.success(function(result) {
console.log(result[0].SKU);
deferred.resolve(result);
console.log("Factory results -- "+result);
});
return deferred.promise;
}
return {
searchCatalogue: searchCatalogue
}; //return
}]);
I think the problem is the ng-repeat can not access the inventory in scope. You have to create a div which contains both the form and the ng-repeat.
The html should be:
<div ng-controller="catalogueController">
<!-- Move the controller from the form to parent div -->
<form role="search" class="navbar-form navbar-left" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
</div>
I've seen the situation a few times where when you are updating a property directly on the $scope object there are interesting problems around databinding to that value (such as inventory). However if you databind to an object property of an object then the databinding works as expected. So for example use a property on $scope. I believe this is a copy by value vs copy by reference issue.
Update all your inventory references as follows
$scope.data.inventory = result;
Also don't forget to update your inventory reference in the html template:
<div ng-repeat="items in data.inventory" class="row-fluid">
Update: I made this plunk to figure it out - http://plnkr.co/edit/0ZLagR?p=preview
I think the primary problem is you have the controller specified twice. I removed it from the form and it started working.