Есть у меня приложение, написанное на Ionic Framework. На его основе хочу поделиться со всеми своим опытом разработки и напишу как создать кроссплатформенное приложение по шагам.
В этой статье будем с нуля разрабатывать приложение, которое позволяет читать статьи (публикации). У публикации будет название (заголовок), заглавное фото, краткое содержание, полное содержание, категория, автор, дата публикации. Все данные для приложения будут браться с сервера посредством Http-запросов.
В приложении будет несколько страниц (экранов):
- список всех публикаций, отсортированный по дате.
- список категорий, отсортированный по алфавиту.
- список авторов, отсортированный по имени.
- список публикаций выбранной категории, отсортированный по дате.
- список публикаций выбранного автора, отсортированный по дате.
- содержание публикации.
Результатом статьи получится приложение, которое выглядит как на картинке выше.
Плюс ссылка на исходники всего проекта.
Начало
Создадим новый проект и назовем его articles. Для этого выполним команду:
ionic start articles tabs
Результатом увидим созданный каталог с именем articles:
Структура проекта
Создадим новые страницы, которые нам нужны: postlist, categorylist, authorlist. Для этого поочередно выполним команды:
ionic generate page postlist
ionic generate page categorylist
ionic generate page authorlist
В результате увидим созданные каталоги в папке \articles\src\pages\:
Созданные страницы
Откроем файлы: postlist.ts, categorylist.ts, authorlist.ts и в каждом файле напишем строчку для импорта класса NavController и объекта NavParams
import { NavController, NavParams } from 'ionic-angular';
В результате получим следующий вид файлов postlist.ts, categorylist.ts, authorlist.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'page-postlist',
templateUrl: 'postlist.html',
})
export class Postlist {
constructor(public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad Postlist');
}
}
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'page-categorylist',
templateUrl: 'categorylist.html',
})
export class Categorylist {
constructor(public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad Categorylist');
}
}
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'page-authorlist',
templateUrl: 'authorlist.html',
})
export class Authorlist {
constructor(public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad Authorlist');
}
}
Удалим лишние папки в \articles\src\pages\: about, contact, home. Они были созданы автоматически при создании проекта. Нам они не нужны.
Откроем файл \src\app\app.module.ts и внесем туда изменения. Пропишем использование вновь созданных страниц и удалим всякие упоминания об удаленных страницах.
Результатом всех действий будет вот такое содержимое файла app.module.ts
app.module.ts
import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { Postlist } from '../pages/postlist/postlist';
import { Categorylist } from '../pages/categorylist/categorylist';
import { Authorlist } from '../pages/authorlist/authorlist';
import { TabsPage } from '../pages/tabs/tabs';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
@NgModule({
declarations: [
MyApp,
Postlist,
Categorylist,
Authorlist,
TabsPage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
Postlist,
Categorylist,
Authorlist,
TabsPage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
Откроем файл \src\pages\tabs\tabs.ts и изменим содержимое для того, чтобы вкладки ссылались на страницы postlist, categorylist, authorlist.
Измененный файл выглядит так:
import { Component } from '@angular/core';
import { Postlist } from '../postlist/postlist';
import { Categorylist } from '../categorylist/categorylist';
import { Authorlist } from '../authorlist/authorlist';
@Component({
templateUrl: 'tabs.html'
})
export class TabsPage {
tab1Root = Postlist;
tab2Root = Categorylist;
tab3Root = Authorlist;
constructor() {
}
}
Откроем файл \src\pages\tabs\tabs.html и внесем туда следующие изменения: изменим названия вкладок в tabTitle и иконок в tabIcon (все необходимые иконки перечислены в документации ionicons):
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="Публикации" tabIcon="ios-paper-outline"></ion-tab>
<ion-tab [root]="tab2Root" tabTitle="Категории" tabIcon="ios-albums-outline"></ion-tab>
<ion-tab [root]="tab3Root" tabTitle="Авторы" tabIcon="ios-contacts-outline"></ion-tab>
</ion-tabs>
Выполним команду
ionic serve
, чтобы посмотреть полученный результат:Первый результат
Поменяем основной цвет приложения на тот, который мы хотим. Для этого откроем файл \src\theme\variables.scss и добавим нужный цвет
clmain:#3949ab
в массиве $colors
:$colors: (
primary: #488aff,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
clmain: #3949ab,
);
И затем применим этот цвет в верхней части (для
<ion-navbar>
) каждой из страниц postlist.html, categorylist.html, authorlist.html:<ion-navbar color="clmain">
...
</ion-navbar>
Переопределим цвет для вкладок (
Tabs
). Пропишем такие строки в файле \src\theme\variables.scss:$tabs-md-tab-color-active: #283593;
$tabs-ios-tab-color-active: #283593;
$tabs-wp-tab-color-active: #283593;
В результате внешний вид приложения получится таким:
Основной цвет приложения
Меню
Теперь сделаем меню, которое выдвигается слева при нажатии на кнопку-гамбергер.
Создадим массив, в котором будут перечислены страницы с названиями, индексами и иконками. Откроем файл app.component.ts и сначала объявим массив
pages
в классе MyApp
:pages: Array<{title: string, component: any, index: string, icon_name: string}>;
а затем в конструкторе класса заполним этот массив:
this.pages = [
{ title: 'Публикации', component: TabsPage, index: '0', icon_name: 'ios-paper-outline' },
{ title: 'Категории', component: TabsPage, index: '1', icon_name: 'ios-albums-outline' },
{ title: 'Авторы', component: TabsPage, index: '2', icon_name: 'ios-contacts-outline' }
];
Также в самом начале импортируем используемые табы:
import { TabsPage } from '../pages/tabs/tabs';
Используем заполненный массив
pages
для отображения пунктов меню. Откроем файл app.html и приведем его к следующему виду:<ion-menu [content]="content">
<ion-header>
</ion-header>
<ion-content>
<ion-list no-lines>
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)" color="clmain">
<ion-icon item-left [name]="p.icon_name" item-left color="light"></ion-icon>
{{p.title}}
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav [root]="rootPage" #content></ion-nav>
Метод
openPage(p)
срабатывает при нажатии на пункт меню. В качестве параметра передается элемент массива нажатого пункта меню.Опишем работу этого метода в файле app.component.ts
openPage(page) {
this.navCtrl.setRoot(page.component, {index: page.index});
}
При вызове
navCtrl.setRoot
мы передаем страницу page.component
, а также параметр page.index
, являющийся индексом выбранного пункта. Этот параметр нужен нам будет, чтобы знать какая из трех вкладок открывается.navCtrl
— объявляется следующим образом (всё в том же файле app.component.ts):import { ViewChild } from '@angular/core';
import { Nav } from 'ionic-angular';
и в классе
MyApp
в самом начале пишем объявление:@ViewChild(Nav) navCtrl: Nav;
В результате получаем следующее содержимое файла app.component.ts:
app.component.ts
import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { TabsPage } from '../pages/tabs/tabs';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
@ViewChild(Nav) navCtrl: Nav;
rootPage:any = TabsPage;
pages: Array<{title: string, component: any, index: string, icon_name: string}>;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
this.pages = [
{ title: 'Публикации', component: TabsPage, index: '0', icon_name: 'ios-paper-outline' },
{ title: 'Категории', component: TabsPage, index: '1', icon_name: 'ios-albums-outline' },
{ title: 'Авторы', component: TabsPage, index: '2', icon_name: 'ios-contacts-outline' }
];
}
openPage(page) {
this.navCtrl.setRoot(page.component, {index: page.index});
}
}
Теперь сделаем выделение именно той вкладки, которая должна быть открыта при нажатии определенного пункта меню: «Публикации», «Категории», «Авторы». Для этого откроем файл tabs.ts и напишем получение параметра
Index
(который передается в методе openPage(page)
).Импортируем
NavParams
:import { NavParams } from 'ionic-angular';
Объявим новую переменную
index
в классе TabsPage
:index: string;
В параметрах конструктора впишем:
public navParams: NavParams
а в теле конструктора напишем получение значения индекса:
this.index = navParams.get('index');
Полностью файл tabs.ts будет выглядеть так:
import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';
import { Postlist } from '../postlist/postlist';
import { Categorylist } from '../categorylist/categorylist';
import { Authorlist } from '../authorlist/authorlist';
@Component({
templateUrl: 'tabs.html'
})
export class TabsPage {
index: string;
tab1Root = Postlist;
tab2Root = Categorylist;
tab3Root = Authorlist;
constructor(public navParams: NavParams) {
this.index = navParams.get('index');
}
}
И еще добавим выделение нужной вкладки по полученному индексу. Для этого в файле tabs.html для
<ion-tabs>
напишем следующее:<ion-tabs selectedIndex={{index}}>
Сделаем для всех меню фон таким же как наш основной цвет. Для этого откроем файл \src\app\app.scss и добавим туда стиль:
.myBg{
background-color: #3949ab;
}
Откроем файл app.html и применим этот стиль для элемента
<ion-content>
:<ion-content class="myBg">
Левое меню
Добавим следующие строки (внутри элемента
<ion-navbar>
) в файлы postlist.html, categorylist.html, authorlist.html, чтобы увидеть иконку меню (гамбургер) в левой части верхней строки приложения.<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
Посмотрим результат и увидим такой вид:
Иконка меню
Ну и в качестве эксперимента добавим рисунок перед выводом всех пунктом меню. Для этого возьмем картинку любого размера и поместим ее в папку
\src\assets\imgs\
с именем menu.png
. Затем будем ее использовать в файле app.html:<ion-content class="myBg">
<img src="assets/imgs/menu.png" />
<ion-list no-lines>
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)" color="clmain">
<ion-icon item-left [name]="p.icon_name" item-left color="light"></ion-icon>
{{p.title}}
</button>
</ion-list>
</ion-content>
Посмотрим результат и увидим следующее:
Меню с рисунком
В результате проделанной работы мы получили приложение, которое содержит:
- три страницы: «Публикации», «Категории», «Авторы».
- меню, выдвигающееся слева.
- табы для переключения страниц.
- пункты меню для переключения табов.
Получение данных
Дальше будем заполнять каждую из страниц (postlist, categorylist, authorlist) данными, которые будем получает в формате JSON посредством HTTP запросов.
Вот список пунктов, которые будут реализовываны для каждой страницы:
- загрузка данных при открытии страницы.
- подгрузка следующих данных при пролистывании к последнему элементу списка.
- обновление страницы при потягивании списка вниз, когда список отображает самое начало.
- отображение спиннера загрузки (в момент выполнения запроса при получении данных).
- обработка ошибок при выполнении запроса.
Пару слов о выполнении запросов в Ionic. При работе с запросами нужно помнить о технологии CORS.
В моем примере я использую свой сервер для получения данных, соответственно при получении данных с сервера в заголовках я без проблем указываю нужный заголовок:
header('Content-Type: application/json;charset=utf-8');
header('Access-Control-Allow-Origin: *');
Например полный текст скрипта для получения списка категорий выглядит примерно так:
Скрипт для получения списка категорий
header('Content-Type: application/json;charset=utf-8');
header('Access-Control-Allow-Origin: *');
$dblocation = "localhost";
$dbname = "database";
$dbuser = "username";
$dbpasswd = "password";
$mysqli = new mysqli($dblocation, $dbuser, $dbpasswd, $dbname);
$query = "
select `tb_categories`.*
from `tb_categories`
order by `tb_categories`.`category`";
$data = array();
if ($res = $mysqli->query($query))
{
$data['count'] = strval($res->num_rows);
while($row = $res->fetch_assoc()){
$data['data'][] = array(
'id' => $row['id'],
'category' => $row['category'],
'url' => $row['url']
);
}
}
echo json_encode($data);
При выполнении этого скрипта мы получим в качестве ответа данные в формате JSON:
JSON-данные
Возвращаемся к Ionic-приложению. Начнем с подключения сервиса Http. Сначала в файле app.module.ts импортируем HttpModule следующей строкой:
import { HttpModule } from '@angular/http';
а также пропишем его в разделе импорта:
imports: [
BrowserModule,
HttpModule,
IonicModule.forRoot(MyApp)
],
Далее переходим к файлу postlist.ts и описываем импорт следующих объектов:
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
LoadingController используется для отображения индикатора загрузки.
объявляем в конструкторе класса
Postlist
сервис Http
и LoadingController
:constructor(public navCtrl: NavController, public http: Http, public loadingCtrl: LoadingController, public navParams: NavParams)
Дальше я приведу полное содержимое файла postlist.ts с комментариями:
postlist.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
@Component({
selector: 'page-postlist',
templateUrl: 'postlist.html',
})
export class Postlist {
postlists: any; // данные со списком публикаций, полученные из запроса
postlists_new: any; // данные СЛЕДУЮЩЕГО списка публикаций, которые получаются при пролистывании списка к последнему элементу
countElement: number = 10; // кол-во элементов, которые мы получаем из запроса
beginElement: number = 0; // начальный номер публикации, с которого получаем список элементов
post_error: string; // результат выполнения запроса 0-успешно, 1-ошибка
constructor(public navCtrl: NavController, public http: Http, public loadingCtrl: LoadingController, public navParams: NavParams) {
// Метод получения данных из запроса
// 0 - получаем данные с самого начала
// 1 - получаем СЛЕДУЮЩИЕ данные по порядку
this.loadData(0);
}
loadData(isNew) {
if (isNew==0){
// Первоначальные значения переменных
this.beginElement = 0;
this.countElement = 10;
// Создаем окно загрузки
let loadingPopup = this.loadingCtrl.create({
content: ''
});
// Показываем окно загрузки
loadingPopup.present();
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/postlist.php?begin='+this.beginElement+'&limit='+this.countElement)
.timeout(20000) // Ставим лимит на получение запроса и прерываем запрос через 20 сек.
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.postlists = data.data; // Данные получены, записываем их
this.countElement = data.count; // Записываем кол-во полученных публикаций
this.post_error = "0"; // Результат - успешно
loadingPopup.dismiss(); // Убираем окно загрузки
}, 1000);
},
err => {
loadingPopup.dismiss(); // Убираем окно загрузки
this.post_error = "1"; // Результат - ошибка
}
);
}else{
// Увеличиваем начальную позицию номера публикации для последующего получения именно с нужной позиции
this.beginElement = Number(this.beginElement) + Number(this.countElement);
}
}
// Выполняется при пролистывании к последнему элементу списка
doInfinite(infiniteScroll) {
// Проверяем нужно ли выполнять запрос
// Если в предыдущем запросе мы получили 0 публикаций,
// значит больше не нужно выполнять запрос для получения СЛЕДУЮЩЕГО набора данных
if (this.countElement != 0){
this.loadData(1);
// Get the data
this.http.get('https://mysite.ru/postlist.php?begin='+this.beginElement+'&limit='+this.countElement)
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.postlists_new = data.data; // Записали новую порцию данных
this.countElement = data.count;
this.post_error = "0";
for (let i = 0; i < this.countElement; i++) {
this.postlists.push( this.postlists_new[i] ); // Добавили новые данные в основной массив публикаций
}
infiniteScroll.complete();
}, 1000);
},
err => console.error(err)
);
}else{
infiniteScroll.complete();
}
}
// Выполняется при потягивании списка вниз, когда список находится в верхнем положении
doRefresh(refresher) {
this.loadData(0);
setTimeout(() => {
refresher.complete();
}, 2000);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Postlist');
}
}
Откроем файл postlist.html и сделаем отображение полученных данных в виде списка:
...
<ion-content padding>
<ion-refresher (ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingIcon="arrow-dropdown"
pullingText="Потяните для обновления"
refreshingSpinner="circles"
refreshingText="Обновление...">
</ion-refresher-content>
</ion-refresher>
<div *ngIf="post_error == '0'">
<ion-card *ngFor="let postlist of postlists" (click)="openPostPage(postlist)" text-wrap class="list-selected">
<ion-card-title class="postlist-title"></ion-card-title>
<div>
<div class="postlist-category">{{postlist.category}}</div>
<div class="postlist-dat">{{postlist.dat3}}</div>
</div>
<ion-card-title class="postlist-title">
{{postlist.title}}
</ion-card-title>
<img [src]="postlist.img" />
<h5 class="postlist-intro-text">{{postlist.intro_text}}</h5>
</ion-card>
</div>
<div *ngIf="post_error == '1'" style="text-align: center">
<ion-label>Ошибка при получении данных</ion-label>
<button ion-button (click)="loadData(0)" color="clmain" icon-left>
<ion-icon name="refresh"></ion-icon>
Обновить
</button>
</div>
<ion-infinite-scroll (ionInfinite)="doInfinite($event)">
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="Загрузка данных...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
Теперь пару слов об этом коде. В верхней части в блоке <ion-refresher> описаны действия, при потягивании списка вниз, когда список отображает самое начало. В нижней части в блоке <ion-infinite-scroll> описаны действия, при пролистывании к последнему элементу списка.
В центральной части два блока
div
. Один отображается при условии, что нет ошибки при получении данных (post_error == '0'
). Второй отображается, если была ошибка (post_error == '1'
).Результат получается таким
Теперь немного приукрасим отображение. Для этого опишем необходимые стили (
postlist-title, postlist-intro-text, postlist-dat, postlist-category
) в файле postlist.scss:page-postlist {
.postlist-title {
font-size: 18px !important;
white-space: inherit;
}
.postlist-intro-text {
font-size: 14px !important;
color: gray;
white-space: inherit;
}
.postlist-dat {
font-size: 12px !important;
color: gray;
white-space: inherit;
float: right;
text-align: right;
width: 50%;
}
.postlist-category {
font-size: 12px !important;
color: gray;
white-space: inherit;
float: left;
width: 50%;
}
}
Результат получится таким
Загрузка данных
Ошибка получения данных
Обновление данных
Подгрузка следующих данных
При нажатии на элемент списка (<ion-card>) срабатывает событие
click
. И у нас там написан вызов метода openPostPage(postlist)
. Данный метод позволит открыть содержимое публикации. Позднее мы вернемся к нему и опишем его.Проводим аналогичные действия для оставшихся двух страниц
В categorylist будем отображать список всех категорий публикаций.
В authorlist будем отображать список всех пользователей публикаций.
Чуть ниже приведу сразу готовые файлы каждой страницы, потому что методика получения и отображения данных в них такая же как и для страницы postlist.
Единственное исключение касается категорий. Т.к. категорий будет малое кол-во в принципе, то для этой страницы не нужно делать подгрузку следующих данных при достижении конца списка. Сразу получим все категории и отобразим их целиком.
Дополнительно сделаем еще один функционал: при нажатии на элемент списка (либо на категорию либо на автора) откроем список публикаций для выбранного элемента. Для этого у события
click
напишем вызов методов openPostCategoryPage
и openPostAuthorPage
соответственно в каждой странице, а также опишем работу методов (в обоих файлах categorylist.ts и authorlist.ts):openPostCategoryPage(item) {
this.navCtrl.push(Postlist, { item: item, type: '1' });
}
openPostAuthorPage(item) {
this.navCtrl.push(Postlist, { item: item, type: '2' });
}
В качесте параметра передадим выбранную страницу (
item
) и номер страницы (type
), чтобы потом отличить страницу категорий от страницы авторов.Вот полное содержимое файлов categorylist.ts, categorylist.html и authorlist.ts, authorlist.html.
categorylist.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
import { Postlist } from '../postlist/postlist';
@Component({
selector: 'page-categorylist',
templateUrl: 'categorylist.html',
})
export class Categorylist {
categorylists: any;
post_error: string;
constructor(public navCtrl: NavController, public navParams: NavParams, public http: Http, public loadingCtrl: LoadingController) {
this.loadData();
}
openPostCategoryPage(item) {
this.navCtrl.push(Postlist, { item: item, type: '1' });
}
loadData() {
// Создаем окно загрузки
let loadingPopup = this.loadingCtrl.create({
content: ''
});
// Показываем окно загрузки
loadingPopup.present();
// Получение данных, с указание URL-запроса
this.http.get('https://mysite.ru//categorylist.php')
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.categorylists = data.data;
this.post_error = "0";
loadingPopup.dismiss();
}, 1000);
},
err => {
loadingPopup.dismiss();
this.post_error = "1";
}
);
}
// Выполняется при потягивании списка вниз, когда список находится в верхнем положении
doRefresh(refresher) {
this.loadData();
setTimeout(() => {
refresher.complete();
}, 2000);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Categorylist');
}
}
categorylist.html
<ion-header>
<ion-navbar color="clmain">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Категории</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-refresher (ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingIcon="arrow-dropdown"
pullingText="Потяните для обновления"
refreshingSpinner="circles"
refreshingText="Обновление...">
</ion-refresher-content>
</ion-refresher>
<div *ngIf="post_error == '0'">
<ion-list>
<button ion-item *ngFor="let categorylist of categorylists" (click)="openPostCategoryPage(categorylist)" text-wrap class="list-selected">
<ion-avatar item-left>
<img [src]="categorylist.icon" />
</ion-avatar>
<ion-label class="categorylist-title">{{categorylist.category}}</ion-label>
</button>
</ion-list>
</div>
<div *ngIf="post_error == '1'" style="text-align: center">
<ion-label>Ошибка при получении данных</ion-label>
<button ion-button (click)="loadData(0)" color="clmain" icon-left>
<ion-icon name="refresh"></ion-icon>
Обновить
</button>
</div>
</ion-content>
authorlist.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
import { Postlist } from '../postlist/postlist';
@Component({
selector: 'page-authorlist',
templateUrl: 'authorlist.html',
})
export class Authorlist {
authorlists: any;
authorlists_new: any;
countElement: number = 40;
beginElement: number = 0;
post_error: string;
constructor(public navCtrl: NavController, public navParams: NavParams, public http: Http, public loadingCtrl: LoadingController) {
this.loadData(0);
}
openPostAuthorPage(item) {
this.navCtrl.push(Postlist, { item: item, type: '2' });
}
loadData(isNew) {
if (isNew==0){
// Первоначальные значения переменных
this.beginElement = 0;
this.countElement = 40;
// Создаем окно загрузки
let loadingPopup = this.loadingCtrl.create({
content: ''
});
// Показываем окно загрузки
loadingPopup.present();
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/authorlist.php?begin='+this.beginElement+'&limit='+this.countElement)
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.authorlists = data.data;
this.countElement = data.count;
this.post_error = "0";
loadingPopup.dismiss();
}, 1000);
},
err => {
loadingPopup.dismiss();
this.post_error = "1";
}
);
}else{
// Увеличиваем начальную позицию номера публикации для последующего получения именно с нужной позиции
this.beginElement = Number(this.beginElement) + Number(this.countElement);
}
}
// Выполняется при пролистывании к последнему элементу списка
doInfinite(infiniteScroll) {
// Проверяем нужно ли выполнять запрос
// Если в предыдущем запросе мы получили 0 публикаций,
// значит больше не нужно выполнять запрос для получения СЛЕДУЮЩЕГО набора данных
if (this.countElement != 0){
this.loadData(1);
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/authorlist.php?begin='+this.beginElement+'&limit='+this.countElement+'&t='+this.searchtext)
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.authorlists_new = data.data;
this.countElement = data.count;
this.post_error = "0";
for (let i = 0; i < this.countElement; i++) {
this.authorlists.push( this.authorlists_new[i] );
}
infiniteScroll.complete();
}, 1000);
},
err => console.error(err)
);
}else{
infiniteScroll.complete();
}
}
// Выполняется при потягивании списка вниз, когда список находится в верхнем положении
doRefresh(refresher) {
this.loadData(0);
setTimeout(() => {
refresher.complete();
}, 2000);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Authorlist');
}
}
authorlist.html
<ion-header>
<ion-navbar color="clmain">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Авторы</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding class="android-scroll-bar">
<ion-refresher (ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingIcon="arrow-dropdown"
pullingText="Потяните для обновления"
refreshingSpinner="circles"
refreshingText="Обновление...">
</ion-refresher-content>
</ion-refresher>
<div *ngIf="post_error == '0'">
<ion-list>
<button ion-item *ngFor="let authorlist of authorlists" (click)="openPostAuthorPage(authorlist)" text-wrap class="list-selected">
<ion-avatar item-left>
<img [src]="authorlist.img" />
</ion-avatar>
<ion-label class="authorlist-title">{{authorlist.author}}</ion-label>
</button>
</ion-list>
</div>
<div *ngIf="post_error == '1'" style="text-align: center">
<ion-label>Ошибка при получении данных</ion-label>
<button ion-button (click)="loadData(0)" color="clmain" icon-left>
<ion-icon name="refresh"></ion-icon>
Обновить
</button>
</div>
<ion-infinite-scroll (ionInfinite)="doInfinite($event)">
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="Загрузка данных...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
Результат отображения категорий и авторов
Чтобы отобразить список публикаций выбранной категории и выбранного автора, будем использовать уже существующую страницу postlist.
Для этого в Http запросах введем параметр C для передачи значения выбранной категории и параметр A для передачи выбранного автора. Если данный параметр не заполнен, будем возвращать все публикации.
После внесения изменений в файлы postlist.ts и postlist.html получим следущее:
postlist.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
@Component({
selector: 'page-postlist',
templateUrl: 'postlist.html',
})
export class Postlist {
title: string;
categoryId: any;
authorId: any;
selectedItem: any;
selectedType: string;
postlists: any; // данные со списком публикаций, полученные из запроса
postlists_new: any; // данные СЛЕДУЮЩЕГО списка публикаций, которые получаются при пролистывании списка к последнему элементу
countElement: number = 10; // кол-во элементов, которые мы получаем из запроса
beginElement: number = 0; // начальный номер публикации, с которого получаем список элементов
post_error: string; // результат выполнения запроса 0-успешно, 1-ошибка
constructor(public navCtrl: NavController, public http: Http, public loadingCtrl: LoadingController, public navParams: NavParams) {
this.selectedItem = navParams.get('item');
this.selectedType = navParams.get('type');
this.categoryId = '';
this.authorId = '';
this.title = 'Публикации';
if (this.selectedType == '1'){
this.title = this.selectedItem.category;
this.categoryId = this.selectedItem.id;
}
if (this.selectedType == '2'){
this.title = this.selectedItem.author;
this.authorId = this.selectedItem.id;
}
// Метод получения данных из запроса
// 0 - получаем данные с самого начала
// 1 - получаем СЛЕДУЮЩИЕ данные по порядку
this.loadData(0);
}
loadData(isNew) {
if (isNew==0){
// Первоначальные значения переменных
this.beginElement = 0;
this.countElement = 10;
// Создаем окно загрузки
let loadingPopup = this.loadingCtrl.create({
content: ''
});
// Показываем окно загрузки
loadingPopup.present();
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/postlist.php?begin='+this.beginElement+'&limit='+this.countElement+'&c='+this.categoryId+'&a='+this.authorId)
.timeout(20000) // Ставим лимит на получение запроса и прерываем запрос через 20 сек.
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.postlists = data.data; // Данные получены, записываем их
this.countElement = data.count; // Записываем кол-во полученных публикаций
this.post_error = "0"; // Результат - успешно
loadingPopup.dismiss(); // Убираем окно загрузки
}, 1000);
},
err => {
loadingPopup.dismiss(); // Убираем окно загрузки
this.post_error = "1"; // Результат - ошибка
}
);
}else{
// Увеличиваем начальную позицию номера публикации для последующего получения именно с нужной позиции
this.beginElement = Number(this.beginElement) + Number(this.countElement);
}
}
// Выполняется при пролистывании к последнему элементу списка
doInfinite(infiniteScroll) {
// Проверяем нужно ли выполнять запрос
// Если в предыдущем запросе мы получили 0 публикаций,
// значит больше не нужно выполнять запрос для получения СЛЕДУЮЩЕГО набора данных
if (this.countElement != 0){
this.loadData(1);
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/postlist.php?begin='+this.beginElement+'&limit='+this.countElement+'&c='+this.categoryId+'&a='+this.authorId)
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.postlists_new = data.data; // Записали новую порцию данных
this.countElement = data.count;
this.post_error = "0";
for (let i = 0; i < this.countElement; i++) {
this.postlists.push( this.postlists_new[i] ); // Добавили новые данные в основной массив публикаций
}
infiniteScroll.complete();
}, 1000);
},
err => console.error(err)
);
}else{
infiniteScroll.complete();
}
}
// Выполняется при потягивании списка вниз, когда список находится в верхнем положении
doRefresh(refresher) {
this.loadData(0);
setTimeout(() => {
refresher.complete();
}, 2000);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Postlist');
}
}
В файле postlist.html изменения коснутся только в части отображения заголовка:
<ion-header>
<ion-navbar color="clmain">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>{{title}}</ion-title>
</ion-navbar>
</ion-header>
В результате всех изменений теперь можно просматривать публикации выбранной категории и выбранного автора:
Публикации выбранной категории и выбранного автора
Осталось сделать последнюю страницу для отображения содержимого публикации. А именно: заголовок, дата, категория, автор, фото, краткое содержание, полное содержание.
Для этого создадим новую страницу командой:
ionic generate page post
Внесем изменения в файл app.module.ts. Добавим строку для импорта:
import { Post } from '../pages/post/post';
а также пропишем созданную страницу в секциях
declarations
и entryComponents
. ...
declarations: [
MyApp,
Postlist,
Categorylist,
Authorlist,
TabsPage,
Post
],
...
entryComponents: [
MyApp,
Postlist,
Categorylist,
Authorlist,
TabsPage,
Post
],
...
В файле post.ts напишем строчку для импорта класса
NavController
и объекта NavParams
import { NavController, NavParams } from 'ionic-angular';
Далее методика аналогичная: получаем данные публикации посредством запроса и отображаем их в нужном виде. Результат готовых измененных файлов:
post.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
Component({
selector: 'page-post',
templateUrl: 'post.html',
})
export class Post {
selectedItem: any;
postphotos: any;
post_category: any;
post_author: any;
post_author_id: any;
post_author_img: any;
post_title: any;
post_dat3: any;
post_intro_text: any;
post_full_text: any;
post_img: any;
post_is_photo: any;
post_error: string;
constructor(public navCtrl: NavController, public http: Http, public loadingCtrl: LoadingController, public navParams: NavParams) {
this.selectedItem = navParams.get('item');
this.loadData();
}
loadData() {
// Создаем окно загрузки
let loadingPopup = this.loadingCtrl.create({
content: ''
});
// Показываем окно загрузки
loadingPopup.present();
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/post.php?p='+this.selectedItem.id)
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.postphotos = data.data;
this.post_category = data.category;
this.post_author = data.author
this.post_author_id = data.author_id;
this.post_author_img = data.author_img;
this.post_title = data.title;
this.post_dat3 = data.dat3;
this.post_intro_text = data.intro_text;
this.post_full_text = data.full_text;
this.post_img = data.img;
this.post_is_photo = data.is_photo;
this.post_error = «0»;
loadingPopup.dismiss();
}, 1000);
},
err => {
loadingPopup.dismiss();
this.post_error = «1»;
}
);
}
// Выполняется при потягивании списка вниз, когда список находится в верхнем положении
doRefresh(refresher) {
this.loadData();
setTimeout(() => {
refresher.complete();
}, 2000);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Post');
}
}
import { NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/timeout';
import { LoadingController } from 'ionic-angular';
Component({
selector: 'page-post',
templateUrl: 'post.html',
})
export class Post {
selectedItem: any;
postphotos: any;
post_category: any;
post_author: any;
post_author_id: any;
post_author_img: any;
post_title: any;
post_dat3: any;
post_intro_text: any;
post_full_text: any;
post_img: any;
post_is_photo: any;
post_error: string;
constructor(public navCtrl: NavController, public http: Http, public loadingCtrl: LoadingController, public navParams: NavParams) {
this.selectedItem = navParams.get('item');
this.loadData();
}
loadData() {
// Создаем окно загрузки
let loadingPopup = this.loadingCtrl.create({
content: ''
});
// Показываем окно загрузки
loadingPopup.present();
// Получение данных, с указание URL-запроса и параметров
this.http.get('https://mysite.ru/post.php?p='+this.selectedItem.id)
.timeout(20000)
.map(res => res.json())
.subscribe(
data => {
setTimeout(() => {
this.postphotos = data.data;
this.post_category = data.category;
this.post_author = data.author
this.post_author_id = data.author_id;
this.post_author_img = data.author_img;
this.post_title = data.title;
this.post_dat3 = data.dat3;
this.post_intro_text = data.intro_text;
this.post_full_text = data.full_text;
this.post_img = data.img;
this.post_is_photo = data.is_photo;
this.post_error = «0»;
loadingPopup.dismiss();
}, 1000);
},
err => {
loadingPopup.dismiss();
this.post_error = «1»;
}
);
}
// Выполняется при потягивании списка вниз, когда список находится в верхнем положении
doRefresh(refresher) {
this.loadData();
setTimeout(() => {
refresher.complete();
}, 2000);
}
ionViewDidLoad() {
console.log('ionViewDidLoad Post');
}
}
post.html
<ion-header>
<ion-navbar color="clmain">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Содержание</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-refresher (ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingIcon="arrow-dropdown"
pullingText="Потяните для обновления"
refreshingSpinner="circles"
refreshingText="Обновление...">
</ion-refresher-content>
</ion-refresher>
<div *ngIf="post_error == '0'">
<h3></h3>
<div>
<div class="post-category">{{post_category}}</div>
<div class="post-dat">{{post_dat3}}</div>
</div>
<h3 class="post-title">{{post_title}}</h3>
<img *ngIf="post_is_photo != '1'" src="{{post_img}}" />
<h5 class="post-intro-text">{{post_intro_text}}</h5>
<div class="post-text" [innerHTML] = "post_full_text"></div>
<button ion-item>
<ion-avatar item-left>
<img [src]="post_author_img" />
</ion-avatar>
<ion-label class="author-title">{{post_author}}</ion-label>
</button>
</div>
<div *ngIf="post_error == '1'" style="text-align: center">
<ion-label>Ошибка при получении данных</ion-label>
<button ion-button (click)="loadData(0)" color="clmain" icon-left>
<ion-icon name="refresh"></ion-icon>
Обновить
</button>
</div>
</ion-content>
post.scss
page-post {
.post-title {
font-size: 19px !important;
white-space: inherit;
}
.post-intro-text {
font-size: 15px !important;
color: gray;
white-space: inherit;
}
.post-dat {
font-size: 14px !important;
color: gray;
white-space: inherit;
float: right;
text-align: right;
width: 50%;
}
.post-category {
font-size: 14px !important;
color: gray;
white-space: inherit;
float: left;
width: 50%;
}
.post-text {
font-size: 16px !important;
}
}
Теперь вспомним про метод
openPostPage()
, который вызывается в postlist.html у события click
.Этот метод позволит открыть страницу с содержимым публикации. Описываем метод в postlist.ts:
openPostPage(item) {
this.navCtrl.push(Post, { item: item });
}
а также импортируем страницу
Post
:import { Post } from '../post/post';
Проверяем результат всех внесенных изменений и видим страницу, которая открывается при нажатии на публикацию в списке.
Содержание страницы
Вот пожалуй и весь основной функционал, который позволит листать публикации и читать их содержимое, данные для которых берутся с сайта посредством Http запросов.
Исходники данного примера-проекта можно посмотреть на GitHub