От автора: Angular 4 или теперь уже просто Angular – это мощный фреймворк для разработки адаптивных веб-приложений. Angular – хороший фреймворк, однако его можно очень долго изучать. У новичков возникают сложности, когда они в первый раз встречаются с Angular API, и это нормально. Зачастую это приводит к тому, что разработчики отказываются от фреймворка, выбирая что-то, с чем уже знакомы. Angular действительно не прост в освоении, это всеобъемлющий фреймворк, способный создавать масштабируемые, расширяемые и долгоподдерживаемые приложения. Он создает модульные проекты, которые придерживаются лучшим практикам хорошего дизайна. Angular намного сложнее, но в равной степени и лучше любого другого фреймворка. Потратьте время на изучение Angular – это окупаемые инвестиции, и оплаты будут расти экспоненциально.
Одна из первых задач, которую разработчикам нужно понять, это как получать данные с веба. В Angular для этого есть конкретная процедура. Вам может показаться, что кода будет очень много, но в конечном итоге это будет более качественный код, который потом будет легче тестировать, отлаживать и поддерживать. Этот процесс можно разбить на маленькие шаги для лучшего усвоения.
Шаг 1 (необязательный). Где вы храните конфиденциальные данные?
Шаг 1 необязательный, но крайне рекомендуется к выполнению. Защитить информацию можно несколькими способами. Простой, предпочтительный метод – ввести информацию в файл environment.ts, расположенный в папке app/environments. Если вы работаете с Angular CLI (лучше работать в ней), то Angular уже создал const-объект под названием environment. Просто добавьте в этот объект пару «name:value», где name ссылается на секретное «value». Здесь мы будем хранить url api, любые ключи и другие данные. Например, можно добавить url api, чтобы потом мы могли на него ссылаться.
1 2 3 4 |
export const environment = { production: false api_url: '//jsonplaceholder.typicode.com/posts/' }; |
Не забудьте добавить app/environments/environment.ts в файл .gitignore, чтобы не проверять его в исходном коде!
Шаг 2 (необязательный). Как вы моделируете данные?
Шаг 2 необязательный, но крайне рекомендуется к выполнению. Приложения работают с данными. Данные могут представлять собой изображения, посты, твиты, да что угодно. Прежде чем работать с данными, будь то в режиме RESTful или нет, необходимо создать модель. Модель – отличный способ моделирования и определения данных. Кроме того, модели предоставляют схему для последующего сохранения своих экземпляров в базе данных. Предпочтительно создавать модель в Angular через интерфейс Typescript. Интерфейс описывает то, как должны выглядеть данные. Например, если определить интерфейс Post, он может быть примерно таким.
1 2 3 4 5 6 |
export interface Post { userId: number; id: number; title: string; body: string } |
Чтобы облегчить доступ к файлу, его можно поместить в папку app.
Шаг 3. Создание компонентов
Для обработки данных необходимо создать компоненты. Количество компонентов и их связи зависят от факторов, которые определяются для каждого приложения самостоятельно. Например, мы создадим 2 компонента, родительский и дочерний. В Angular CLI можно использовать сокращения: g – сгенерировать, c – компонент. Таким образом, команды превращаются в ng g c post и ng g c post-list. Теперь у нас есть 2 компонента для обработки данных. С помощью Http-модуля Angular можно вытягивать placeholder-данные из наших компонентов, однако это нарушает принцип единственной ответственности. Вместо этого мы будем следовать лучшим практикам Angular Style Guide для получения данных через сервис.
Шаг 4. Создание сервиса для получения данных
Вернемся опять к Angular CLI и создадим сервис. Сервис можно создать с помощью символа s: ng g s post. Команда создаст файл post.service.ts импортирует PostService в app.module.ts. Команда также выдаст предупреждение о том, что сервис успешно создан, но к нему не определен ни один провайдер. Это предупреждение напоминает нам, что для работы с нашим сервисом его нужно добавить в массив providers в файле app.module.ts. Спасибо за напоминание, Angular!
1 2 3 4 5 6 7 8 |
// app.module.ts ... // для краткости остальной код удален providers: [PostService], // added PostService to providers array bootstrap: [AppComponent] }) export class AppModule { } |
Прежде чем мы начнем работать над нашим сервисом, давайте создадим файл rxjs-operators.ts, чтобы импортировать операторы Reactive Extensions, которые нам понадобятся. Начнем файл с импорта 3 операторов:
1 2 3 4 |
// rxjs-operators.ts import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; |
Теперь нужно импортировать всего одну строку в наш файл post.service.ts, чтобы получить доступ ко всем трем операторам – довольно изящно.
1 2 3 4 5 6 7 8 9 10 |
// post.service.ts import { Http, Response } from '@angular/http'; import { Injectable } from '@angular/core'; import {Post} from './post'; import { Observable } from 'rxjs/Observable'; import './rxjs-operators'; import { environment } from './../environments/environment'; @Injectable() export class PostService { } |
Обратите внимание, что над PostService прописан декоратор @Injectable, также мы импортировали Http и Response модули из @angular/http. Мы импортировали наш файл окружения, который настроили в первом шаге, чуть позже мы его используем. Также мы импортировали Observable из rxjs/Observable. Angular модуль Http теперь использует observables по умолчанию, т.е. когда мы используем метод GET, мы получаем в качестве ответа observable. Если вы не знаете, что это такое, почитайте. Они нативно представлены в Angular, а скоро будут и в ECMAScript. Они обеспечивают методы составления асинхронных потоков данных, на которые можно подписаться, объединить, манипулировать и т.д. Почитайте на Github.
Наш PostService будет собирать данные по вебу, а конструктор должен создавать объект Http-модуля.
1 |
constructor(private http: Http) { } |
Мы создадим метод getData, который будет посылать GET-запрос на сервер и обрабатывать ответ.
1 2 3 4 5 |
getData(): Observable<Post[]> { return this.http.get(this.url) .map(this.extractData) .catch(this.handleError); } |
Первая строка определяет функцию getData() типа Post. Не забывайте, что мы ранее создали интерфейс post и импортировали Post в верх файла. Мы возвращаем this.http.get(‘url’). Сервис возвращает ответ, с которым мы будем работать в нашем компоненте. Запись http – объект Http, get – один из методов Http. Далее мы используем оператор .map и передаем в функцию extractData. Передавать функцию в map необязательно. Если этого не делать, функцию getData() будет короче и проще для тестирования и чтения. Мы могли вообще просто передать контент extractData() в оператор map.
1 2 3 4 |
private extractData(res: Response) { let body = res.json(); return body || []; } |
В объекте ответа есть json-метод, который конвертирует возвращенную строку в соответствующее объектное представление, хранящееся в памяти. Функция extractData() также возвращает body, который является json-строкой или пустым массивом. Наша функция getData() использует оператор .catch() для обработки ошибок, передавая их в функцию handleError():
1 2 3 4 5 6 |
private handleError(error: any) { let errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error'; console.error(errMsg); return Observable.throw(errMsg); } |
Мы получили PostService, который вытягивает данные и обладает методами для обработки ответов и ошибок. Весь сервис post должен выглядеть так:
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 |
// post.service.ts import { Injectable } from '@angular/core'; import {Post} from './post'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import './rxjs-operators'; import { environment } from './../environments/environment'; @Injectable() export class PostService { private url = environment.api_url; // STEP ONE constructor(private http: Http) {} getData(): Observable<Post[]> { return this.http.get(url) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { let body = res.json(); return body || []; } private handleError(error: any) { let errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error'; console.error(errMsg); // log to console instead return Observable.throw(errMsg); } } |
Шаг 5. Использование PostService
Мы создали PostService для сбора данных, теперь давайте задействуем его. Откройте файл post-list.component.ts и импортируйте обязательные файлы.
1 2 3 |
import { Component, OnInit } from '@angular/core'; import { PostService } from '../post.service'; import { Post } from '../Post'; |
Мы импортируем хук жизненного цикла OnInit, наш Post интерфейс и PostService. Хук onInit можно использовать для запуска любого кода инициализации для нашего компонента. Это позволяет удалить код инициализации из конструктора. Конструктор должен быть как можно меньше, чтобы снизить зависимости и облегчить тестирование.
1 2 3 4 5 |
export class PostListComponent implements OnInit { private posts: Post[] = []; private errorMessage: any = ''; constructor(private postService: PostService) { } |
Обратите внимание, что мы также создали 2 свойства в PostListComponent – свойство posts типа Post и errorMessage типа any. Далее мы вставляем наш PostService в конструктор с помощью создания объекта postService. Сейчас наш PostService будет вытягивать данные, и этот сервис мы вставили в наш компонент. Все остальное для подписки на observable, чтобы мы могли получать json и отображать его в представлении. Мы определили метод getPosts():
1 2 3 4 5 6 |
getPosts() { this.postService.getData() .subscribe( posts => this.posts = posts, error => this.errorMessage = <any>error); } |
Метод getPosts() с помощью объекта postService вызывает getData. Помните, что getData определен в файле post.service.ts и получает данные с сервера через http.get(‘URL’). Далее мы подписываемся на observable и определяем наши свойства posts и error.
Шаг 6. Вызов PostService в момент инициализации компонента
После загрузки страницы сервис сразу должен вытягивать данные. Для этого мы используем хук жизненного цикла OnInit и вызываем getPosts. Полный код PostListComponent:
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 |
// post-list.component.ts import { Component, OnInit } from '@angular/core'; import { PostService } from '../post.service'; import { Post } from '../Post'; @Component({ selector: 'app-post-list', template: ` <div> <ul class="items"> <li *ngFor="let post of posts"> <span>{{post.title}}</span> </li> </ul> </div> `, styleUrls: ['./post-list.component.scss'] }) export class PostListComponent implements OnInit { private posts: Post[] = []; private errorMessage: any = ''; constructor(private postService: PostService) {} ngOnInit() { this.getPosts(); } getPosts() { this.postService.getData() .subscribe( posts => this.posts = posts, error => this.errorMessage = <any>error); } } |
В шаблоне мы с помощью директивы *ngFor пробегаемся в цикле по posts и печатаем их заголовки, интерполируя их свойство title. Не забывайте, что posts имеет тип Post, а там мы определили свойство title. Селектор app-post-list нужно будет добавить на страницу app.component.html.
1 2 3 |
// app.component.html <h1> Post Titles </h1> <app-post></app-post> |
Запустите в командной строке ng s –o, чтобы запустить приложение на сервере разработки на порту 4200. Опция –o откроет приложение в браузере по умолчанию. После загрузки приложения вы должны увидеть список постов на странице:
Если вы работаете в Chrome, можете установить расширение Augury. С ним очень удобно работать с приложениями на Angular. Загрузить его можно из магазина Chrome. Нажмите правой кнопкой мыши на странице и проинспектируйте элемент, чтобы открыть панель разработчика. Вкладка Augury будет самая правая. Для появления вкладки может понадобиться перезапуск браузера.
Обратите внимание на то, как Augury отображает дерево компонентов слева. Кликнув на PostListComponent, можно узнать текущее состояние. У нас есть объект posts – массив из 100 постов со свойствами userId, id, title и body. Augury очень удобен при получении данных с API с заранее известной структурой. Например, теперь мы знаем, что body поста можно отобразить с помощью {{post.body}}. Репозиторий приложения найдете на github. Не стесняйтесь, играйте с ним, попробуйте получить доступ к разным частям ответа. Попробуйте отобразить только первые 10 заголовков постов. Получите доступ к первым трем заголовкам постов с их body. Надеюсь, вам понравилась статья, и вы узнали из нее что-то новое для себя.
Автор: Frederick John
Источник: //medium.com/
Редакция: Команда webformyself.