AngularJS and UI-Router: keep controller loaded - angularjs

I am building a web application for our customer support. One of the needs is to be able to keep multiple tickets opened at the same time.
I was able to do the first part easily using a tabulation system and UI-Router.
However, with my current implementation, each time I change active tab, the previously-current tab is unloaded, and the now-current tab is loaded (because it was unloaded with a previous tab change).
This is not at all the expected behavior. I've already spent a couple of days trying to find a way to achieve this, without any luck.
The closest thing I was able to do is to use the multiple views system from UI-Router, but I need multiple instance of the same view to keep in memory (if multiple tickets are opened, they all are on the same view, with the same controller, but a different scope)
Here's my current implementation:
supportApp.js:
var app = angular.module("supportApp", ["ui.router", "ui.bootstrap"]);
app.config(function($stateProvider, $urlRouterProvider, $httpProvider){
$urlRouterProvider.otherwise("/");
$stateProvider
.decorator('d', function(state, parent){
state.templateUrl = generateTemplateUrl(state.self.templateUrl);
return state;
})
.state("main", {
abtract: true,
templateUrl: "main.html",
controller: "mainController"
})
.state("main.inbox", {
url: "/",
templateUrl: "inbox.html",
controller: "inboxController"
})
.state('main.viewTicket', {
url: '/ticket/{id:int}',
templateUrl: "viewTicket.html",
controller: "ticketController"
})
;
});
mainController.js: (handles other stuff, minimal code here)
app.controller("mainController", function($rootScope, $http, $scope, $state, $interval){
// Tabs system
$scope.tabs = [
{ heading: "Tickets", route:"main.inbox", active:false, params:{} }
];
var addTabDefault = {
heading: '',
route: null,
active: false,
params: null,
closeable: false
};
$rootScope.addTab = function(options){
if(!options.hasOwnProperty('route') || !options.route)
{
throw "Route is required";
}
var tabAlreadyAdded = false;
for(var i in $scope.tabs)
{
var tab = $scope.tabs[i];
if(tab.route == options.route && angular.equals(tab.params, options.params))
{
tabAlreadyAdded = true;
break;
}
}
if(!tabAlreadyAdded)
{
$scope.tabs.push($.extend({}, addTabDefault, options));
}
if(options.hasOwnProperty('active') && options.active === true)
{
$state.go(options.route, options.hasOwnProperty('params')?options.params:null);
}
};
$scope.removeTab = function($event, tab){
$event.preventDefault();
if($scope.active(tab.route, tab.params))
{
$scope.go($scope.tabs[0].route, $scope.tabs[0].params);
}
$scope.tabs.splice($scope.tabs.indexOf(tab), 1);
};
$scope.go = function(route, params){
$state.go(route, params);
};
$scope.active = function(route, params){
return $state.is(route, params);
};
$scope.$on("$stateChangeSuccess", function() {
$scope.tabs.forEach(function(tab) {
tab.active = $scope.active(tab.route, tab.params);
});
});
});
main.html:
<div class="container-fluid" id="sav-container">
<div class="row-fluid">
<div class="col-lg-2">
<form role="form" id="searchForm" action="#">
<div class="form-group has-feedback">
<input class="form-control" type="search" />
<span class="glyphicon glyphicon-search form-control-feedback"></span>
</div>
</form>
</div>
<div class="col-lg-10" id="support_main_menu">
<ul class="nav nav-tabs">
<li ng-repeat="t in tabs" ng-click="go(t.route, t.params)" ng-class="{active: t.active, closeable: t.closeable}" style="max-width: calc((100% - 128px) / {{tabs.length}});">
<a href class="nav-tab-text">
<button ng-show="t.closeable" ng-click="removeTab($event, t)" class="close" type="button">×</button>
<span>{{t.heading}}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="row-fluid">
<div class="tab-content" ui-view></div>
</div>
</div>
It seems to me that what I ask is pretty standard, but I sadly couldn't find any usefull thing on the Internet

The basic idea is to store state (i.e. list of tickets) in a service as opposed to a controller. Services hang around for the life of the application. There are some articles on this. I'm still developing my approach but here is an example:
var RefereeRepository = function(resource)
{
this.resource = resource; // angular-resource
this.items = []; // cache of items i.e. tickets
this.findAll = function(reload)
{
if (!reload) return this.items;
return this.items = this.resource.findAll(); // Kicks off actual json request
};
this.findx = function(id)
{
return this.resource.find({ id: id }); // actual json query
};
this.find = function(id) // Uses local cache
{
var itemx = {};
// Needs refining
this.items.every(function(item) {
if (item.id !== id) return true;
itemx = item;
return false;
});
return itemx;
};
this.update = function(item)
{
return this.resource.update(item);
};
};
refereeComponent.factory('refereeRepository', ['$resource',
function($resource)
{
var resource =
$resource('/app_dev.php/referees/:id', { id: '#id' }, {
update: {method: 'PUT'},
findAll: {
method: 'GET' ,
isArray:true,
transformResponse: function(data)
{
var items = angular.fromJson(data);
var referees = [];
items.forEach(function(item) {
var referee = new Referee(item); // Convert json to my object
referees.push(referee);
});
return referees;
}
},
find: {
method: 'GET',
transformResponse: function(data)
{
var item = angular.fromJson(data);
return new Referee(item);
}
}
});
var refereeRepository = new RefereeRepository(resource);
// Load items when service is created
refereeRepository.findAll(true);
return refereeRepository;
}]);
So basically we made a refereeRepository service that queries the web server for a list of referees and then caches the result. The controller would then use the cache.
refereeComponent.controller('RefereeListController',
['$scope', 'refereeRepository',
function($scope, refereeRepository)
{
$scope.referees = refereeRepository.findAll();
}
]);

Related

Output image from RESTful Service Angular

I am new to Angular so to get to grips with it I have been working with a Dummy RESTful service. Right now I have managed to pull the image URL and then push it into an array.
I would like to output this array as an image when the "ng-click" directive is fired.
Any guidance or help would be much appreciated.
<p ng-click="outputImageData()">click me</p>
<ul>
<li ng-repeat="photo in photos">
{{ image }}
</li>
</ul>
myApp.factory('getImages', function($http) {
var imageService = {
async: function(id) {
var promise = $http.get('https://jsonplaceholder.typicode.com/photos/1').then(function(response) {
return response.data;
})
return promise;
}
};
return imageService;
});
myApp.controller("outputImages", function($scope, getImages) {
var photos = [];
$scope.outputImageData = function() {
getImages.async().then(function(data) {
var photoId = data.url;
photos.push(photoId);
console.log(photos);
})
}
});
Thanks
I've been using angularjs but generally as a developer, I'm just started so bear with me, please.
I think something like this would work:
<p ng-click="updateImageData()">click me</p>
<ul>
<li ng-repeat="photo in photos">
<img src="{{photo.url}}">
</li>
</ul>
myApp.factory('getImages', function($http) {
var imageService = {
async: function() {
var promise = $http.get('https://jsonplaceholder.typicode.com/photos/');
return promise;
}
};
return imageService;
});
myApp.controller("outputImages", function($scope, getImages) {
$scope.photos = [];
$scope.updateImageData = function() {
getImages.async(photoId).then(function(data) {
$scope.photos = data;
console.log(photos);
})
}
});

Passing data in master detail view in Ionic using this (NOT Scope)?

UPDATE
The service seem to be fetching data but when the data is sent to controller, it is undefined. Adding the service.js file for reference as well
service.js
.service('VideosModel', function ($http, Backand) {
var service = this,
baseUrl = '/1/objects/',
objectName = 'videos/';
function getUrl() {
return Backand.getApiUrl() + baseUrl + objectName;
}
function getUrlForId(id) {
return getUrl() + id;
}
service.all = function () {
console.log($http.get(getUrl()));
return $http.get(getUrl());
};
service.fetch = function (id) {
console.log('Inside s');
console.log($http.get(getUrlForId(id)));
return $http.get(getUrlForId(id));
};
service.create = function (object) {
return $http.post(getUrl(), object);
};
service.update = function (id, object) {
return $http.put(getUrlForId(id), object);
};
service.delete = function (id) {
return $http.delete(getUrlForId(id));
};
})
Pic
I am trying to implement the master-detail view on one of the tabs in my app using this instead of scope (example using scope here). But my details view is not getting the data/ it is saying undefined for detailsCtrl. I believe I'm making a mistake in my controller or app.js but I don't really have an idea about how to fix it.
master.html
<ion-view view-title="Videos">
<div ng-if="!vm.isCreating && !vm.isEditing">
<ion-content class="padding has-header">
<!-- LIST -->
<div class="row gallery">
<div class="list card col col-25" ng-repeat="object in vm.data"
ng-class="{'active':vm.isCurrent(object.id)}">
<a class="cardclick" href="#/details/{{object.id}}">
<div class="item item-image">
<img ng-src="{{object.img}}"/>
</div>
<div class="item item-icon-left assertive">
<i class="icon ion-play"></i>
<p> Watch</p>
<h2> {{object.title}} </h2>
</div>
</a>
</div>
</div>
</ion-content>
</div>
details view or videoplayer.html
<ion-view title="Now Playing" hide-nav-bar="true">
<div class="modal transparent fullscreen-player">
<video src="{{object.src}}" class="centerme" controls="controls" autoplay></video>
</div>
app.js
$stateProvider
// setup an abstract state for the tabs directive
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl as login'
})
.state('forgotpassword', {
url: '/forgot-password',
templateUrl: 'templates/forgot-password.html',
})
.state('tab', {
url: '/tabs',
abstract: true,
templateUrl: 'templates/tabs.html'
})
.state('tab.videos', {
url: '/videos',
views: {
'tab-videos': {
templateUrl: 'templates/tab-videos.html',
controller: 'VideosCtrl as vm'
}
}
})
.state('tab.games', {
url: '/games',
views: {
'tab-games': {
templateUrl: 'templates/tab-games.html'
}
}
})
.state('tab.help', {
url: '/help',
views: {
'tab-help': {
templateUrl: 'templates/tab-help.html'
}
}
})
.state('details', {
url: "/details/:id",
templateUrl: 'templates/videoplayer.html',
controller: 'detailsCtrl as vm'
});
$urlRouterProvider.otherwise('/login');
$httpProvider.interceptors.push('APIInterceptor');
})
controller
.controller('VideosCtrl', function (VideosModel, $rootScope) {
var vm = this;
function goToBackand() {
window.location = 'http://docs.backand.com';
}
function getAll() {
vm.data=[];
VideosModel.all()
.then(function (result) {
vm.data = result.data.data;
console.log(vm.data);
});
}
function initCreateForm() {
vm.newObject = {name: '', description: ''};
}
function setEdited(object) {
vm.edited = angular.copy(object);
vm.isEditing = true;
}
function isCurrent(id) {
return vm.edited !== null && vm.edited.id === id;
}
function cancelEditing() {
vm.edited = null;
vm.isEditing = false;
}
function cancelCreate() {
initCreateForm();
vm.isCreating = false;
}
function clearData(){
vm.data = null;
}
function create(object) {
VideosModel.create(object)
.then(function (result) {
cancelCreate();
getAll();
});
}
function update(object) {
VideosModel.update(object.id, object)
.then(function (result) {
cancelEditing();
getAll();
});
}
function deleteObject(id) {
VideosModel.delete(id)
.then(function (result) {
cancelEditing();
getAll();
});
}
vm.edited = null;
vm.isEditing = false;
vm.isCreating = false;
vm.getAll = getAll;
vm.create = create;
vm.update = update;
vm.delete = deleteObject;
vm.setEdited = setEdited;
vm.isCurrent = isCurrent;
vm.cancelEditing = cancelEditing;
vm.cancelCreate = cancelCreate;
vm.goToBackand = goToBackand;
vm.isAuthorized = false;
$rootScope.$on('authorized', function () {
vm.isAuthorized = true;
getAll();
});
$rootScope.$on('logout', function () {
clearData();
});
if(!vm.isAuthorized){
$rootScope.$broadcast('logout');
}
initCreateForm();
getAll();
})
.controller('detailsCtrl',function($stateParams,VideosModel){
var vm = this;
var videoId = $stateParams.id;
function getforId(id) {
vm.data=[];
VideosModel.fetch(id)
.then(function (result) {
vm.data = result.data.data;
console.log(vm.data);
});
}
getforId(videoId);
});
How do pass the data using this?
In order to use controllerAs syntax (bind your scope properties to 'this' in the controller) you need to give your controller an alias in the html.
<div ng-controller="detailsCtrl as vm">
By default, your html is going to reference $scope on your controller unless you give it an alias.
https://docs.angularjs.org/api/ng/directive/ngController

how to implement ng-model value from another view in ionic

I want to display ng-model value from page to input in another page
I Want display selected issue from issues page to contact page
Issue Controller
.controller('IssueCtrl', function($scope, $http) {
$http.get('api/issues').then(function(resp) {
console.log('Success', resp);
$scope.issues = resp.data;
}, function(err) {
console.error('ERR', err);
$scope.issues = err;
});
})
Contact Controller
.factory('Post', function($resource) {
return $resource('api/add_new_order',{problem: "#problem"});
})
.controller('ContactCtrl', function($scope, Post) {
// Get all posts
$scope.posts = Post.query();
// Our form data for creating a new post with ng-model
$scope.postData = {};
$scope.newPost = function() {
var post = new Post($scope.postData);
post.$save();
}
$scope.issues = {};
$scope.answer = function(){
console.log($scope.issues.name);
}
})
Issue View
<ion-list ng-repeat="item in issues">
<ion-radio ng-model="issues.name" ng-value="'{{item.issue}}'">
{{item.issue}}
</ion-radio>
</ion-list>
Contact View
<form ng-submit="newPost()">
<label class="item item-input">
<span class="input-label">Problem :</span>
<input type="text" name="problem" ng-model="postData.problem">
</label>
</form>
Your API requests should be on independent services, so they can be accessed by any controller.
As you seen to know how a factory works, I will give you an example.
.factory('IssuesService', function($http) {
var issues = [];
return {
all: function() {
return $http.get('api/issues')
.then(function(data){ // Optional callback inside service
issues = data;
});
}
}
})
.controller('ContactCtrl', function($scope, Post, IssuesService) {
...
$scope.issues = [];
IssuesService.all().then(function(data){
$scope.issues = data;
})
...
})
.controller('IssueCtrl', function($scope, $http) {
IssuesService.all()
.then(function(resp) {
console.log('Success', resp);
$scope.issues = resp.data;
}, function(err) {
console.error('ERR', err);
$scope.issues = err;
});
})

Ionic update Checkbox value

I'm trying to update the Toggle/Checkbox value back to default/Off when the user selects the cancel option. Currently it sticks to Red/On.
Any suggestions to figuring this out would be great,
Before
Alert
After
Controller
// Default Value
$scope.turnClearAppData = 'Off';
// Ionic OK Alert
$scope.showConfirm = function(val) {
if ( val == 'On') {
var confirmPopup = $ionicPopup.confirm({
title: 'Clear App Data',
template: 'Select OK to Confirm!'
});
confirmPopup.then(function(res) {
if(res) {
// Reset LocalStorage Data
$localStorage.$reset();
// Redirect After Reset
$location.path('/intro/part-one');
} else {
// On Cancel Update Value
$scope.turnClearAppData = 'Off';
}
});
}
};
HTML
<li class="item item-toggle noborder">
Clear App Data {{turnClearAppData}}
<label class="toggle toggle-assertive">
<input type="checkbox" ng-model="turnClearAppData" ng-true-value="'On'" ng-false-value="'Off'" ng-change="showConfirm(turnClearAppData)">
<div class="track">
<div class="handle"></div>
</div>
</label>
</li>
The problem is in the scope.
I answered a question few days ago where I have some links to some tutorials where they tell you exactly why you should avoid using scope as a model.
The best solution is to avoid $scope to attach your view model.
You can have a look at this plunker where your sample works.
I've used a ControllerAs syntax to fix the problem.
It's very easy to implement. In my plunker I've defined the ControllerAs in the state:
.state('home', {
url: '/home',
templateUrl: 'home.html',
controller: 'HomeController as vm'
});
but you can do it in many other ways:
<div ng-controller="HomeController as vm">
</div>
in your controller now you create your viewmodel:
.controller('HomeController', function($scope, $state, $timeout, $ionicPopup) {
var vm = this;
vm.turnClearAppData = 'Off';
vm.showConfirm = function(val) {
if ( val === 'On') {
var confirmPopup = $ionicPopup.confirm({
title: 'Clear App Data',
template: 'Select OK to Confirm!'
});
confirmPopup.then(function(res) {
if(res) {
// Reset LocalStorage Data
// $localStorage.$reset();
// Redirect After Reset
// $location.path('/intro/part-one');
alert('Ok pressed!');
} else {
// On Cancel Update Value
vm.turnClearAppData = 'Off';
return vm.turnClearAppData;
}
});
}
};
});
Notice the var vm = this as first expression.
Now all your objects and methods are mapped on the viewmodel (vm).
Your HTML now can work with the viewmodel
<li class="item item-toggle noborder">
Clear App Data {{vm.turnClearAppData}}
<label class="toggle toggle-assertive">
<input type="checkbox" ng-model="vm.turnClearAppData" ng-true-value="'On'" ng-false-value="'Off'" ng-change="vm.showConfirm(vm.turnClearAppData)">
<div class="track">
<div class="handle"></div>
</div>
</label>
</li>
obtaining the expected behaviour.
Try to use $timeout in such cases where updating the scope variable value do not update the related changes. Try this.
// Default Value
$scope.turnClearAppData = 'Off';
// Ionic OK Alert
$scope.showConfirm = function(val) {
if ( val == 'On') {
var confirmPopup = $ionicPopup.confirm({
title: 'Clear App Data',
template: 'Select OK to Confirm!'
});
confirmPopup.then(function(res) {
if(res) {
// Reset LocalStorage Data
$localStorage.$reset();
// Redirect After Reset
$location.path('/intro/part-one');
} else {
// On Cancel Update Value
$timeout(function () {
$scope.turnClearAppData = 'Off';
}, 0);
}
});
}
};

Angular Fire - Display Issue with Empty Data

I am new to Firebase and Angular JS so please excuse any obvious "what the heck are you doing's"...
I have a few pages that are nested:
A) Directory (templates/tab-directory.html)
A.1) Specialty (templates/tab-specialty.html)
A.1.1) Team (templates/tab-team.html)
My tab-specialty.html looks like this:
<ion-view view-title="{{specialty.name}}">
<ion-header-bar class="bar-positive bar-subheader item-input-inset">
<div class="button-bar">
...
</div>
</ion-header-bar>
<ion-content class="has-subheader">
...
</ion-content>
</ion-view>
This doesn't always display the data. I know I am doing something wrong with asynchronous calls or something along those lines because it seems like the data is just not 'ready' for me to display.
The {{specialty.name}} doesn't always display. If I do ionic serve on the specialty page, it will display fine. However, when I start from one level higher and click into that page, it will not.
My app.js has the following states in it:
.state('tab.directory', {
url: '/directory',
views: {
'tab-directory': {
templateUrl: 'templates/tab-directory.html',
controller: 'DirectoryCtrl'
}
}
})
.state('tab.specialty', {
url: '/specialty/:specialtyId',
views: {
'tab-directory': {
templateUrl: 'templates/tab-specialty.html',
controller: 'SpecialtyCtrl'
}
}
})
.state('tab.team', {
url: '/team/:teamId',
views: {
'tab-directory': {
templateUrl: 'templates/tab-team.html',
controller: 'TeamCtrl'
}
}
})
My controllers.js has the following controller for specialty:
...
.controller('SpecialtyCtrl', function($scope, $stateParams, Specialties, Staff, Teams){
var specialtyId = $stateParams.specialtyId;
...
Specialties.$loaded().then(function(array){
var syncArray = array.getById(specialtyId);
if(syncArray.length > 0){
$scope.specialty = syncArray[0];
console.log($scope.specialty);
}
});
Staff.$loaded().then(function(array){
var allArray = array.getBySpecialtyId(specialtyId);
hasConsults = getHasConsults(allArray);
if(hasConsults){
// Default to consults if we have them
$scope.isConsultsActive = true;
$scope.staffList = allArray.filter(function(staff){
return staff.is_consult;
});
} else {
// If no consults, default to all
$scope.isAllActive = true;
$scope.staffList = allArray;
}
});
// Watch for new/updated/removed staff for this specialty
Staff.$watch(function(event){
var allArray = Staff.getBySpecialtyId(specialtyId);
hasConsults = getHasConsults(allArray);
// Sort the lists
allArray.sort(sortList);
$scope.allStaff = allArray;
if($scope.isConsultsActive){
$scope.staffList = allArray.filter(function(staff){
return staff.is_consult;
});
}
});
...
And my services.js has the following services defined:
.service('Staff', function($firebaseArray, $window){
var staffList = $firebaseArray.$extend({
...
});
var ref = new $window.Firebase(FIREBASE_URL).child("staff");
var syncArray = new staffList(ref);
return syncArray;
})
.service('Teams', function($firebaseArray, $window){
var staffList = $firebaseArray.$extend({
...
});
var ref = new $window.Firebase(FIREBASE_URL).child("teams");
var syncArray = new staffList(ref);
return syncArray;
})
The $scope.specialty is the object that doesn't always seem to get filled. I am having the same problem on the teams page as well, but the example is almost exactly the same as this.

Resources