
Есть у меня приложение, написанное на 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 и объекта NavParamsimport { 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
