I'm using the excellent AngularJS Rails Resources and with one object - which has deep nested objects in turn - when I update some of its properties, the updated property does not show on the template until I reload the page.
Let start from the beginning: here's my object:
var invoice = Invoice.get($stateParams.invoiceId).then(function (result) {
$scope.invoice = result;
});
And here's how I open my modal to edit the values:
$scope.openEdit = function (edit) {
$scope.editModal = $modal.open({
templateUrl: 'editModalContent.html',
controller: 'InvoiceShowController',
size: 'lg'
});
$scope.editModal.result.then(function(select) {
});
};
$scope.cancel = function () {
$scope.$close();
};
$scope.ok = function () {
$scope.invoice.update().then(function (result) {
$scope.cancel();
console.log(result);
});
};
In my view I have the following:
...
<li>{{invoice.trading_details.trading_name}}</li>
<li>{{invoice.trading_details.trading_address_1}}</li>
...
In the modal body I have the following:
...
<div class="form-group">
<label for="exampleInputEmail1">Trading Name</label>
<input ng-model="invoice.trading_details.trading_name" type="text" class="form-control" id="exampleInputEmail1">
</div>
<div class="form-group">
<label for="exampleInputEmail1">Trading Address Line 2</label>
<input ng-model="invoice.trading_details.trading_address_1" type="text" class="form-control" id="exampleInputEmail1">
</div>
...
So when I edit the properties in the modal and console the object, the changes are there. When I save and get the result back, the changes are there, but for whatever reason the view is not updating.
Any ideas?
EDIT: My whole controller
It looks like you are missing the resolve setting. It passing data to your modal.
$scope.openEdit = function (edit) {
$scope.editModal = $modal.open({
templateUrl: 'editModalContent.html',
controller: 'InvoiceShowController',
size: 'lg',
//notice a function is returning the data
resolve: {
invoice: function(){
return $scope.invoice;
}
}
});
};
EDIT
Link to Plunker: http://plnkr.co/edit/IJvdBJrJngsNYaG39Gfh?p=preview
Notice how the resolve creates an instance invoice that is passed into the editCtrl.
UPDATE
You can also do
$scope.editModal = $modal.open({
templateUrl: 'editModalContent.html',
controller: 'InvoiceShowController',
size: 'lg',
//notice a function is returning the data
resolve: {
invoice: function(){
return Invoice.get($stateParams.invoiceId);
}
}
});
...because the resolve can process a promise.
Related
I have simple case where I defined modal as component and open that modal using uiModal.open. However when I want to pass any custom data to that modal using "resolve" on open method and arguments on controller constructor the data is not passed. Also trying to inject $uibModalInstance fails with error Unknown provider: $uibModalInstanceProvider <- $uibModalInstance so I cannot return the results while closing the modal.
Template:
<div class="modal-header">
<h3 class="modal-title" id="modal-title">Modal</h3>
</div>
<div class="modal-body" id="modal-body">
Some text
<div class="row">
<div class="col-sm-10">
<textarea class="form-control" name="closureNotes" rows="6" data-ng-model="vm.closureNote" required>
</textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default btn-close" type="submit" ng-click="vm.ok()">Close Exception Request</button>
<button class="btn btn-danger" type="button" ng-click="vm.cancel()">Cancel</button>
</div>
Component:
import template from './closeExceptionModal.html';
import controller from './closeExceptionModal.controller';
let closeExceptionModalComponent = {
restrict: 'E',
bindings: {
resolve: '<',
close: '&',
dismiss: '&'
},
template,
controller,
controllerAs: 'vm'
};
export default closeExceptionModalComponent;
Controller:
class CloseExceptionModalController {
constructor() {
//I need to retrieve here some data from caller
}
ok() {
this.close(); //I need to pass here result to caller using modalInstance.close
}
cancel() {
this.dismiss();
}
}
export default CloseExceptionModalController;
Caller controller code:
//I need to pass some data to modal
let modalInstance = this.$uibModal.open({
animation: true,
component: 'closeExceptionModal',
appendTo: angular.element(document.body),
})
modalInstance.result.then( (result) => {
alert(result); //this must be result data from modal
}, () => {
});
I spent almost three hours researching that issue, I tried to passing $modalInstance, $uibModalInstance, etc.. I tried resolve and constructor arguments, I searched a lot of threads on stackoverflow each with no luck suggesting upgrade of ui.bootstrap, angularjs etc..
The core issue is that what I try to do in different way is to use component to define modal not to inline define the controller and indicated template within the code of "caller" controller.
Finally with help of some partial knowledge gathered from many threads I came with this wonderful and easy solution.
To pass any data to component based modal and modalInstance all we have to do is update the component definition bindings section:
import template from './closeExceptionModal.html';
import controller from './closeExceptionModal.controller';
let closeExceptionModalComponent = {
restrict: 'E',
bindings: {
resolve: '<', //this let us pass any data from caller constructor
modalInstance: '<', //modalInstance will be auto injected
close: '&',
dismiss: '&'
},
template,
controller,
controllerAs: 'vm'
};
export default closeExceptionModalComponent;
The calling of modal should look like this:
let modalInstance = this.$uibModal.open({
animation: true,
component: 'closeExceptionModal',
appendTo: angular.element(document.body),
resolve: {
modalData: function() {
return "test";
}
}
})
modalInstance.result.then( (result) => {
alert(result);
}, () => {
});
And my final modal controller looks like this:
class CloseExceptionModalController {
constructor() {
this.$ctrl = this; //we store the controller instance
console.log(this.$ctrl.resolve.modalData); //here is my input data
}
ok() {
//modal instance is auto injected and we can call close passing result
this.$ctrl.modalInstance.close(this.closureNote);
}
cancel() {
this.dismiss();
}
}
export default CloseExceptionModalController;
I hope this will help others trying to do the ui bootstrap modal with component and pass/return data! :)
Hi all my requirement is to share input field data using get set method in factory with another controller.
angular.module('dataModule',[]).factory('myFact',function($http){
var user = {};
return {
getDetails: function () {
return user ;
},
setDetails : function (name,add,number) {
user.name = name;
user.add = add;
user.number = number;
}
}
});
Here is controller code.
angular.module('dataModule',[]).controller('thirdCtrl',function(myFact,$scope) {
$scope.saw=function(){
alert("hello get set method");
$scope.user=myFact.user.getDetails();
console.log(user);
};
});
Here is my html code
<div ng-controller="thirdCtrl">
<h1>hello gaurav come here after click one.</h1>
<div>
<lable>NAME</lable>
<div><input type="text"ng-model="user.name"></div>
</div>
<div>
<lable>ADDRESS</lable>
<div><input type="text"ng-model="user.add"></div>
</div>
<div>
<lable>MOBILE</lable>
<div><input type="number"ng-model="user.number"></div>
</div>
</br>
</br>
<button type="button" ng-click="saw()">Click</button>
</div>
Here is my app.js
var app = angular.module('sapient',['ui.router','dataModule']);
app.config(['$stateProvider','$urlRouterProvider',function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('one', {
url: '/one',
templateUrl: 'view/sap.html',
controller: 'sapCtrl'
})
.state('two', {
url: '/two',
templateUrl: 'view/two.html',
controller: 'secondCtrl'
})
.state('three', {
url: '/three',
templateUrl: 'view/three.html',
controller: 'thirdCtrl'
});
$urlRouterProvider.otherwise('two');
}])
Any Suggestions
Thanks in advance
The problem is you're recreating the module. Remove the [] (array of module dependencies) to make angular retrieve the previously created module, rather than create a new one.
Change:
angular.module('dataModule',[]).controller('thirdCtrl',function(myFact,$scope)
To:
angular.module('dataModule').controller('thirdCtrl',function(myFact,$scope)
I have been following some online tutorials and using the angularjs-template to get started with Angular. I can't get the page (html template) to update with the controller. I think there is a problem with the way I have set up the controller as the values are not available to the html template.
I have been trying to follow some of the best practive guides which suggested to wrap my components in an 'Invoked Function Expression' and to seperate out the controller, service and service manager. However, I think I have made a bit of a hash of this and need some help to figure out what I am doing wrong.
With the console I can see that $scope.metric contains the information I want. For me this means that the controller has successfully pulled the data back from my API via the metricService. However I can't seem to have the results printed back onto the html page e.g. metric.id.
Any help appreciated - I am at the end of my wits trying to figure this out.
metric.html
<div class="panel panel-primary">
<div class="panel-body">
<!-- Try First Way to Print Results -->
Id: <span ng-bind="metric.id"></span></br>
Name:<input type="text" ng-model="metric.metadata.name" /></br>
<!-- Try Second Way to Print Results -->
<p data-ng-repeat="thing in ::MEC.metric track by $index">
{{$index + 1}}. <span>{{thing.metadata.name}}</span>
<span class="glyphicon glyphicon-info-sign"></span>
</a>
</p>
<!-- Try Third Way to Print Results -->
Id: <span ng-bind="Metric.metricId"></span></br>
Id: <span ng-bind="Metric.id"></span></br>
Id: <span ng-bind="metricService.id"></span></br>
<!-- Try Fourth Way to Print Results -->
Id: <strong>{{::MEC.metric.id}}</strong></br>
Name: <strong>{{::MEC.metric.metadata.name}}</strong></br>
Height: <strong>{{::MEC.metric.type}}</strong>
</div>
metricController.js
(function () {
'use strict';
angular.module('app.metric', ['app.metricService', 'app.metricManager'])
.controller('MetricController', MetricController)
MetricController.$inject = ['$scope', 'metricManager', '$log'];
function MetricController($scope, metricManager, $log) {
metricManager.getMetric(0).then(function(metric) {
$scope.metric = metric
$log.info('$scope.metric printed to console below:');
$log.info($scope.metric);
})
}
})();
metricService.js
(function () {
'use strict';
angular.module('app.metricService', [])
.factory('Metric', ['$http', '$log', function($http, $log) {
function Metric(metricData) {
if (metricData) {
this.setData(metricData);
}
// Some other initializations related to book
};
Metric.prototype = {
setData: function(metricData) {
angular.extend(this, metricData);
},
delete: function() {
$http.delete('https://n4nite-api-n4nite.c9users.io/v1/imm/metrics/' + metricId);
},
update: function() {
$http.put('https://n4nite-api-n4nite.c9users.io/v1/imm/metrics/' + metricId, this);
},
hasMetadata: function() {
if (!this.metric.metadata || this.metric.metadata.length === 0) {
return false;
}
return this.metric.metadata.some(function(metadata) {
return true
});
}
};
return Metric;
}]);
})();
metricManager.js
(function () {
'use strict';
angular.module('app.metricManager', [])
.factory('metricManager', ['$http', '$q', 'Metric', function($http, $q, Metric) {
var metricManager = {
_pool: {},
_retrieveInstance: function(metricId, metricData) {
var instance = this._pool[metricId];
if (instance) {
instance.setData(metricData);
} else {
instance = new Metric(metricData);
this._pool[metricId] = instance;
}
return instance;
},
_search: function(metricId) {
return this._pool[metricId];
},
_load: function(metricId, deferred) {
var scope = this;
$http.get('https://n4nite-api-n4nite.c9users.io/v1/imm/metrics/' + metricId).then(successCallback, errorCallback)
function successCallback(metricData){
//success code
var metric = scope._retrieveInstance(metricData.id, metricData);
deferred.resolve(metric);
};
function errorCallback(error){
//error code
deferred.reject();
}
},
/* Public Methods */
/* Use this function in order to get a metric instance by it's id */
getMetric: function(metricId) {
var deferred = $q.defer();
var metric = this._search(metricId);
if (metric) {
deferred.resolve(metric);
} else {
this._load(metricId, deferred);
}
return deferred.promise;
},
/* Use this function in order to get instances of all the metrics */
loadAllMetrics: function() {
var deferred = $q.defer();
var scope = this;
$http.get('ourserver/books')
.success(function(metricsArray) {
var metrics = [];
metricsArray.forEach(function(metricData) {
var metric = scope._retrieveInstance(metricData.id, metricData);
metrics.push(metric);
});
deferred.resolve(metrics);
})
.error(function() {
deferred.reject();
});
return deferred.promise;
},
/* This function is useful when we got somehow the metric data and we wish to store it or update the pool and get a metric instance in return */
setMetric: function(metricData) {
var scope = this;
var metric = this._search(metricData.id);
if (metric) {
metric.setData(metricData);
} else {
metric = scope._retrieveInstance(metricData);
}
return metric;
},
};
return metricManager;
}]);
})();
Snippet from App.routes
.state('root.metric', {
url: 'metric',
data: {
title: 'Metric',
breadcrumb: 'Metric'
},
views: {
'content#': {
templateUrl: 'core/features/metric/metric.html',
controller: 'MetricController',
controllerAs: 'MEC'
}
}
})
Console
You are mixing two concepts controller alias and $scope, in your case you are creating controller alias as MEC using controllerAs. If you are using controller alias then this will work fine for you :
function MetricController($scope, metricManager, $log) {
var MEC = this;
metricManager.getMetric(0).then(function(metric) {
MEC.metric = metric
$log.info('$scope.metric printed to console below:');
$log.info($scope.metric);
})
}
If you don't want to use controller alias and share data between view and controller via $scope then in your view you should use something like this {{::metric.metadata.name}} and controller function should stay as it is.
PS: If you are using alias then MEC in var MEC = this can be MEC or abc or any name you like but convention is to use var vm = this and controllerAs: 'vm'. If you have controllerAs: 'xyz' then in your view xyz should be used to access model.
Problem with your view HTML, you need to use proper Angular expressions while binding. When you want use ::MEC alias name you need to mark your controller with as keyowrd, like ng-controller="xyz as MEC". And checkout working Plunker
<div class="panel panel-primary">
<div class="panel-body">
<!-- Try First Way to Print Results -->
Id: <span ng-bind="metric.id"></span>
<br> Name1:
<input type="text" ng-model="metric.metadata.name" />
<br><br><br><br>
<!-- Try Second Way to Print Results -->
<p data-ng-repeat="thing in [metric] track by $index">
{{$index + 1}}. <span>{{thing.metadata.name}}</span>
<span class="glyphicon glyphicon-info-sign"></span>
</p><br><br><br>
<!-- Try Third Way to Print Results -->
Id: <span ng-bind="metric.metricId"></span>
<br> Id: <span ng-bind="metric.id"></span>
<br><br><br>
<!-- Try Fourth Way to Print Results -->
Id: <strong>{{::metric.id}}</strong>
<br> Name: <strong>{{::metric.metadata.name}}</strong>
<br> Height: <strong>{{::metric.type}}</strong>
</div>
</div>
I'm struggling to display 'nested' [array] data in my modal. I am able to see all my data fetched from a url in angular, on my view page. However, when I ng-click on the same element to pop out a modal to see the same information on the modal, I can't see some of the information. In this case I cant the employees or developers.
url fetching code in angular
**controller used is 'HomeController'
**/comments is where my json data is located with 'employees'
being a one-to-many relationship entity in my backend (which might be the issue in my head)
var vm = this;
vm.projects = [];
$http.get('/comments')
.then(function(result) {
console.log(result);
vm.projects = result.data;
});
data as used in my modal template (not working code in modal)
<div ng-repeat="data in controller.projects">
<ul ng-repeat="employee in data.employees">
{{employee.name}}
</ul>
</div>
controller comes from <div ng-controller="HomeController as controller">
I have also tried this;
<div ng-repeat="employee in Project.Employees" class="selected-item">
{{employee.name}}
</div>
but doesn't work!
As seen below, the employees show up on the 'normal' page.
However, when I ng-click="editProject(data)" the above card to pop up the modal, everything else shown above (not shown below here) pops up except the employees. Could it be a routing issue?
Basically everything works perfect on the template view until I pop out the modal. This is the "modal popping" code;
$scope.editProject = function(data) {
$scope.showSelected = true;
$scope.SelectedProject = data;
var fromDate = moment(data.start).format('DD/MM/YYYY LT');
var endDate = moment(data.end).format('DD/MM/YYYY LT');
$scope.Project = {
ProjectID : data.projectID,
Client : data.client,
Title : data.title,
Description: data.description,
Employees: data.employees,
StartAt : fromDate,
EndAt : endDate,
IsFullDay : false
}
$scope.ShowModal()
},
//This function is for show modal dialog
$scope.ShowModal = function(){
$scope.option = {
templateUrl: 'modalContent.html',
controller: 'modalController',
backdrop: 'static',
resolve: {
Project : function () {
return $scope.Project;
},
SelectedProject : $scope.SelectedProject
}
};
Could there be a promise I'm missing in the ShowModal resolve?
Meanwhile in chrome devtools, the employees seemed to have been 'picked' up as shown below;
However when I open the object, interestingly it says array is empty;
I am new to angular and web development in general. Let me know if you need me to reveal my backend or any other code you think is missing. I'm just thinking it's an angular issue. Thanks for your help.
you need to pass projects as dependency injection to you modal instance controller:
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($uibModalInstance, projects) {
var $ctrl = this;
$ctrl.projects = projects;
$ctrl.ok = function () {
$uibModalInstance.close();
};
$ctrl.cancel = function () {
$uibModalInstance.dismiss();
};
});
and resolve it in your opening function:
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($http, $uibModal, $log, $document) {
var $ctrl = this;
$http.get('data.json').then(function(result) {
console.log(result);
$ctrl.projects = result.data;
});
$ctrl.open = function (size, parentSelector) {
var parentElem = parentSelector ?
angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
var modalInstance = $uibModal.open({
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
controllerAs: '$ctrl',
size: size,
appendTo: parentElem,
resolve: {
projects: function () {
return $ctrl.projects;
}
}
});
};
});
please note also that controller name is passed with controllerAs property and not within HTML template.
plunker: https://plnkr.co/edit/wLI0pa07UNhr9Ld8CSg3?p=preview
I'm fairly new to Angular, and I'm trying to figure out why scope variables isn't updating after they've been set.
I'm calling a Node API returing json objects containing my data. Everything seems to work fine except setting $scope.profile to the data returned from the API.
Setup:
app.js
(function() {
var app = angular.module("gamedin", []);
app.controller('profileController', function($scope, $http, $timeout) {
$scope.profile = {};
$scope.getProfile = function() {
var vanityUrl = $scope.text.substr($scope.text.lastIndexOf('/') + 1);
$http.get('/steamid/' + vanityUrl)
.then(function(data) {
$http.get('/profile/' + data.data.response.steamid)
.then(function(data) {
console.log(data.data.response.players[0]); // Correct data
$scope.profile = data.data.response.players[0]; // View isn't updated
})
})
// Reset the input text
$scope.text = "";
}
});
...
app.directive('giHeader', function() {
return {
restrict: 'E',
templateUrl: 'components/header/template.html'
};
})
app.directive('giProfile', function() {
return {
restrict: 'E',
templateUrl: 'components/profile/template.html'
}
})
})();
components/header/template.html
<header>
<div class="header-content" ng-controller="profileController">
<div class="col-md-3"></div>
<div class="col-md-6">
<div class="header-content-inner">
<input ng-model="text" ng-keyup="$event.keyCode == 13 && getProfile()" class="form-control" type="text" placeholder="Enter Steam URL">
</div>
<p>e.g., http://steamcommunity.com/id/verydankprofilelink</p>
</div>
<div class="col-md-3"></div>
</div>
</header>
components/profile/template.html
<div class="container">
<div ng-controller="profileController">
<h3>
<strong>Username: {{ profile.personaname }}</strong>
</h3>
<p> SteamID: {{ profile.steamid }}</p>
</div>
</div>
index.html
<!doctype html>
<html ng-app="gamedin">
<head>
...
</head>
<body>
...
<gi-header></gi-header>
<gi-profile></gi-profile>
...
</body>
</html>
I've tried wrapping it in $scope.$apply, like this
$scope.$apply(function () {
$scope.profile = data.data.response.players[0];
});
... which resulted in Error: [$rootScope:inprog]
Then I tried
$timeout(function () {
$scope.profile = data.data.response.players[0];
}, 0);
and
$scope.$evalAsync(function() {
$scope.profile = data.data.response.players[0];
});
... and although no errors were thrown, the view still wasn't updated.
I realize that I'm probably not understanding some aspects of angular correctly, so please enlighten me!
The problem is that you have 2 instances of profileController, one in each directive template. They should both share the same instance, because what happens now is that one instance updates profile variable on its scope, and the other is not aware. I.e., the profileController instance of header template is executing the call, and you expect to see the change on the profile template.
You need a restructure. I suggest use the controller in the page that uses the directive, and share the profile object in both directives:
<gi-header profile="profile"></gi-header>
<gi-profile profile="profile"></gi-profile>
And in each directive:
return {
restrict: 'E',
scope: {
profile: '='
},
templateUrl: 'components/header/template.html'
};
And on a more general note - if you want to use a controller in a directive, you should probably use the directive's "controller" property.
Try using this method instead:
$http.get('/steamid/' + vanityUrl)
.then(function(data) {
return $http.get('/profile/' + data.data.response.steamid).then(function(data) {
return data;
});
})
.then(function(data) {
$scope.profile = data.data.response.players[0]; // View isn't updated
})
Where you use two resolves instead of one and then update the scope from the second resolve.