Angular JS - Change path, Same controller, don't reload scope - angularjs

I know it's been asked so many times here and I found it too. But it could not solve my problem.
Here is the case. I have one AngularJS application.
I have a list page. I have a button to add. When I click on add button, a pop-up window will come with a form. I want to change the URL when the pop-up comes but in the same controller.
Also I would like to add some other buttons on each, some html display as popup-or other location, but same controller without reloading all scope when url changes.
What I have tried.
app.js
var WebClientApp = angular.module('WebClientApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ui.bootstrap',
'ngRoute'
]);
WebClientApp.config(function ($routeProvider) {
$routeProvider
.when('/groups/:template', {
templateUrl: 'groups.html',
controller: 'GroupCtrl'
}
groups.html
<div>
<button ng-click="showAdd()">Add Group</button>
<div ng-include src="views/listpage.html">
<div ng-if="addGroupModal" class="popup-modal">
<form name="addGroup" ng-submit="addandEditGroup()">
<input type="text" ng-model="group.name">
</form>
<div>
<div ng-if="editGroupModal" class="popup-modal">
<form name="editGroup" ng-submit="saveGroup()">
<input type="text" ng-model="group.name">
<input type="text" ng-model="group.desc">
<input type="text" ng-model="group.id">
</form>
<div>
Controllers.js
WebClientApp.controller('GroupCtrl', function ($scope,$http, $location, $routeParams) {
$scope.group = {};
$scope.showAdd=function(){
$location.path('/groups/add');
}
var template = $routeParams.template;
switch(template){
case 'add':
loadAddPage();
break;
case 'edit':
loadEditPage();
break;
default:
loadListPageHideAll();
break;
}
function loadAddPage() {
$scope.addGroupModal=true;
$scope.editGroupModal=false;
}
function loadEditPage(){
$scope.addGroupModal=false;
$scope.editGroupModal=true;
}
function loadListPageHideAll() {
$scope.addGroupModal=false;
$scope.editGroupModal=false;
// connect to server and list all groups
}
$scope.addandEditGroup = function() {
$location.path('/groups/edit');
}
$scope.saveGroup = function() {
// Save group with $scope.group.
$location.path('/groups');
}
});
When I click on add button, it will show the add form. When I enter group name, and submit, it should show edit form after changing url with the group name filled in the form. But when I try, the value of group object becomes empty since the url is changing. I added the following in controller, but don't know what to do exactly after.
$scope.$on('$routeChangeStart', function(event, present, last) {
console.log(event,present,last);
});
How to assign the scope variables of last route to present route scope. I tried reload on search to false also, But it didnt work.

There might be an error here :
function loadEditPage(){
$scope.addGroupModal=false;
$scope.editGroupModal=true;
}
There are probably some typos in the HTML template code you posted, but what you are basically doing is hiding the parent addGroupModal and showing the child editGroupModal. Remove the nesting and the tags like this:
<div ng-if="addGroupModal" class="popup-modal">
<form name="addGroup" ng-submit="addandEditGroup()">
<input type="text" ng-model="group.name">
</form>
</div>
<div>
<div ng-if="editGroupModal" class="popup-modal">
<form name="editGroup" ng-submit="saveGroup()">
<input type="text" ng-model="group.name">
<input type="text" ng-model="group.desc">
<input type="text" ng-model="group.id">
</form>
</div>
</div>
Here is the plunkr ( hit enter to submit the form): http://plnkr.co/edit/MGT8HZ4lpgVWCkWlt8Ak?p=preview
If this is what you want to acheive, honestly you are complicating things ... There are simpler solutions.
I see! What you want to acheive is to have a reference of the old group variable before the route was changed... And you want to do that using the same controller...
Ok, to get the group from the last controller, you are half way there . You have to store the group somewhere because the targetScopes and currentScopes you receive in the $routeChange listeners don't point to the scopes.
http://plnkr.co/edit/HfK3fhVtZ4bxHtpiFR3B?p=preview
$scope.group = $rootScope.group || {};
$scope.$on('$routeChangeStart', function(event, present, last) {
console.log('start change route');
$rootScope.group = event.currentScope.group;
console.log('target scope group ',event.currentScope.group);
});
I agree the rootScope might not be the best place to keep that variable, but you can also put it inside an angular constant of variable.

Related

Can't access form from inside AngularJS controller

I am trying to manually reset a form from inside an AngularJS controller but I can't access it using either $scope, or using controllerAs. When I log $scope.SupportForm, it returns undefined.
HTML
<form name="supportForm" id="supportForm" novalidate>
<label for="message">Message</label>
<textarea name="message" id="message" model="$ctrl.formData.message" maxlength="5000" required></textarea>
<button type="submit" data-ng-click="$ctrl.submitForm($ctrl.formData)" data-ng-disabled="supportForm.$invalid">
Request support
</button>
</form>
Contoller
function GeneralSupportController($scope, $state, $timeout, $stateParams, SupportService, $uibModal) {
var vm = this;
vm.formData = {};
vm.submitForm = submitForm;
function submitForm(data) {
console.log('$scope.supportForm : ', $scope.supportForm)
}
}
I have also tried adding ngModel to the form, but it also doesn't work.
Question
Any idea why the form isn't being assigned to the scope?
Form is assigned to scope in your code. (https://plnkr.co/edit/7eYvApaW36DrRmvK >> it works) I guess actually you have following:
<div ng-if="...">
<form name=...
In this case form is assigned to nested scope of ng-if not controller scope. You have several solutions:
pass form to submit function $ctrl.submitForm(supportForm... useful when u have several forms
put form into controller <form name="$ctrl.supportForm" do it when u have one form

angular scope initialization sometimes empty

I'm working with angular 1.5 and a python/mongodb api.
The api is working fine (consistent and fast), but sometimes when I load a page with a form the data fields are empty.
The app uses ui.router to associate the chap state with the correct url and the chapController. I type that url into the address bar and hit return to load the page. In the controller, the path/to/api returns json data.
Here's a simplified version of the controller:
angular.module('configurer')
.controller('chapController', function($scope, $http, $state) {
var url='path/to/api';
$http.get(url).success(function(data){
$scope.data = data;
});
$scope.save = function()( {
$http.post(url, $scope.data).success(function() {
$state.go('home', {reload:true});
});
});
The view looks like this:
<button class="btn btn-primary" ng-click="save()">Save Changes</button>
<form>
<div class="row">
<div class="col-sm-3 form-group">
<label for="name">Name</label>
<input class="form-control" id="name" type="text" name="name" ng-model="data.chap.name" />
</div>
<div class="col-sm-9 form-group">
<label for="title">Title</label>
<input class="form-control" id="title" type="text" title="title" ng-model="data.chap.title" />
</div>
</div>
</form>
Usually the form comes up populated with data but sometimes the fields are blank (using the same url).
I can reload the page from the browser and then it will populate, but of course that's not a good user experience.
What am I doing wrong?
After research plus trial-and-error, I think I've got the answer. The reason the scope wasn't binding to the data is that there was no data: my server was sending a 304 "not modified" response.
So of course it couldn't bind with non-existent data but the http response was still counted as success. Seems like this would be a common 'gotcha'.
What worked for me is to add a config object to each http.get call, like this
http.get(url, {cache:true}).success(function(data) { etc...
My guess is that you could also set max-age and public on the http request headers so the server cannot respond with a 304. That didn't seem like the most efficient thing to do, so I went with using cache on the client so the server isn't even bothered.
This is working for me, but if problems pop up again, I'll repost.
Since you're using ui.router, I recommend using resolve for this.
Example:
$stateProvider
.state('chap', {
// ...
resolve: {
data: function($http){
return $http.get(...);
}
}
});
and in your controller:
angular
.module('configurer')
.controller('chapController', function(data, $scope, $http, $state) {
$scope.data = data;
}
Sometimes data won't bind with the scope. can you check by including this line
$scope.data = data;
$scope.$apply();
Inside success handler.
I am not sure, it may work. please try

How to pass an array from one view to another within same controller

I am just a newbie to angularjs. I have a form in one view of my application which shows on my other view. There is only one controller for the application. The user can enter the fields in the form which should get displayed on the other view.
The code looks like this.
var app2 = angular.module('myApp2', ['ngRoute','ngStorage']);
app2.controller('rtCtrl', function($scope,$localStorage,$rootScope){
$scope.names = [
{name:'Jani',email:'jani#gmail.com'},
{name:'Hege',email:'hege#gmail.com'},
{name:'Kai',email:'kai#gmail.com'}
];
$rootScope.namesfinal = $scope.names;
$scope.saveData = function(){
$scope.names.push({name: $scope.username, email: $scope.emailaddress});
$localStorage.localData = $scope.names;
$rootScope.namesfinal = $localStorage.localData;
console.log($rootScope.namesfinal);
};
}
);
app2.config(['$routeProvider',function($routeProvider) {
$routeProvider.when('/page2', {
templateUrl: 'Home2.html',
controller: 'rtCtrl'
}).when('/page3', {
templateUrl: 'Home3.html',
controller: 'rtCtrl'
}).otherwise({
redirectTo: '/'
});
}]);
<div ng-app="myApp2" ng-controller="rtCtrl">
<!--a href="#page2">CLick here for page 2</a-->
<button ng-click="traverse()">Page2</button><br>
CLick here for page 3
<div ng-view></div>
</div>
//The second page comes here
<div>This is the second page<br>
<!--<button ng-click="locstor()">Click Here</button>-->
<span ng-repeat="x in namesfinal">
Name: <span ng-bind="x.name"></span> Email: <span ng-bind="x.email"></span><br>
</span>
</div>
//the form page comes here
<div>This is the third page
<form name="form1" novalidate>
Name: <input type="text" name="username" ng-model="username" required>
<span ng-show="form1.username.$pristine">Enter email here.</span>
<span style="color:red;" ng-show="form1.username.$dirty && form1.username.$invalid && form1.username.$error.required">
User name cannot be left empty.</span><br>
Email: <input type="email" name="emailaddress" ng-model="emailaddress" required>
<span style="color:red;" ng-show="form1.emailaddress.$error.email">Email is not valid.</span><br>
<!-- Password: <input type="password" ng-model="userpassword" required><span ng-show="">Password should be at least 8 characters long</span>-->
<input type="submit" value="Submit" ng-disabled="form1.username.$pristine || form1.emailaddress.$pristine || form1.username.$dirty && form1.username.$invalid || form1.emailaddress.$dirty && form1.emailaddress.$invalid"
ng-click="saveData()">
</form>
</div>
Can you tell me why is the array not getting updated on the view of the second page after clicking on the submit button.
Need to understand that although you are using the same controller ... each view will run a new instance of that controller and the scope from previous path (controller) is destroyed once view changes.
You need to use a service to share data across the application
You have two "instances" of your controller, one for each view. If you wan't to share data between those you can use services/factories. Angular guarantees that only one instance will exist of a given service during the app's lifecycle and this makes it an ideal place to share data.
Other approach is to use the rootScope. It looks easier but this solution can easily lead to a polluted rootScope. In general it is better to separate your code so it will be easier to write nice unit tests and maintain the code.
You use 1 controller for two separate pages. The problem is - when you go to another page your current controller will be destroyed and that next page's controller will be created. This will happen even if you use 1 controller function for 2 different pages. You can see it if you place debugger in your controller code. So you can't use controller to save state when moving to another page.
You can use factory for this. factories (and services and providers) are singletons in angular. So they won't be recreated when you go to another page.
tl;dr : create a factory to save your data when you move to another page

Angular directive with custom / conditional actions

I have questions about Angular directives. The following is my code:
main controller & the directive:
<div ng-controller='ShopsController'>
<update-createform shop="shop" action='update()'></update-createform>
</div>
directive js:
(this way the direction action will take the 'action' input argument)
angular.module('app')
.directive('updateCreateform', function(){
return {
templateUrl: '/form.html',
restrict : 'E',
scope: {
shop: '=',
action: '&'
}
}
})
form.html template:
<form name="shopForm" ng-submit='action(shopForm.$valid)' novalidate>
<input type='text' name='name' required/>
<input type='text' name='description' required/>
</form>
ShopsController has a method:
exports.update = function(isValid) {
if (isValid) { /* update the shop*/ }
}
What I am doing is I am passing the shop data I get from the server, send it into the form so I can view and/or update the shop info.
It's also that I want to create shop info using the same form. In this case I just send in shop = [] and action='create()' instead.
My controller has an update method that takes the argument isValid. I don't know how to pass the directive shopForm.$valid outside and send it to server.
Two questions:
how do I get isValid variable from the directive?
Following Ari Lerner's ng-book: He said it's possible to do the following:
http://www.scribd.com/doc/215682987/NG-Book-The-Complete-Book-on-AngularJS-2013
instead of using directive above we use
<update-createform shop="shop" on-update='update()' on-create='create()'></update-createform>
and the directive 'action' will change to 'update' when shop is not empty otherwise action equals to 'create'? I tried his code but I cannot get it to work..
Any help would be greatly appreciated!
You can add an argument to action=update(isValid). This then gets resolved on the form submit.
So your html would look like this
<div ng-controller='ShopsController as shopCtrl'>
<update-createform shop="shop" action='shopCtrl.update(isValid)'></update-createform>
</div>
And your form would look like like this
<form name="shopForm" ng-submit='action({isValid:shopForm.$valid})' novalidate>
<input type='text' name='name' required/>
<input type='text' name='description' required/>
<button type="submit">Submit</button>
</form>
and controller would be
.controller('ShopsController', function() {
var exports = this;
exports.update = function(isValid) {
console.log(isValid)
if (isValid) { /* update the shop*/ }
}
})
http://plnkr.co/edit/Qh3HzKGnOo1NTP9Pfsmh?p=preview
OR
There's another way, although personally i find the syntax a little odd. Not that the first solution feels that intuitive either.
http://plnkr.co/edit/CRN9ruRekJiozJIBTe80?p=preview
Found that one in an excellent post about directives by Dan Wahlin
http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

From AngularJS how to pass data from a controller+template to another controller+template?

I'm building an application with AngularJS and Slim PHP framework for the backend, now I completed my 1st form and created a route to the template for it. Now my problem arise when I want to pass my data to another controller, I need to pass the data to another controller+view(template), I don't want to pollute my first view neither the controller of it and so I really want/need to pass the data to another controller which I could play with my data and another form (the 2nd form is for calculation and other stuff)...So you can call the first controller a pre-save, while the real data save (backend to DB) will only happen in the second controller+template. Here is a short of my 1st template view that has the form:
<form novalidate id="formAdd" name="formAdd" class="form-horizontal well col-md-7 col-md-pull-5" method="post">
<fieldset>
<legend>Transaction form</legend>
<div class="form-group">
<label for="symbol" class="col-sm-4 control-label">Symbol</label>
<div class="col-sm-5 symbols">
<input type="text" name="symbol" class="form-control" ng-model="trsn.symbol" placeholder="symbol" required />
</div>
</div>
<div class="form-group">
<label for="accnt_id" class="col-sm-4 control-label">Account</label>
<div class="col-sm-5">
<select id="accnt_id" name="accnt_id" ng-model="trsn.accnt_id" class="form-control" required>
<option value="">...</option>
<option ng-repeat="account in trsn.accounts" value="{{account.accnt_id}}">{{account.accnt_name}}</option>
</select>
</div>
</div>
<!-- ....etc, etc.... -->
<div class="form-actions col-sm-8 col-sm-offset-4">
<button type="submit" name="save_btn" class="btn btn-primary" ng-disabled="formAdd.$invalid" ng-click="preSaveTrsn(trsn, formAdd)">Save transaction</button>
<button type="reset" class="btn btn-default">Cancel</button>
</div>
</fieldset>
</form>
then the app with the module and routes:
var investingApp = angular.module('investingApp', ['ngSanitize','ngResource', 'ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/new-trsn',
{
templateUrl: 'templates/StockTransaction.html',
controller: 'StockTransactionController'
});
$routeProvider.when('/presave-trsn',
{
templateUrl: 'templates/PreSaveTransaction.html',
controller: 'PreSaveTrsnController'
});
});
now inside my first controller is the presave function, which is empty since I don't know what to do with it so that I can send the transaction data to the next controller+view:
investingApp.controller('StockTransactionController',
function TransactionController($scope, $http, $location, $compile, $timeout, transactionDataService, dateFilter) {
// define some default variables
$scope.trsn = {};
$scope.trsn.symbol = "";
...
$scope.preSaveTrsn = function(trsn, formAdd) {
// what to put in here to transfer data to next controller????
};
and then my last controller, I have also nothing in there yet since I can't receive any data....but basically what I want to inject is the transaction data (trsn) which comes from 1st form/controller.
investingApp.controller('PreSaveTrsnController',
function MenuController($scope, $http, trsn) {
console.debug(trsn);
});
Does I have to put something inside the routeProvider somehow? ...or does I have to fill in something special inside the preSaveTrsn function inside my 1st controller??? I'm quite confused with this since all example I find are for saving right away to database, but I can't do that the way I build my app, it really has to be on the second controller for few reasons which I don't think I have to explain here.... Thanks for any help given :)
You may create a lightweight service - value
angular.module('investingApp').value('MySharedValue', {});
And then inject it in both controllers:
TransactionController($scope, $http, $location, $compile, $timeout, transactionDataService, dateFilter, MySharedValue)
And just to assign your shared value to it
$scope.preSaveTrsn = function(trsn, formAdd) {
MySharedValue.trsn = trsn;
};
There are 2 ways to achieve it. First is to declare your model object on $rootScope or on a scope which is parent to both of these controller scope. This way the data gets shared and the changes are available to both controller, irrespective of who makes it.
The second better approach is to create a service which tracks the model update. Inject this service into both the controller. Any controller can ask ask for the model from the service and update it. Since services are singleton, the model changes are shared across controller.
Like
angular.module("myApp",[]).factory('transactionService',[function(){
var service={};
var model={};
service.preSaveTrsn = function(trsn, formAdd) {
//set model here
};
service.getPreSaveTrsn=function() {
return model;
}
return service;
}]);

Resources