AngularJS controllerAs not binding input controls - angularjs

I'm initializing ControllerAs on all my Routes using the UI Router. The issue i'm having is when i click on Edit record, it takes me to another page which also passes the ID from the current page to fetch the record from the service and data bind the html fields.
In my controller class. When i write
var vm = this; //DOES NOT DATABIND
var vm = $scope; // THIS WORKS, FIELDS GET POPULATED.
Not sure what I'm doing wrong. The first line of code should work because i'm using ControllerAs right ?
Can someone please advise.
Thank you
//UI ROUTER
.state('dashboard.editUser',
{
url: '/editUser/:UserId',
templateUrl: 'app/components/admin/user/editUser.html',
controller: 'editUserController',
controllerAs: 'tF',
ncyBreadcrumb:
{
parent: 'dashboard',
label: 'Edit User'
},
});
//EDIT CONTROLLER
(function () {
var injectParams = ['$stateParams', '$state', 'userService'];
function editUserController($stateParams, $state, userService) {
var vm = this;
//load user
var loadUser = function () {
var userId = $stateParams.UserId;
vm.user = null;
userService.GetUserDetail(userId)
.success(function (data) {
vm.user = data;
})
.error(function (error) {
vm.status = 'Error retrieving data! ' + error.message;
});
};
loadUser();
};
editUserController.$inject = injectParams;
angular
.module('fatApp.userModule')
.controller('editUserController', editUserController);
}());

Related

access the data from the resolve function without relading the controller

How do we access the data from the resolve function without relading the controller?
We are currently working on a project which uses angular-ui-router.
We have two seperated views: on the left a list of parent elements, on the right that elements child data.
If selecting a parent on the left, we resolve it's child data to the child-view on the right.
With the goal not to reaload the childs controller (and view), when selecting a different parent element, we set notify:false.
We managed to 're-resolve' the child controllers data while not reloading the controller and view, but the data (scope) won't refresh.
We did a small plunker to demonstrate our problem here
First click on a number to instantiate the controllers childCtrl. Every following click should change the child scopes data - which does not work.
You might notice the alert output already has the refreshed data we want to display.
Based on sielakos answer using an special service i came up with this solution.
First, i need a additional service which keeps a reference of the data from the resovle.
Service
.service('dataLink', function () {
var storage = null;
function setData(data) {
storage = data;
}
function getData() {
return storage;
}
return {
setData: setData,
getData: getData
};
})
Well, i have to use the service in my resolve function like so
Resolve function
resolve: {
detailResolver: function($http, $stateParams, dataLink) {
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
dataLink.setData(response.data);
return response.data;
});
}
}
Notice the line dataLink.setData(response.data);. It keeps the data from the resolve in the service so I can access it from within the controller.
Controller
I modified the controller a little. I wrapped all the initialisation suff in an function i can execute when the data changes.
The second thing is to watch the return value of the dataLink.getData();
As of https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch provides functionality to watch return values of functions.
Here is some Q&D example:
.controller('childCtrl', function($scope, $log, detailResolver, $interval, dataLink) {
initialise();
/*
* some stuff happens here
*/
$interval(function() {
console.log(detailResolver.id)
}, 1000);
$scope.$watch(dataLink.getData, function(newData) {
detailResolver = newData;
initialise();
});
function initialise() {
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.id = detailResolver;
}
})
The line $scope.$watch(dataLink.getData, function(newData) { ... }); does the trick. Every time the data in the dataLink service changes the callback kicks in and replaces the old data with the new one.
Ive created a plunker so you can give it a try https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM
You don't have to be afraid of memory leaks using this solution cause angular is removing watchers automatically. See https://stackoverflow.com/a/25114028/6460149 for more information.
Not so pretty, but working solution would be to use events. Well, maybe it is not that bad, at least it is not complicated.
https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview
angular.module('app',[
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views:{
'parent':{
controller: 'parentCtrl',
template: '<div id="parent">'+
'<button ng-click="go(1)">1</button><br>'+
'<button ng-click="go(2)">2</button><br>'+
'<button ng-click="go(3)">3</button><br>'+
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views:{
'child#':{
controller: 'childCtrl',
template:'<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: function($http, $stateParams, $rootScope) {
return $http.get('file'+$stateParams.id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
$rootScope.$broadcast('newData', response.data);
return response.data;
});
}
}
});
})
.controller('parentCtrl', function ($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function (id) {
$state.go('parent.child', {id: id}, {notify:notify});
notify = false;
};
})
.controller('childCtrl', function ($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
$scope.$on('newData', function (event, detailResolver) {
$scope.id = detailResolver;
});
$scope.id = detailResolver;
$interval(function(){
console.log(detailResolver.id)
},1000)
})
;
EDIT:
A little bit more complicated solution, that requires changing promise creator function into observables, but works:
https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview
angular.module('app', [
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state('parent', {
views: {
'parent': {
controller: 'parentCtrl',
template: '<div id="parent">' +
'<button ng-click="go(1)">1</button><br>' +
'<button ng-click="go(2)">2</button><br>' +
'<button ng-click="go(3)">3</button><br>' +
'</div>'
},
},
url: ''
});
$stateProvider.state('parent.child', {
views: {
'child#': {
controller: 'childCtrl',
template: '<b>{{ id }}</b>'
}
},
url: '/:id/child',
resolve: {
detailResolver: turnToObservable(['$http', '$stateParams', function($http, $stateParams) { //Have to be decorated either be this or $inject
return $http.get('file' + $stateParams.id + '.json')
.then(function(response) {
alert('response ' + response.data.id);
return response.data;
});
}])
}
});
})
.controller('parentCtrl', function($log, $scope, $state) {
$log.info('parentCtrl');
var notify = true;
$scope.go = function(id) {
$state.go('parent.child', {id: id}, {notify: notify});
notify = false;
};
})
.controller('childCtrl', function($scope, $log, detailResolver, $interval) {
/*
* some stuff happens here
*/
$log.info('childCtrl detailResolver.id == ' + detailResolver);
detailResolver.addListener(function (id) {
$scope.id = id;
});
});
function turnToObservable(promiseMaker) {
var promiseFn = extractPromiseFn(promiseMaker);
var listeners = [];
function addListener(listener) {
listeners.push(listener);
return function() {
listeners = listeners.filter(function(other) {
other !== listener;
});
}
}
function fireListeners(result) {
listeners.forEach(function(listener) {
listener(result);
});
}
function createObservable() {
promiseFn.apply(null, arguments).then(fireListeners);
return {
addListener: addListener
};
}
createObservable.$inject = promiseFn.$inject;
return createObservable;
}
function extractPromiseFn(promiseMaker) {
if (angular.isFunction(promiseMaker)) {
return promiseMaker;
}
if (angular.isArray(promiseMaker)) {
var promiseFn = promiseMaker[promiseMaker.length - 1];
promiseFn.$inject = promiseMaker.slice(0, promiseMaker.length - 1);
return promiseFn;
}
}
1) For current task ng-view is not needed (IMHO). If you need two different scopes then redesign ng-views to become directives with their own controllers. This will prevent angular to reload them
2) if you need to share data between scopes then service could be used to store data (see helperService in the following code)
3) if we talk about current code simplification then it could be done so: use service from 2) and just use one controller:
(function() {
angular.module('app',[
'ui.router'
]);
})();
(function() {
angular
.module('app')
.service('helperService', helperService);
helperService.$inject = ['$http', '$log'];
function helperService($http, $log) {
var vm = this;
$log.info('helperService');
vm.data = {
id: 0
};
vm.id = 0;
vm.loadData = loadData;
function loadData(id) {
vm.id = id;
$http
.get('file'+id+'.json')
.then(function(response) {
alert('response ' + response.data.id);
vm.data = response.data;
});
}
}
})();
(function() {
angular
.module('app')
.controller('AppController', ParentController);
ParentController.$inject = ['helperService', '$log'];
function ParentController(helperService, $log) {
var vm = this;
$log.info('AppController');
vm.helper = helperService;
}
})();
4) interval, watch, broadcast, etc are not needed as well
Full code is here: plunker
P.S. don't forget about angularjs-best-practices/style-guide

Save form before state change with uirouter

I have multi steps form in my application and i would like to save it before go to the next step...
I did an simple example, but i'm not sure if it's the right approach.
I used resolve config and a service to access to the formData.
Config
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('home', {
url : '/',
controller : 'appCtrl',
templateUrl : 'partials/home.html',
resolve : {
saveform : function($state, FormData){
}
},
})
.state('home.state1', {
url: "state1",
templateUrl : 'partials/item1.html',
resolve : {
saveform : function($state, FormData){
return FormData.save();
}
},
})
.state('home.state2', {
url: "state2",
templateUrl : 'partials/item2.html',
resolve : {
saveform : function($state, FormData){
return FormData.save();
}
}
})
});
Controller
app.controller('appCtrl', function($scope, $rootScope, $state, saveform, FormData){
$scope.formData = {};
$scope.tempData = {};
$rootScope.$on('$stateChangeStart', function(event){
console.log($scope.tempData , $scope.formData)
if(!_.isEqual($scope.tempData , $scope.formData)){
console.log('OK CHANGE DETECTED, SAVE')
var temp = {}
FormData.set(angular.copy($scope.formData));
}else{
console.log('NO CHANGE')
}
});
$rootScope.$on('$stateChangeSuccess', function(event){
var temp = FormData.get();
if(Object.keys(temp).length!=0){
FormData.cancel();
angular.copy(temp, $scope.tempData );
angular.copy(temp, $scope.formData );
}
});
})
Service
app.service('FormData', function($q, $timeout){
this.formdata = {};
this.save = function(){
if(Object.keys(this.formdata).length===0)
return false;
var deferred = $q.defer();
$timeout(function() {
this.formdata.test = "YEAH TEST";
deferred.resolve(this.formdata);
//this.formdata = {};
}.bind(this), 1000);
return deferred.promise;
}
this.cancel = function(){
this.formdata = {};
}
this.set = function(data){
this.formdata = data;
}
this.get = function(){
return this.formdata;
}
})
Example on codepen : http://codepen.io/anon/pen/RWpZGj
I would approach this differently.
You're using multiple states for one single form. Usually you only want to save complete form data. Therefore I would devide the form into multiple sections that you can show/hide. That way, upon clicking your "next" button you can validate each step separately and show the next step once the current one is valid.
Once you've reached the last step of your form, you can save the whole thing in one go.
If you insist on using separate states:
You're using the state change to trigger a save action. Instead, use the response of your FormData service to trigger a statechange. That way you can validate and save your data before you move on to the next step. It also prevents you from saving data when someone moves to another state that is not in your multi-step form.

AngularJS bind returning json data to template

I am unable to find easy steps on how to bind a returning json object to a angular template [The template is also remote]
e.g. data in success callback is JSON and I want to bind it to a template before display
app.controller('jobs', ['$scope', 'wpHttp', function ($scope, wpHttp) {
$scope.buildMatches = function (job_id) {
$scope.matchesHtml = 'Loading matches please wait ...';
wpHttp("get_employer_job_matches").success(function (data) {
// bind json to template similar to mustache.js?
$scope.matchesHtml = data;
});
}; etc.......
I am using bootstrap.ui and I am want to load the rendered template as follows
<tab select="buildMatches(job.ID)" heading="Matches">
<div data-ng-bind-html="matchesHtml"></div>
</tab>
# azium
Yes the JSON data is returned as expected everything is working fine, I think the issue can be solved using a custom directive, I want to bind data to templateUrl: views_url + "matches.php" only when tab select="buildMatches(job.ID)" heading="Matches" is selected.
(function (angular, wp_localize) {
var app = angular.module("app", ['ngRoute', 'ngSanitize', 'ui.bootstrap']);
var theme_url = wp_localize.theme_url;
var views_url = theme_url + "/angular-apps/employer-profile/views/";
var language = lang;
app.service('wpHttp', ['$http', function ($http) {
var request = {
method: 'POST',
url: ajaxurl,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
};
return function (action, data) {
var queryStr = "action=" + action;
if (data) {
queryStr += "&data=" + JSON.stringy(data);
}
request.data = queryStr;
return $http(request);
};
}]);
app.controller('jobs', ['$scope', 'wpHttp', function ($scope, wpHttp) {
$scope.lang = language;
$scope.img = theme_url + "/images/front_page/candidate-mummy.png";
$scope.questionnaireHtml = $scope.matchesHtml = '';
$scope.buildMatches = function (job_id) {
$scope.matchesHtml = 'Loading matches please wait ...';
wpHttp("get_employer_job_matches").success(function (data) {
$scope.matchesHtml = data;
});
};
$scope.buildQuestionnaire = function () {
$scope.matchesHtml = 'Loading please wait';
};
wpHttp("get_employer_jobs").success(function (data) {
$scope.jobs = data;
});
}]);
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: views_url + "create-jobs.php",
controller: 'jobs'
}).when('/jobs', {
templateUrl: views_url + "list-jobs.php",
controller: 'jobs'
});
}
]);
app.directive('ngMatchesHtml', function () {
return {
restrict: 'A',
templateUrl: views_url + "matches.php"
}
});
})(angular, wp_localize_employer_profile);

Prevent multiple ajax calls when re-using a controller/factory

just starting out really with Angular and need some advice regarding preventing repeated ajax requests for the same data when re-using a controller with multiple view.
So I have say 6 views all referencing the same controller but different views
app.js
(function() {
var app = angular.module('myApp', ['ngRoute','ui.unique']);
app.config(function ($routeProvider) {
// Routes
$routeProvider
.when('/',
{
controller: 'SamplesController',
templateUrl: 'app/views/home.html'
})
.when('/view2/',
{
controller: 'SamplesController',
templateUrl: 'app/views/main.html'
})
.when('/view3/:rangeName',
{
controller: 'SamplesController',
templateUrl: 'app/views/samples.html'
})
.when('/view4/:rangeName',
{
controller: 'SamplesController',
templateUrl: 'app/views/samples.html'
})
.when('/view5/',
{
controller: 'SamplesController',
templateUrl: 'app/views/basket.html'
})
.when('/view6/',
{
controller: 'SamplesController',
templateUrl: 'app/views/lightbox.html'
})
.otherwise({ redirectTo: '/' });
});
}());
samplesController.js
(function() {
var SamplesController = function ($scope, SamplesFactory, appSettings, $routeParams) {
function init() {
// back function
$scope.$back = function() {
window.history.back();
};
// app settings
$scope.settings = appSettings;
// samples list
SamplesFactory.getSamples()
.success(function(data){
var returnSamples = [];
for (var i=0,len=data.length;i<len;i++) {
if (data[i].range === $routeParams.rangeName) {
returnSamples.push(data[i]);
}
}
$scope.samples = returnSamples;
})
.error(function(data, status, headers, config){
// return empty object
return {};
});
// variables for both ranges
$scope.rangeName = $routeParams.rangeName;
// click to change type
$scope.populate = function(type) {
$scope.attributeValue = type;
};
};
init();
};
SamplesController.$inject = ['$scope','SamplesFactory', 'appSettings', '$routeParams'];
angular.module('myApp').controller('SamplesController', SamplesController);
}());
samplesFactory.js
(function () {
var SamplesFactory = function ($http) {
var factory = {};
factory.getSamples = function() {
return $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK');
};
return factory;
};
SamplesFactory.$inject = ['$http'];
angular.module('myApp').factory('SamplesFactory', SamplesFactory);
}());
So with this - every time a new view is loaded the ajax request is made again - how would I re-purpose to have only a single request happen?
As always thanks in advance
Carl
UPDATE: Answer marked below but I also had success by changing the "cache" config item/property (whatever its called) to true in the jsonp request
return $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK',{cache: true});
You could change your factory in this way:
(function () {
var SamplesFactory = function ($http) {
var factory = {},
samples = $http.jsonp('http://www.website.com/app/index.php?callback=JSON_CALLBACK');
factory.getSamples = function() {
return samples;
};
return factory;
};
SamplesFactory.$inject = ['$http'];
angular.module('myApp').factory('SamplesFactory', SamplesFactory);
}());
Now getSamples() returns a promise that you should manage in your controllers.

How to pass data to an angular-foundation modal $scope?

I am using angular-foundation and specifically the modal http://madmimi.github.io/angular-foundation/#/modal , i am confused in how to pass data to a modal while using one controller , i want to take an array value and update the modal to show a particular user info ,Ex: $scope.updateUserInfo = $scope.user[index] , the only issue is how to pass the data to the modal .
myApp.controller('users',function ($scope,$location,$http,$modal,msg) {
$http.get('api/v1/users')
.success(function (data,status) {
$scope.user = data;
})
.error(function (data,status) {
$location.path('/login');
});
$scope.showWrite = function () {
$scope.write = true;
}
$scope.closeWrite = function () {
$scope.write = false;
$scope.newUser = '';
}
$scope.save = function () {
$http.post('api/v1/users/store',$scope.newUser)
.success(function (data,status) {
$scope.user.unshift({
id: data,
first_name: $scope.newUser.first_name,
last_name: $scope.newUser.last_name,
email: $scope.newUser.email,
role: $scope.newUser.role
});
$scope.write = false;
$scope.newUser = '';
})
.error(function (data,status) {
alert('failed');
});
}
$scope.confirmDelete = function (index,id) {
msg.confirmDelete().then(function(value) {
$scope.text = msg.getText();
$http.get('api/v1/users/destroy/'+id)
.success(function (data,status) {
$scope.user.splice(index,1);
})
.error(function (data,status) {
alert('Error : Operation failed');
});
});
}
$scope.showUserInfo = function () {
}
$scope.userUpdate = function () {
}
$scope.showUserUpdate = function (index) {
$modal.open({
templateUrl: 'partials/message/update.html',
controller: 'users'
});
}
});
To Pass the data to $modal you need to update your $modal function something like this:
$scope.showUserUpdate = function (popUpData) {
var modalInstance = $modal.open({
templateUrl: 'partials/message/update.html',
controller: ['$scope', '$rootScope', '$modalInstance',
function($scope, $rootScope, $modalInstance) {
$scope = angular.extend($scope, popUpData);
}],
resolve: {}
});
return modalInstance;
};
So popupData is the data which you want to pass to your modal. popupdata then will be merged with existing scope of that controller. Now you can access popupData keys in your HTML. Remember we are returning modal instance in this function so you can manually close the popup using this intance.
Other way is to use the resolve attribute and inject it to controller:
$scope.showUserUpdate = function (popUpData) {
var modalInstance = $modal.open({
templateUrl: 'partials/message/update.html',
controller: ['$modalInstance', 'data', function($modalInstance, data) {
data.popUpData = ...
}],
resolve: {
data: popUpData
}
});
return modalInstance;
};

Resources