Ionic is a CSS and JavaScript framework. It is highly recommended that before starting this step you will get yourself familiar with its documentation.
In this step we will learn how to add Ionic library into our project, and use its powerful directives to create cross platform mobile (Android & iOS) applications.
We will achieve this by creating separate views for web and for mobile so be creating a separate view for the mobile applications, but we will keep the shared code parts as common code!
Using ionic is pretty simple - first, we need to install it:
$ meteor npm install ionic-sdk --save
To use ionic in our app we have to install angular-sanitize
:
$ meteor npm install angular-sanitize --save
Now we've got all of modules. Let's add the first module to Socially:
1
2
3
4
5
6
7
18
19
20
21
22
23
24
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import ngMaterial from 'angular-material';
import ngSanitize from 'angular-sanitize';
import uiRouter from 'angular-ui-router';
import template from './socially.html';
...some lines skipped...
export default angular.module(name, [
angularMeteor,
ngMaterial,
ngSanitize,
uiRouter,
PartiesList,
PartyDetails,
The second one is Ionic. We need to import not one but two files. It should look like this:
3
4
5
6
7
8
9
10
26
27
28
29
30
31
32
33
import ngMaterial from 'angular-material';
import ngSanitize from 'angular-sanitize';
import uiRouter from 'angular-ui-router';
import 'ionic-sdk/release/js/ionic';
import 'ionic-sdk/release/js/ionic-angular';
import template from './socially.html';
import { name as PartiesList } from '../partiesList/partiesList';
...some lines skipped...
PartyDetails,
Navigation,
Auth,
'accounts.ui',
'ionic'
]).component(name, {
template,
controllerAs: name,
Now's the time to add some style:
5
6
7
8
9
10
11
import uiRouter from 'angular-ui-router';
import 'ionic-sdk/release/js/ionic';
import 'ionic-sdk/release/js/ionic-angular';
import 'ionic-sdk/release/css/ionic.css';
import template from './socially.html';
import { name as PartiesList } from '../partiesList/partiesList';
We will do the same thing as we did in previous chapter with Login
component.
Let's create a view for the web. We can achieve this by copying the content of socially.html
:
1
2
3
<navigation></navigation>
<div ui-view=""></div>
Now let's take care of mobile view:
1
2
3
<ion-nav-bar class="bar-positive" align-title="center">
</ion-nav-bar>
<ion-nav-view></ion-nav-view>
This is a simple navigation layout. As you can see it is pretty similar to the web view.
The
ion-nav-view
tag is similar to theui-view
tag
Last thing to do is to implement these views:
7
8
9
10
11
12
13
14
15
16
19
20
21
22
23
24
25
import 'ionic-sdk/release/js/ionic-angular';
import 'ionic-sdk/release/css/ionic.css';
import { Meteor } from 'meteor/meteor';
import webTemplate from './web.html';
import mobileTemplate from './mobile.html';
import { name as PartiesList } from '../partiesList/partiesList';
import { name as PartyDetails } from '../partyDetails/partyDetails';
import { name as Navigation } from '../navigation/navigation';
...some lines skipped...
class Socially {}
const name = 'socially';
const template = Meteor.isCordova ? mobileTemplate : webTemplate;
// create a module
export default angular.module(name, [
We will no longer use socially.html
, so let's remove it!
By now, the navigation bar is empty, we can change this by adding ionNavTitle
:
1
2
3
4
5
6
<ion-nav-bar class="bar-positive" align-title="center">
<ion-nav-title>
Socially
</ion-nav-title>
</ion-nav-bar>
<ion-nav-view></ion-nav-view>
We will use the techniques we learned in Step 21 of the tutorial and run the project in our favorite emulator, I used Android so
$ meteor run android
Socially is working but the list of parties looks terrible!
The web view stays the same so let's just copy partiesList.html
to web.html
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<div layout="column" layout-padding>
<party-add-button ng-show="partiesList.isLoggedIn"></party-add-button>
<div>
<h2 class="md-display-1">List of the parties:</h2>
</div>
<div layout="row" layout-padding>
<md-input-container>
<label>Search</label>
<input ng-model="partiesList.searchText">
</md-input-container>
<parties-sort on-change="partiesList.sortChanged(sort)" property="name" order="1"></parties-sort>
</div>
<div layout="column" layout-gt-sm="row">
<div class="list list-web" flex="50">
<md-card dir-paginate="party in partiesList.parties | itemsPerPage: partiesList.perPage" total-items="partiesList.partiesCount">
<md-card-title>
<md-card-title-text>
<span class="md-headline" ui-sref="partyDetails({ partyId: party._id })">
{{party.name}}
<party-remove party="party"></party-remove>
</span>
<span class="md-subhead">{{party.description}}</span>
</md-card-title-text>
<md-card-title-media ng-if="party.images">
<div class="md-media-lg card-media">
<party-image images="party.images"></party-image>
</div>
</md-card-title-media>
</md-card-title>
<md-card-content>
<party-rsvps-list rsvps="party.rsvps"></party-rsvps-list>
<party-unanswered party="party" ng-if="!party.public"></party-unanswered>
<div ng-if="party.public">
Everyone is invited
</div>
<party-creator party="party"></party-creator>
</md-card-content>
<md-card-actions>
<party-rsvp party="party" ng-show="partiesList.isLoggedIn"></party-rsvp>
<div ng-hide="partiesList.isLoggedIn">
<i>Sign in to RSVP for this party.</i>
</div>
</md-card-actions>
</md-card>
<dir-pagination-controls on-page-change="partiesList.pageChanged(newPageNumber)"></dir-pagination-controls>
</div>
<div flex="50">
<parties-map parties="partiesList.parties"></parties-map>
</div>
</div>
</div>
For the purpose of tutorial we want to keep mobile version of Socially as simple as possible.
Let's display only name, description, image and list of RSVPs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<ion-view hide-back-button="true">
<ion-content padding="true" class="has-header">
<div class="list list-mobile">
<div class="card" ng-repeat="party in partiesList.parties">
<div class="item">
<h2>
{{party.name}}
</h2>
<p>
{{party.description}}
</p>
</div>
<div class="item item-image">
<party-image images="party.images"></party-image>
</div>
<party-rsvps-list class="item" rsvps="party.rsvps"></party-rsvps-list>
</div>
</div>
</ion-content>
</ion-view>
Ok! We have both views. It's time to implement them and remove the old partiesList.html
file:
4
5
6
7
8
9
10
11
12
13
73
74
75
76
77
78
79
import utilsPagination from 'angular-utils-pagination';
import { Counts } from 'meteor/tmeasday:publish-counts';
import { Meteor } from 'meteor/meteor';
import webTemplate from './web.html';
import mobileTemplate from './mobile.html';
import { Parties } from '../../../api/parties';
import { name as PartiesSort } from '../partiesSort/partiesSort';
import { name as PartiesMap } from '../partiesMap/partiesMap';
...some lines skipped...
}
const name = 'partiesList';
const template = Meteor.isCordova ? mobileTemplate : webTemplate;
// create a module
export default angular.module(name, [
Since the layout of the view has changed, it's a bit broken and not functioning well. For example, in the mobile view we have a terribly looking margin at the top of the first party item, and in the web view we can't scroll the parties list. Here are some few adjustments which will make the layout work properly again:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
parties-list {
> div:first-child {
height: calc(~"100vh - 64px");
}
[ui-sref] {
cursor: pointer;
}
.list-web {
overflow-y: visible;
overflow-x: hidden;
}
.list-mobile > .card:first-child {
margin-top: 0;
}
}
@import "../partiesMap/partiesMap.less";
You've probably noticed an issue with images. It happens because jalik:ufs
package saves an absolute path of a file. So if you uploaded an image in the browser its path will contain http://localhost:3000
.
But hey! We're on the mobile app so the port is different.
We can fix it by creating a filter. Let's call it DisplayImageFilter
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import angular from 'angular';
import { Meteor } from 'meteor/meteor';
const name = 'displayImageFilter';
function DisplayImageFilter(image) {
if (!image) {
return image;
}
// leave it as it is for the web view
if (!Meteor.isCordova) {
return image.url;
}
// create new path of an image
const path = `ufs/${image.store}/${image._id}/${image.name}`;
return Meteor.absoluteUrl(path);
}
// create a module
export default angular.module(name, [])
.filter(name, () => {
return DisplayImageFilter;
});
Done, we have it! Now we want to use it:
3
4
5
6
7
8
9
27
28
29
30
31
32
33
34
import template from './partyImage.html';
import { Images } from '../../../api/images';
import { name as DisplayImageFilter } from '../../filters/displayImageFilter';
class PartyImage {
constructor($scope, $reactive) {
...some lines skipped...
// create a module
export default angular.module(name, [
angularMeteor,
DisplayImageFilter
]).component(name, {
template,
bindings: {
1
<img ng-src="{{partyImage.mainImage | displayImageFilter}}"/>
And... we're done!
In this tutorial we showed how to use Ionic and how to separate the whole view for both, web and mobile.
We also learned how to share component between platforms, and change the view only!
We also used Ionic directives in order to provide user-experience of mobile platform instead of regular responsive layout of website.