What I want
I have 3 views. All views have their own controller. The first view is home. From the home view the user goes to view 2 by clicking on a div element. On view 2 the user opens view 3 by clicking on a button. On view 3 the user clicks on a button and goes back to view 2.
The problem I am facing is that I want to know if the user came from view 2. In that case I want to show different content then when the user came from the home view.
Create another view
Can it be done without creating another view?
My try
I created a service that keeps track of if I came from view 3, but the controller of view 2 isn't executed anymore after view 2 got opened from the home view. So basicly $scope.fromViewThree isn't updated and still false.
I added $route.reload() before $window.location = "#/app/viewtwo"; because I thought it would reinitialise the controllers(source) and then $scope.fromViewThree should have been updated. I also tried adding it below $window.location = "#/app/viewtwo";.
Controller view 2
.controller('ViewTwoCtrl', function($scope, $window, fromViewThree) {
$scope.fromViewThree = fromViewThree.getBoolean();
fromViewThree.setBoolean(false);
$scope.goToViewThree = function() {
$window.location = "#/app/viewthree";
};
})
Controller view 3
.controller('ViewThreeCtrl', function($scope, $window, fromViewThree) {
$scope.goToViewTwo = function() {
fromViewThree.setBoolean(true);
$window.location = "#/app/viewtwo";
};
})
Directives.js
.service('fromViewThree', function () {
var b = false;
return {
getBoolean: function() {
return b;
},
setBoolean: function(value) {
b = value;
}
};
})
HTML view 2
<div ng-if="fromViewThree == false">
<p>You came from view home!</p>
</div>
<div ng-if="fromViewThree == true">
<p>You came from view three!</p>
</div>
<div>
<button ng-click="goToViewThree()" ng-if="fromViewThree == false">Go to view 3</button>
<button ng-click="goToViewThree()" ng-if="fromViewThree == true">Go again to view 3</button>
</div>
HTML view 3
<div class="row">
<button class='button' ng-click="goToViewTwo()">Lets go to view two!</button>
</div>
Try implementing the views and controllers using the UI router. Once you have the states setup, accessing the previous state will be easy Angular - ui-router get previous state
Solution
Manu Antony pushed me in the right direction towards $rootScope(more info). I added code in app.js which stores the previous state/view name into $rootScope.fromViewThree. I can now access the previous state/view name in the HTML of view 2 and the previous state/view name will be updated when switching state/view has been successful.
Warning:
All scopes inherit from $rootScope, if you have a variable
$rootScope.data and someone forgets that data is already defined
and creates $scope.data in a local scope you will run into problems.
Controller view 2
.controller('ViewTwoCtrl', function($scope, $window) {
$scope.goToViewThree = function() {
$window.location = "#/app/viewthree";
};
})
Controller view 3
.controller('ViewThreeCtrl', function($scope, $window) {
$scope.goToViewTwo = function() {
$window.location = "#/app/viewtwo";
};
})
Directives.js
I no longer need the service because I use $rootScope now to monitor which view is my previous view.
HTML view 2
<div class="row" ng-if="fromViewThree != 'app.viewthree'">
<p>You came from view home!</p>
</div>
<div class='row' ng-if="fromViewThree == 'app.viewthree'">
<p>You came from view three!</p>
</div>
<div class="row">
<button id="activateCameraBtn" class='button' ng-click="goToViewThree()" ng-if="fromViewThree != 'app.viewthree'">Go to view 3</button>
<button id="activateCameraBtn" class='button' ng-click="goToViewThree()" ng-if="fromViewThree == 'app.viewthree'">Go again to view 3</button>
</div>
HTML view 3
The HTML for view 3 hasn't changed.
App.js
angular.module('myApp', [])
.run(function($rootScope) {
$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
$rootScope.fromViewThree = from.name; //e.g. app.home
});
})
....
Related
Say I have several forms each with their own controller that look like this
<div ng-controller="MyController1 as ctrl">
<label>Some label </label>
</div>
<div ng-controller="MyController2 as ctrl">
<label>Some label </label>
</div>
And I have a global controller, that gets the information about the form names. Now I want to find the controllers for each form. For instance, if in my global controller function, I get the name of the first form, how can I find out that its controller is MyController1? Is that even possible?
Calling a controller from another controller is possible. But, I believe the problem you're trying to solve is somewhere else: the architecture of your app.
Ideally, your app should 'react' to changes on the state. The state should be kept in a single place (also called 'single source of truth'), ie. a service. Then you share that service state with as many controllers as you need.
You can either update the service state directly from the controller, or by calling a method on the service itself.
Look at the example below. I hope that sheds some light.
Cheers!
angular.module('app', [])
.service('MyService', function(){
var self = this;
self.state = {
name: 'John'
};
self.changeNameFromService = function() {
self.state.name = 'Peter';
}
})
.controller('Ctrl1', function($scope, MyService){
$scope.state = MyService.state;
$scope.changeName = function(){
// update the state of the scope, which is shared with other controllers by storing the state in a service
$scope.state.name = 'Mary';
}
})
.controller('Ctrl2', function($scope, MyService){
$scope.state = MyService.state;
// call a method defined in service
$scope.changeName = MyService.changeNameFromService;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="Ctrl1">
Ctrl1: {{state.name}}
<button ng-click="changeName()">Change name!</button>
</div>
<div ng-controller="Ctrl2">
Ctrl2: {{state.name}}
<button ng-click="changeName()">Change name from service!</button>
</div>
</div>
I am having an issue displaying response data, returned from my factory,inside an ionic view page. I believe the issue has something to do with the way I am handling the promise, but I cannot pinpoint why. So to start the roadmap to my problem, here is my:
Factory
.factory('profileFactory', function ($http, $q) {
var config = {
headers: {
'MyKey': 'myvalue'
}
};
this.getEmployee = function (userId) {
return $http.get('http://someurlpathtogoto' + userId + 'etc', config).then(function (response) {
console.log(response.data)
return response.data;
})
}
return this;
})
The above code returns the JSON object I need for my controller so:
Controller
.controller('EmployeeCtrl', ['$scope', 'profileFactory', function ($scope, profileFactory) {
//Set back to false
$scope.profileLoaded = true;
$scope.serviceUnavailable = false;
$scope.setId = function (userId) {
profileFactory.getEmployee(userId).then(function(arrItems){
$scope.employee = arrItems;
$scope.firstName = $scope.employee.record[0].First_Name;
$scope.lastName = $scope.employee.record[0].Last_Name;
};
}])
Now when my page displays I want it to look like such:
Ionic View (Profile)
<ion-view view-title="Profile" ng-controller="EmployeeCtrl" hide-nav-bar="true">
<ion-pane>
<!-- TODO: BACK BUTTON, NO TITLE -->
<ion-header-bar class="bar-stable">
<h1 class="title">Ionic Blank Starter</h1>
</ion-header-bar>
<ion-content>
<div ng-show="serviceUnavailable">
Directory service unavailable
</div>
<div ng-show="profileLoaded">
<br />
<center><img class="focus-img-circle" ng-src="default.png" /></center>
<center><b>{{firstName}} {{lastName}}</b></center>
</div>
</ion-content>
</ion-pane>
<ion-view>
All of this is invoked whenever a user presses on an arrow button in the app which should take them to their profile page to view some info. The following code appears in the previous view.
Ionic View (Search Results)
<ion-item class="item item-avatar item-button-right" ng-repeat="user in results" ng-controller="EmployeeCtrl">
<img ng-src="data:image/png;base64,{{user.pic}}">
<strong>{{user.first_name}} {{user.last_name}}</strong>
<div class="buttons" style="padding:20px;">
<button type="button" class="button button-icon ion-ios-telephone-outline customSearchIcon" ng-click=""></button>
<a class="button button-icon ion-ios-arrow-right customSearchIcon" ng-click="setId(user.id)" href="#/tab/search/profile"></a>
<button type="button" class="button button-icon ion-ios-heart-outline customSearchIcon" ng-click="addFavorite(user.id, user.first_name, user.last_name, user.pic)"></button>
</div>
</ion-item>
So essentially a user clicks on a persons tab, takes them to that profile where my factory is used to make a RESTful call to return a JSON object containing data about that user. Except nothing is being displayed in the Ionic View. If I hard code a userId in the controller and take away the function setId() it works but that obviously isn't an option. Does anyone see anything specifically that I am doing wrong? Been stuck on this for hours any help would be great.
I left a lot of code out to get my point across.
EDIT
The two views you see are different ionic templates. So using ng-controller="EmployeeCtrl is not happening twice in the same view.
The mistake is, on arrow click, you call the service and store the data to scope variable and then navigates to second view. The second view though uses the same controller as the first view, a new controller is being instantiated with empty scope - and hence your view is empty.
Instead of ng-click in <a> tag, modify your profile route to pass the userid as well - tab/search/profile/:userid and in anchor tag have ng-href= "#/tab/search/profile/{{user.id})" and in your profile controller get the userid from query string ($location.search().userid) and make the Ajax call.
I'm working on a small app in AngularJS. My project contain a Body.html file that contain 3 views: SideMenu, Header and Content, each with each own Controller and a MainController as there parent - the controller of the Body.html.
Can the header's controller change a property in the side-menu - the open/close status of the side-menu.
And Can the side-menu controller change a property in the header - the header's text.
I can use the main controller, since both of the header's controller and the side-menu controller can reference the main controller. But the data won't be consist. Updating the data from the 1st controller wan't effect the data in the 2nd controller (without the use of $watch).
Can both the side-menu's controller and the header's controller (sibling controllers) communicate with each other? without the help of there parent?
Body.html
<div>
<!-- Header placeholder -->
<div ui-view="header"></div>
<!-- SideMenu placeholder -->
<div ui-view="sideMenu"></div>
<!-- Content placeholder -->
<div ui-view></div>
</div>
Header.html
<div>
{{ headerCtrl.text }}
</div>
<div ng-click="headerCtrl.openSideMenu()">
--Open--
</div>
HeaderController.js
// sideMenuCtrl = ???
headerCtrl.text = "Title";
headerCtrl.openSideMenu = function()
{
sideMenuCtrl.isSideMenuOpen = true;
};
SideMenu.html
<div ng-class={ 'side-menu-open': sideMenuCtrl.isSideMenuOpen }>
<div ng-repeat="menuItem in sideMenuCtrl.sideMenuItems"
ng-click="sideMenuCtrl.selectMenuItem(menuItem)">
{{ menuItem.text }}
</div>
</div>
SideMenuController.js
// headerCtrl = ???
sideMenuCtrl.selectMenuItem = function(menuItem)
{
headerCtrl.text = menuItem.text;
}
As stated in my comment, you can use an AngularJS service to share some data between your controllers.
app.service('AppContextService', function(){
this.context = {
isSideMenuOpen: false
};
});
app.controller('SideMenuCtrl', ['$scope', 'AppContextService', function($scope, AppContextService) {
// exposing the application context object to the controller.
$scope.appContext = AppContextService.context;
}]);
app.controller('HeaderCtrl', ['$scope', 'AppContextService', function($scope, AppContextService) {
$scope.appContext = AppContextService.context;
$scope.openSideMenu = function()
{
$scope.appContext.isSideMenuOpen = true;
};
}]);
Then adapt the HTML to use your shared appContext object.
<div ng-class={ 'side-menu-open': appContext.isSideMenuOpen }>
[...]
</div>
Here is a working fiddle that illustrates the issue: fiddle
This answer covers the use of a service to fit your needs but I am sure that there are other (and perhaps better) ways to tackle the problem which might involve other Angular feature, or even some overall application refactoring.
To dig a little deeper, this SO topic might be a good start: difference between service, factory and providers
I am new to Angular and need your help on an issue with the ng-repeat of my app.
Issue:
I have an html page (event.html) and in the corresponding controller of the file, I make a request to a firebase collection and update an array ($scope.events). The issue is that the data from firebase takes a few seconds to load and by the time data arrives to $scope.events, ng-repeat has already been executed and it displays an empty screen. The items are displayed correctly the moment I hit on a button in the HTML page (event.html).
Sequence of events:
I have a login page (login.html) where I enter a user name and phone number and I click on the register button. I've configured this click on the register button to go to the new state (event.html).
Here is the controller code for login.html:
$scope.register = function (user) {
$scope.user = user.name;
$scope.phonenumber = user.phonenumber;
var myuser = users.child($scope.user);
myuser.set({
phone : $scope.phonenumber,
Eventid : " ",
name : $scope.user
})
var userid = myuser.key();
console.log('id is ' +userid);
$state.go('event');
}
The controller of event.html (the state: event) has the following code:
var ref = new Firebase("https://glowing-torch-9862.firebaseio.com/Users/Anson/Eventid/");
var eventref = new Firebase("https://glowing-torch-9862.firebaseio.com/Events");
var myevent = " ";
$scope.events = [];
$scope.displayEvent = function (Eventid) {
UserData.eventDescription(Eventid)
//UserData.getDesc()
$state.go('myevents');
//console.log(Eventid);
};
function listEvent(myevents) {
$scope.events.push(myevents);
console.log("pushed to array");
console.log($scope.events);
};
function updateEvents(myevents) {
EventService.getEvent(myevents);
//console.log("success");
};
ref.once('value', function (snapshot) {
snapshot.forEach(function (childSnapshot) {
$scope.id = childSnapshot.val();
angular.forEach($scope.id, function(key) {
eventref.orderByChild("Eventid").equalTo(key).on("child_added", function(snapshot) {
myevents = snapshot.val();
console.log(myevents) // testing 26 Feb
listEvent(myevents);
updateEvents(myevents);
});
});
});
});
$scope.createEvent = function () {
$state.go('list');
}
event.html contains the following code:
<ion-view view-title="Events">
<ion-nav-buttons side="primary">
<button class="button" ng-click="createEvent()">Create Event</button>
<button class="button" ng-click="showEvent()">Show Event</button>
</ion-nav-buttons>
<ion-content class="has-header padding">
<div class="list">
<ion-item align="center" >
<button class= "button button-block button-light" ng-repeat="event in events" ng-click="displayEvent(event.Eventid)"/>
{{event.Description}}
</ion-item>
</div>
</ion-content>
</ion-view>
The button showEvent is a dummy button that I added to the HTML file to test ng-repeat. I can see in the console that the data takes about 2 secs to download from firebase and if I click on the 'Show Events' button after the data is loaded, ng-repeat works as expected. It appears to me that when ng-repeat operates on the array $scope.events, the data is not retrieved from firebase and hence its empty and therefore, it does not have any data to render to the HTML file. ng-repeat works as expected when I click the dummy button ('Show Event') because a digest cycle is triggerred on that click. My apologies for this lengthy post and would be really thankful if any of you could give me a direction to overcome this issue. I've been hunting in the internet and in stackoverflow and came across a number of blogs&threads which gives me an idea of what the issue is but I am not able to make my code work.
Once you update your events array call $scope.$apply(); or execute the code that changes the events array as a callback of the $scope.$apply function
$scope.$apply(function(){
$scope.events.push(<enter_your_new_events_name>);
})
If you are working outside of controller scope, like in services, directive, or any external JS. You will need to trigger digest cycle after change in data.
You can trigger digest cycle by
$scope.$digest(); or using $scope.$apply();
I hope it will be help you.
thanks
In your case you have to delay the binding time. Use $timeout function or ng-options with debounce property in your view.
you have to set a rough time taken to get the data from the rest API call. By using any one of the methods below will resolve your issue.
Method 1:
var myapp = angular.module("myapp", []);
myapp.controller("DIController", function($scope, $timeout){
$scope.callAtTimeout = function() {
console.log("$scope.callAtTimeout - Timeout occurred");
}
$timeout( function(){ $scope.callAtTimeout(); }, 3000);
});
Method 2:
// in your view
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ debounce: 1000 }" />
I am trying to pop up a modal to get some input, but the angular binding via ng-model seems to be read only. This is my modal markup:
<script type="text/ng-template" id="signatureWindow.html">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">Signature Capture</h4>
</div>
<input type="text" width="100px" ng-model="theName" />
<div class="modal-footer">
<button ng-click="accept()" class="btn btn-primary">Accept</button>
<button ng-click="cancel()" class="btn btn-default">Cancel</button>
</div>
</script>
Then, I invoke this modal as follows:
$scope.getSignatureModal = function(signatureBase64) {
var modalInstance = $modal.open({
templateUrl: 'signatureWindow.html',
controller: 'SignatureModalController',
size: 'lg',
resolve: {
signatureData: function() {
return signatureBase64;
}
}
});
modalInstance.result.then(function(signatureData) {
alert('Signed');
signatureBase64 = signatureData;
}, function() {
alert('Canceled');
});
};
And the following controller code for the modal:
MlamAngularApp.controller('SignatureModalController', function($scope, $modalInstance, signatureData) {
$scope.base64 = signatureData;
$scope.thename = "NamesRus";
$scope.accept = function() {
debugger;
$modalInstance.close($scope.thename);
}
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
}
});
The modal pops up as expected, and the input has the initial value "NamesRus", but when I close the modal, invoking accept in the modal controller, $scope.thename still has it's initial value, not any new value I type when the modal is active. What could be wrong here?
NOTE: At the debugger breakpoint in accept, no matter what I type in the modal's input, theName still has the initial assigned value.
MORE: My Plunker for this works fine. It's when in-place, in an ASP.NET MVC5 project, that I get the strange behaviour.
I think that you mix up two differents scopes.
If you want several variables to be passed to and retrieved from the modal you have to mention them:
in the resolve attribute
resolve: {modalData: function (){return {signatureData:$scope.base64,name:$scope.theName}}}
pass modalData as dependencie to your controller
MlamAngularApp.controller('SignatureModalController', function($scope, $modalInstance, modalData)
update the modal controller $scope with modalData
$scope.signatureData = modalData.signatureData;
$scope.name=modalData.name;
invoke
$modalInstance.close({signatureData:$scope.base64,name:$scope.theName})
reassign the original $scope with the result of promise
modalInstance.result.then(function (data) {
$scope.base64 = data.signatureData;
$scope.thename=data.name;
}
take a look at this plunker forked from ui-boostrap modal orginal example: http://plnkr.co/edit/whLSYt?p=info