I recently started with Ionic framework but to begin with Ionic Angular.js is pre-requisite. Can any one please tell me why do we use abstract keyword in nested ui-router. I was unable to find a good tutorial on that.
Please tell me what is the significance/advantages of abstract in angular.js.
Here is the code that I am unable to understand
var app = angular.module('ionicApp', ['ionic'])
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/todos')
$stateProvider.state('app', {
abstract: true,
templateUrl: 'main.html'
})
$stateProvider.state('app.todos', {
abstract: true,
url: '/todos',
views: {
todos: {
template: '<ion-nav-view></ion-nav-view>'
}
}
})
All right I will just post some of my experience using ionic.
- Ui router parent/child scope inheritance
This is not specific to ionic. Sometimes if you want to share some common function in relation to views, by using scope inheritance, you can consolidate default/share function in the parent, or setup default states in your view, or inherit resolved dependencies.
$stateProvider.state('app', {
abstract: true,
templateUrl: 'main.html',
resolve: {
commonMeta: ["$stateParams","$http", function($stateParams, $http) {
return $http.get("/commonMeta/" + $stateParams.id);
}]
},
controller: ["$scope", function($scope) {
// controller should be in a separate file
$scope.data = {};
$scope.state = {};
$scope.display = function() {
// common logic for page display
};
$scope.restoreCustomerPreference = function() {
// restore customer preference from say localstorage
};
$scope.init = function() {
// 'this' will be the child $scope
this.state = {
error: false
};
this.restoreCustomerPreference();
};
}],
});
$stateProvider.state('app.dashboard', {
url: '/dashboard',
views: {
todos: {
template: '<ion-nav-view></ion-nav-view>'
}
},
resolve: {
data: ["$http", function($http) {
return $http.get("/getDashboard/");
}]
},
controller: ["$scope","commonMeta","data" function($scope,commonMeta,data) {
// controller should be in a separate file
$scope.data = data;
$scope.display = function() {
// override the display logic
$
};
$scope.init();
}]
});
$stateProvider.state('app.todos', {
url: '/todos',
views: {
todos: {
template: '<ion-nav-view></ion-nav-view>'
}
},
resolve: {
data: ["$http", function($stateParams, $http) {
return $http.get("/getTodos/");
}]
},
controller: ["$scope", function($scope) {
// controller should be in a separate file
$scope.data = data;
$scope.someFn = function() {
// some function
};
$scope.init();
}]
});
Try to use controller: ["$scope", function($scope) { }] this form of dependencies injection, so your codes do not break when minified.
- ionic-slide-box
Using ionic-slide-box can develop app that user can slide left and right to get to next page, similar to facebook, by using a simple directive to fetch your template.
angular.module("directives",[])
.directive("slideItem", ["$http","$templateCache","$compile",function($http,$templateCache,$compile) {
return {
replace: false,
restrict: "EA",
scope: {
template: "#"
},
link: function(scope, iElement, iAttrs, controller) {
var events = scope.events();
$http.get(iAttrs.template, {cache: $templateCache})
.success(function(result) {
iElement.replaceWith($compile(result)(scope));
});
}
};
}]);
The template file can be hardcoded one:
<ion-view>
<ion-slide-box show-pager="false">
<ion-slide>
<slide-item template="templates/dashboard.html"></slide-item>
</ion-slide>
<ion-slide>
<slide-item template="templates/todos.html"></slide-item>
</ion-slide>
<ion-slide>
<slide-item template="templates/sumary.html"></slide-item>
</ion-slide>
</ion-slide-box>
</ion-view>
or using ng-repeat:
<ion-view>
<ion-slide-box show-pager="false">
<ion-slide ng-repeat="o in slides">
<slide-item template="templates/{{o.template}}.html" ></slide-item>
</ion-slide>
</ion-slide-box>
</ion-view>
Weinre is a debug tool that you must have, it can debug even
when your app is installed to a real device. without that, when your
app start with a blank screen you just don't know what to look for.
ngCordova is a wrapper for cordova function develop by ionic
too.
Android device can be test easily, TestFlight is good for ios
testing.
When using nested states an abstract state can have child states but cannot be activated itself.
Reference:
Nested States & Nested Views
Related
I've a very strange problem, it apparently seems that simple views template are preventing a controller to be executed, I can't understand why.
I've built a simple plunker
Code is here:
angular.module('plunker', ["ui.router"])
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("/nested");
$stateProvider
.state('nested', {
url: '/nested',
controller: function($scope) {
$scope.sayHi = 'Hi';
this.sayHello = 'Hello';
},
controllerAs: 'dog',
//If I comment the "views" section, controller runs correctly
views: {
'main': {
template: 'MainContent'
},
'secondary': {
template: 'SecondaryContent'
}
}
})
$locationProvider.html5Mode(false).hashPrefix('!');
})
html:
<div ui-view>
<h1>{{dog.sayHello}}</h1>
<h1>{{sayHi}}</h1>
<p>Why no "hello" or "Hi" over here?</p>
<div ui-view="main"></div>
<div ui-view="secondary"></div>
</div>
If I comment out the "views" section in the state definition, controller runs correctly.
[edit]
Thanks to Radim, I resolved moving the controller definition in the views section:
views:{
'':{
controller: function ($scope) {
this.page = 'ten';
},
controllerAs:'dog'
},
'main#nested':{
template:'MainContent'
},
'secondary#nested':{
template:'SecondaryContent'
}
}
Check:
UI-Router Multiple Views Single Controller not work
The controller always belongs to view, not to state.
There is an updated plunker
.state('nested', {
url: '/nested',
//controller: function($scope) {
// $scope.sayHi = 'Hi';
// this.sayHello = 'Hello';
//},
//controllerAs: 'dog',
//If I comment the "views" section, controller runs correctly
views: {
'main': {
//template: 'MainContent', - let's use view, to consume dog.sayHello
templateUrl: 'view2.html',
controller: function($scope) {
$scope.sayHi = 'Hi';
this.sayHello = 'Hello';
},
controllerAs: 'dog',
},
'secondary': {
template: 'SecondaryContent',
controller: ...
}
}
check the updated plunekr
I am using UI Router and UI Bootstrap in my Angular app. I'd like to use a service so that I can display alert messages from various controllers. I want the alert to display at the top of the screen. Any suggestions on how to modify the code below so that the alert will display at the top of the page and display messages from different controllers?
I'm using this Stack Overflow post as a model.
HTML:
<alert ng-repeat="alert in allInfos()" type="{{alert.type}}" close="closeAlert($index)"
ng-cloak>{{alert.msg}}</alert>
Service:
.factory('Informer', function(){
var messages = [];
var Informer = {};
Informer.inform = function(msg, type) {
messages.push({
msg: msg,
type: type
});
};
Informer.allInfos = function() {
return messages;
};
Informer.remove = function(info) {
messages.splice(messages.indexOf(info), 1);
};
return Informer;
})
Controller:
.controller('PaymentFormCtrl',
function ($scope, $http, Informer) {
$scope.handleStripe = function () {
Informer.inform("There was a problem authorizing your card.", "danger");
$scope.messages = 'problem';
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
}
};
});
.controller('ContactFormCtrl',
function ($scope, $http, Informer) {
//. . .
Informer.inform("There is already an account with that email address", "danger");
$scope.messages = 'problem';
$scope.allInfos = Informer.allInfos;
$scope.remove = Informer.remove;
}
};
});
Routers:
.state('home', {
url: '/',
views: {
'top': {
templateUrl: 'views/bigHero.html'
},
'bottom': {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
}
}
})
.state('payment', {
url: '/payment',
views: {
'top': {
templateUrl: 'views/customerinfo.html',
controller: 'ContactFormCtrl'
},
'bottom': {
templateUrl: 'views/creditcard.html',
controller: 'PaymentFormCtrl'
},
}
});
});
You really have three good options that I can think of off the top of my head.
Create a global or what i like to call a "RootController" of your application bound higher up in your DOM so that the other controllers scope naturally extends it. i.e.:
<div ng-controller="RootController">
<div ui-view></div>
</div>
You can create a parent state with UI Router that both your child states inherit, giving a similar effect to the case above:
$stateProvider.state('parent', {controller: 'ParentController'});
$stateProvider.state('parent.child1', {controller: 'Child1Controller'});
$stateProvider.state('parent.child2', {controller: 'Child2Controller'});
You can pass all shared functionality through a service, which acts as an error message to your necessary controllers.
myService.service('errorService', function() {
this.errorMessage = 'Everything is happy!';
});
myService.controller('PaymentFormCtrl', function($scope, errorService) {
$scope.errorService = errorService;
$scope.setError = function() {
errorService.errorMessage = 'An error happened!';
};
});
Is it possible to load a template from a other application (like a express app) via $http?
Or from an other external source?
Not with ui-router alone, though the following lazy loading module for ui-router exists which may help you achieve your goal: ocLazyLoad - https://github.com/ocombe/ocLazyLoad
An example of how it works (taken from http://plnkr.co/edit/6CLDsz)
define([
'angular',
'uiRouter',
'ocLazyLoad',
'ocLazyLoad-uiRouterDecorator'
], function (angular) {
var app = angular.module('app', ['ui.router', 'oc.lazyLoad', 'oc.lazyLoad.uiRouterDecorator']);
app.config(function($stateProvider, $locationProvider, $ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
loadedModules: ['app'],
asyncLoader: require
});
$stateProvider
.state('home', {
url: "/",
template: "<p>Hello {{name}}. Would you like to... <a href='#lazy'>load lazy</a>?</p>",
controller: 'mainCtrl'
})
.state('lazy', {
url: "/lazy",
lazyModule: 'app.lazy',
lazyFiles: 'lazy',
lazyTemplateUrl: 'lazy.html',
controller: 'lazyCtrl'
});
$locationProvider.html5Mode(true);
});
app.controller('mainCtrl', function($scope) {
$scope.name = 'World';
});
});
It is possible but you'll have to use a templateProvider. More clear explanation using an example:
$stateProvider.state('state', {
url: '/state',
//templateUrl: 'templates/stateTemplate.html',
templateProvider: function ($http, $templateCache) {
var tplUrl = 'http://another.accesible.domain/stateTemplate.html',
tpl = $templateCache.get(tplUrl);
return (!!tpl) ? tpl :
$http
.get(tplUrl)
.then(function (response) {
tpl = response.data
$templateCache.put(tplUrl, tpl);
return tpl;
});
},
controller: 'stateController as sCtrl',
params: {
target: null
},
resolve: {
loadCtrl: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load(['stateController', 'appDataProvider', 'appDataService', 'stateFactory', 'stateService']);
}],
resolveObject: function ($window) {
var result = $window.localStorage.getItem('resolveObj');
return result;
}
}
})
Hope it helps (late answer I know, but just found this question looking for something else). ocLazyLoad is needed if you don't want to load everything at once when your app starts, but load what is required when it's required. Quite useful if your app's memory footprint is an issue.
Best regards.
I am building an app using Ionic Framework. I am trying to move to a new view/page.html, but when I click on the list it doesn't go to the new view/page. However the URL changes. Why am I having this issue and what is the problem?
This is the Html
<a class="item item-icon-left item-icon-right" ng-click="viewCarLog()">
<i class="icon ion-model-s"></i>
Car Log
<i class="icon ion-chevron-right ion-accessory"></i>
</a>
Here is the app.js
.state('app', {
abstract: true,
url: "/app",
templateUrl: "app/layout/menu-layout.html"
})
.state('app.home', {
url: "/home",
views: {
"tab-home": {
templateUrl: "app/home/home.html"
}
}
})
.state('app.settings', {
url: "/settings",
views: {
"tab-settings": {
templateUrl: "app/settings/settings.html"
}
}
})
.state('app.car-log', {
url: "/car",
templateUrl: "app/car/car-log.html",
controller: 'CarLogCtrl'
})
.state('app.location', {
url:"/location",
templateUrl: "app/location/location.html"
})
.state('login', {
url:"/login",
templateUrl: "app/login/login.html"
});
This is the Home controller that is trying to open a new view/page
(function() {
'use strict';
angular.module('myDrive').controller('HomeCtrl', ['$state', '$scope', HomeCtrl]);
function HomeCtrl($state, $scope) {
var vm = this;
// TODO:: Getting Data from Server (WEB API)
//
$scope.viewCarLog = function() {
$state.go("app.car-log");
};
};
})();
This is a CarLogCtrl. Currently empty as it functionality of this isn't yet implemented. Am I missing something in the controller to show the view?
(function() {
'use strict';
angular.module('myDrive').controller('CarLogCtrl', ['$state', '$scope', CarLogCtrl]);
function CarLogCtrl($state, $scope) {
var vm = this;
// TODO:: Do something
};
})();
Looking for a help, and tips and guidance for this.
Found an issue with not being able to open a view. What happened was that in app.js a state needs to have a view included to display the page.
The following made it work.
.state('app.car-log', {
url: "/car",
views: {
"tab-home": {
templateUrl: "app/car/car-log.html"
}
}
})
Please correct me if I am wrong. But this worked to fix the issue I had.
I have an angular service that creates synced $firebase references. These references are ultimately passed into a controller, and then to a directive with an isolate scope.
When the $scope of the controller is destroyed by navigating to a different state, the references appear to stay in memory and never get GCed.
Code Sample:
var app = angular.module('app', ['firebase', 'ui.router']);
app.service('service', function($firebase) {
var ref = new Firebase('https://ease-bugreport.firebaseio.com/tasks');
return {
find: function(taskId) {
// Creating orphan refs after states are changed. Not getting $destroy()-ed as the corresponding scope is destroyed?
return $firebase(ref.child(taskId)).$asObject();
}
}
});
app.controller('ctrl', function($scope, service) {
$scope.tasks = [];
/*
* In the real application, this list of ids is grabbed from an index of ids.
*/
var taskIds = [
'-JVMmByyk5wvYdVJQ_JT',
'-JVMmBz4hue-5QytQwWb',
'-JVMmBz8aAt5WDUQ4H1R',
'-JVMmC-Q8QEGB6zZuitb',
'-JVMmC-UkMAiyi6v6bcK',
'-JVMmC-WyOrlNKZTjnqH',
'-JVMmC-Y29ncf14G1rkA',
'-JVMmC0coVLi1FUfrbKD',
'-JVMmC1hDrs07XdwcgLh',
'-JVMmC1k-GYz_DWw3dDj',
'-JVMmC2aCuzOIZ2nf1B-',
'-JVMmC2cQKNkOBxhJ5vP',
'-JVMmC2giV_IlXrKXVFw',
'-JVMmC3fXQYfjtXdTk_p',
'-JVMmC3ibcUPT88hcD6Q',
'-JVMmC3mDKms0BVpAcdq',
'-JVMmC4jFwfPNe1-istd',
'-JVMmC4m3ZGAiS7xnXHP',
'-JVMmC4rp3pNfeTgIUCJ',
'-JVMmC4uaH7MdkTZbQVm',
'-JVMmC5ttFy3ojD1bt3t',
'-JVMmC5v_iTwWS02PF9h',
'-JVMmC5xFYPS0zvaU4bi',
'-JVMmC75NA1H1e7dYGdM',
'-JVMmC77o5mBUACibaUG',
'-JVMmC7AmuYy6VDNn9B1',
'-JVMmC85nVa6NexPJLLP',
'-JVMmC88XIFUqq98gexw',
'-JVMmC89h4HLaXxmHld8',
'-JVMmC8CNJ55Olt8D57w'
];
angular.forEach(taskIds, function(taskId) {
$scope.tasks.push(service.find(taskId));
});
});
app.directive('taskPanel', function() {
return {
scope: {
task: '='
},
restrict: 'E',
replace: true,
template: '<div>{{task.name}} - {{task.createdAt | date}}</div>',
link: function(scope, element, attrs) {
}
};
});
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('main', {
url: '/main',
controller: function() {},
template: '<div>MAIN PAGE!</div>'
})
.state('list', {
url: '/list',
controller: 'ctrl',
templateUrl: 'list.html'
});
$urlRouterProvider.otherwise('/main');
});
Here is a codepen demonstrating the issue: http://codepen.io/rabhw/pen/ADiKz
The issue is far more exaggerated in my application as each reference is using the 'objectFactory' option to attach additional instance methods via a factory.
Should I be taking a different approach to my services?
Any advice is appreciated.
Try adding following code to your controller:
$scope.$on('$destroy', function() {
$scope.tasks = undefined; // or []
});
Should automatically be recognized in directive's task.