Как сделать поиск пользователей по Github используя Angular

  • Tutorial
image

Эта статья является ответом на:


Целью статьи является:

  • показать, что на Angular тоже можно быстро написать простое приложение, хотя это не его основной конек,
  • показать плюсы приложения на Angular.

Целью статьи НЕ является:

  • разжигание очередного холивара.

Всех, кому интересно, прошу под кат.

Подготовка


Для работы с Angular необходимо установить глобально angular CLI

npm install -g @angular/cli

Создаем новое приложение
ng new github-ui
cd github-ui

Сразу создадим комопоненты пользователя и ошибки, и сервис для получения данных с github

ng generate component components/user
ng generate component components/error
ng generate service services/github

И подключим их в основной модуль приложения.

Так-же подключим модули HttpClient (для работы с http запросами) и ReactiveForms (для работы с формами).

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

import { AppComponent } from './app.component';
import { UserComponent } from './components/user/user.component';
import { ErrorComponent } from './components/error/error.component';
import { GithubService } from './services/github.service';

@NgModule({
  declarations: [AppComponent, UserComponent, ErrorComponent],
  imports: [BrowserModule, ReactiveFormsModule, HttpClient],
  providers: [GithubService],
  bootstrap: [AppComponent]
})
export class AppModule {}

Модели данных


Т.к. Angular использует Typescript, а Typescript дает нам типизацию, то хорошей практикой является описывать модели данных.

Это дает следующие плюсы:

  • удобный автокомплит при работе с приложением,
  • проверка совпадения типов на стадии компиляции,
  • дает другим разработчикам понять с какими данными они работают.

models/user.model.ts
export class User {
  login: string;
  id: number;
  node_id: string;
  avatar_url: string;
  gravatar_id: string;
  url: string;
  html_url: string;
  followers_url: string;
  following_url: string;
  gists_url: string;
  starred_url: string;
  subscriptions_url: string;
  organizations_url: string;
  repos_url: string;
  events_url: string;
  received_events_url: string;
  type: string;
  site_admin: boolean;
  name: string;
  company: string;
  blog: string;
  location: string;
  email: string;
  hireable: string;
  bio: string;
  public_repos: number;
  public_gists: number;
  followers: number;
  following: number;
  created_at: string;
  updated_at: string;
}


Сервис для получения данных


Работу с запросами на сервер в Angular принято выносить в сервисы.

В созданный ранее сервис добавим метод для получения данных пользователя.

services/github.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';

@Injectable()
export class GithubService {

  // Подключаем модуль для работы с http
  constructor(private http: HttpClient) {}

  // Метод для запроса пользователя
  getUser(name: string): Observable<User> {
    const url = `https://api.github.com/users/${name}`;
    return this.http.get<User>(url);
  }
}

Поиск пользователя


В Angular из коробки встроен RxJs. С помощью него и модуля работы с формами мы можем подписаться на изменение значения контрола, и получить данные пользователя.

app.component.html

<div class="container"
  [class.ready]="!!user">
  <input [formControl]="findControl"
    placeholder="GitHub username" />

  <app-user *ngIf="user"
    [user]="user"></app-user>

  <app-error *ngIf="error"></app-error>
</div>

app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { GithubService } from './services/github.service';
import { User } from './models/user.model';
import { filter, switchMap, debounceTime, catchError } from 'rxjs/operators';
import { EMPTY } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  // Контрол для поиска пользователей
  findControl = new FormControl();
  // Ошибка поиска
  error: boolean = false;
  // Найденный пользователь
  user: User = null;

  // Подключение githubService для поиска пользователя
  constructor(private githubService: GithubService) {}

  // Хук инициализации компонента
  ngOnInit() {
    this.findControl.valueChanges
      .pipe(
        // Фильтруем если введено меньше двух символов
        filter(value => value.length > 2),
        // Ставим задержку одну секунду
        debounceTime(1000),
        // Запрашиваем данные пользователя
        switchMap(value =>
          this.githubService.getUser(value).pipe(
            // Обработка ошибок
            catchError(err => {
              this.user = null;
              this.error = true;
              return EMPTY;
            })
          )
        )
      )
      // Получение данных
      .subscribe(user => {
        this.user = user;
        this.error = false;
      });
  }
}

Остальные компоненты


Остальные компоненты являются «глупыми», т.е. не содержат в себе логики, а только отображают полученные данные.

user component
<div class="github-card user-card">
  <div class="header User"></div>
  <a class="avatar"
    [href]="'https://github.com/'+user.login">
      <img [src]="user.avatar_url+'&s=80'" [alt]="user.name" />
    </a>
  <div class="content">
    <h1>{{user.name}}</h1>
    <ul class="status">
      <li>
        <a [href]="'https://github.com/'+user.login+'?tab=repositories'">
            <strong>{{user.public_repos}}</strong>Repos
          </a>
      </li>
      <li>
        <a [href]="'https://gist.github.com/'+user.login">
            <strong>{{user.public_gists}}</strong>Gists
          </a>
      </li>
      <li>
        <a [href]="'https://github.com/'+user.login+'/followers'">
            <strong>{{user.followers}}</strong>Followers
          </a>
      </li>
    </ul>
  </div>
</div>

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { User } from '../../models/user.model';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
  @Input()
  user: User;
}


error component
<div class="error">
  <h2>Oops!</h2>
  <b>
    User not found.
  </b>
  <p>Please try searching again.</p>
</div>

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-error',
  templateUrl: './error.component.html',
  styleUrls: ['./error.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ErrorComponent {}


Плюсы использования Angular


  • отделение получения данных от работы с ними,
  • отделение шаблона от логики,
  • четкая и понятная масштабируемая структура,
  • встроенные модули для работы с формами и сервером,
  • встроенный RxJs для ассинхронной работы,
  • строгая типизация проверка на наличие ошибок при компиляции.

Исходный код


github
live demo

Выводы


Как было показано выше любое приложение (особенно небольшое) можно написать используя разные библиотеки, фреймворки или чистый JS.

Более важным является знание инструментов которые вы используете, и понимание на сколько они подходят в данной ситуации.

Всем успехов в изучении и чистого кода!
Поделиться публикацией
Комментарии 28
    +1
    Уже потерял надежду что увижу статью — " Как сделать ПРАВИЛЬНЫЙ поиск пользователей по GitHub используя с использованием GraphQL". Ведь Github переписал свое api GraphQL…
      0
      Когда речь заходит об Ангуляре, мне первым делом всегда интересно — сколько мегабайт килобайт получился итоговый бандл?
        +1
        Кстати, зря вы так. Ангуляр имеет AoT-компиляцию и движется в направлении оптимизации итоговых бандлов.
          –2

          Оптимизирующий сборщик – это, конечно, хорошо, но не особо помогает, когда фреймворк по своему дизайну требует много писанины. Вот app.module.ts из статьи:


          app.module.ts
          import { BrowserModule } from '@angular/platform-browser';
          import { NgModule } from '@angular/core';
          import { ReactiveFormsModule } from '@angular/forms';
          import { HttpClient } from '@angular/common/http';
          
          import { AppComponent } from './app.component';
          import { UserComponent } from './components/user/user.component';
          import { ErrorComponent } from './components/error/error.component';
          import { GithubService } from './services/github.service';
          
          @NgModule({
            declarations: [AppComponent, UserComponent, ErrorComponent],
            imports: [BrowserModule, ReactiveFormsModule, HttpClient],
            providers: [GithubService],
            bootstrap: [AppComponent]
          })
          export class AppModule {}

          Весь этот код попадает в конечный бандл и смысл этого не очень понятен.

            +3
            Безусловно Angular будет немного уступать некоторым другим библиотекам в размере бандла.

            Но целью этой статьи было показать, что он пригоден для быстрого написания небольших приложений и у него есть свои плюсы.
            В частности четкое разделение ответственности, масштабируемость и строгая типизация.

            А лишние 100кб бандла в наше время не самая большая проблема. ИМХО
              0
              Хочу уточнить — 100кб это с учетом gzip или нет?
              Сейчас часто любят манипулировать размерами библиотек с учетом сжатия.
                0
                Без учета gzip.

                C min+gzip. Ядро angular занимает примерно 60Кб.
                image

                Собранное приложение.
                image
                  0

                  Это компилировалось с Ivy или нет?

              0
              Разделение ответственности, масштабируемость и т.п. есть у любого фреймворка.

              А что уникального есть у Angular, что он стоит дополнительных килобайт в бандле?
                +3
                У Ангуляра «из коробки» есть модули:
                — роутинга,
                — http,
                — работа с формами,
                — подключен RxJs для удобства работы с ассинхронными операциями.

                Есть стайлгайд в котором описано чем должны заниматься сервисы, пайпы и компоненты.

                Все это от одной команды разработчиков.
                Все это разворачивается одной коммандой.
                ng new my-app

                Повторюсь еще раз, что целью является не доказательство того, что Angular меньше, а то что у Angular есть другие достоинства и на нем можно быстро писать небольшие приложения которые ничем (кроме лишних 40кб gzip) не уступают React, Vue и др.

                  0
                  Да, Angular — это «all inclusive» фреймворк, но это момент также на любителя. Я например предпочитаю standalone решения, которые всегда можно выпилить и впилить другое, если будет необходимость.

                  Повторюсь еще раз, что целью является не доказательство того, что Angular меньше, а то что у Angular есть другие достоинства и на нем можно быстро писать небольшие приложения которые ничем (кроме лишних 40кб gzip) не уступают React, Vue и др.

                  Хорошая попытка, но ИМХО не очень удачная. Для больших приложения все это имеет смысл, но показать достоинства для быстрого написания небольших приложений, я думаю все же не получилось.

                  Однако, все равно спасибо за статью! Ждем ответ с Vue ))))))
                    +2
                    А потом когда в компании несколько проектов на реакте, и каждый лепит свои костыли… это очень полезно для проектов.
                      –1
                      Хорошая попытка, но ИМХО не очень удачная. Для больших приложения все это имеет смысл, но показать достоинства для быстрого написания небольших приложений, я думаю все же не получилось.

                      Почему, кода много? Так оно все автоматически генерируется при помощи команд для cli.
                        0
                        Ниже по треду уже обсудили. Если резюмировать: у нас разное понимание что такое «много кода» применительно к задаче.
                          0
                          Можно резюмировать так. Архитектура и фреймворк — это фундамент вашего здания. Если планируется построить небоскреб, то и фундамент должен быть более качественным и глубоким, если это одноэтажный домик, то тогда можно обойтись и минимальными затратами.

                          Перенося это с метафор на разработку. Если у вашего мини-приложения или стартапа большие амбиции и перспективы роста, то стоит взглянуть на Angular, т.к. он даст много плюсов в перспективе.

                          В противном случае есть более подходящие инструменты.
                            +2
                            Обычно, какие бы не были большие амбиции и перспективы роста у стартапа, он умрет, так и не реализовав их, если затянуть разработку прототипа и MVP. У Angular есть все шансы посодействовать в этом. Кроме того, ничто не мешает строить хорошую архитектуру без навязывания ее со стороны фреймворка. Это вообще слабо связанные вещи.

                            Поймите меня правильно, я считаю, что Angular — это прекрасный инструмент для крупных и очень крупных проектов, особенно энтерпрайза. Особенно, если большая часть команды пришла из типизированных языков с классическим ООП, типа Java/C# и т.п. Такая команда будет действительно чувствовать себя как рыба в воде, используя Angular. Я считаю, что на сегодняшний день, это сугубо его ниша и ни React, ни Vue и уж тем более Svelte, не дотягивают до нее. Сколькими бы redux'ами не обмазались.

                            React и Vue довольно хороши в проектах средних и чуть больших. У них обширная инфраструктура и тулинг. Svelte же хорош в небольших и средних проектах, особенно требовательных к производительности и размеру. Наверное, я бы не стал писать на нем крупный проект. Однако какие-то отдельные компоненты, требовательные к перформансу, можно реализовать на Svelte и бесшовно использовать и в большом проекте. Благо, он это позволяет делать, так как не имеет рантайма.

                            В общем, пусть у каждого будет своя нише, в которой он хорош, а у нас с вами будет большой выбор)))
                              +2

                              А у меня сплошные разочарование от предложений ангуляра по архитектуре. После некоторого опыта возникло ощущение, что купил только этикетку. Если убрать из стандартного набора модули роутера и http, то ангуляр мало чем отличатся от того же react или vue.


                              Под этикеткой я понимаю сразу несколько вещей: консольная утилита, монорепозитории, генераторы, тесты и все это тебе достается из коробки, и это конечно все круто, и экономит время, но к архитектуре относится слишком косвенно.


                              Еще под этикеткой скрывается длинный стайл-гайд, где примерно половина состоит из воды и половина из соглашений по именованию и по длине/типу отступов. Они пишут про некие принципы, типа LITF, или про буквы из SOLID, это то, что я назвал водой, практические советы, по этим принципам, вовсе сводятся к тому чтобы ограничить количество строк кода в функциях/файлах или количество файлов в папочках, это уже какой-то бред идеологический. Но немного про архитектуру там все же отметил.


                              В подавляющем большинстве от ангуляра используются компоненты, сервисы и модули. Модуль это структурный объект. Компонент тоже имеет сильно ограниченную зону ответственности. Для всего остального есть сервис, молоток для всех задач. С помощью декораторов, конечно можно использовать все что угодно, тут нет ограничений, но и архитектурные решения в этом случае полностью перекладываются.


                              В стайл-гайде советуют разбивать код по семантическому признаку, например в один модуль можно объединить компонент списка комментариев, компонент комментария, сервис для загрузки/отправки и уникальные зависимости, или можно список комментариев вместе со списком постов вынести отдельно, в общем случае как взбредет. Если зависимость используется для нескольких семантических модулей, то ее следует вынести в общий модуль. И в случае необходимости, например процесса инициализации для всего проекта, предлагается сделать модуль ядра приложения. Возможно я что-то упустил, но это основные моменты, что у меня отложилось касательно предложений по архитектуре. Соглашения по именованию, по стилю и следование различным принципам, это конечно хорошо и нужно, но с этим справляются и линтеры. А советы по архитектуре, что я смог выделить, будут сносно работать, например, для библиотеки компонентов, желательно изолированных.


                              Буквально самые основные моменты.


                              HttpClient не особо отличается от банального fetch. А для банальных REST запросов, предлагается сделать сервис, и на каждую букву из CRUD написать метод. Положить этот файлик нужно либо в модуль подходящий по семантике, либо в общий модуль. Такое решение, даже для их официального туториала, плохо подходит. Клиент-серверное взаимодействие очень типовая задача. В гайде про это пара слов.


                              Еще для меня типовой является задача построением лайаутов, страницами, их компоновкой, привязкой к роутеру и т. д. Обычно хочется отделить это от компонентов. В целом, для всего этого есть инструменты, но согласованное предложение по организации кода явно было бы не лишним.


                              Работать с состоянием приложения предлагается, понятно, через сервисы и rxjs. Тут я даже не удивлен, что redux для ангуляр набирает популярность.


                              Модуль, куда они предлагают выносить все общие части приложения, я убежден, что уже на этапе создания папки, превращается в технический долг. Если в команде 5+ программистов, то уследить за тем что там происходит, будет очень сложно.


                              В итоге надо либо искать готовые решения, либо создавать свои, уже на самых ранних этапах разработки, и все это без рекомендаций со стороны разработчиков. Даже бэкбон в свое время, гораздо яснее отвечал на большее количество вопросов, касательно архитектуры.


                              Из опыта: мы с коллегой, независимо, примерно в одно время, разрабатывали с нуля два разных внутренних проекта, на этом ангуляре, это был условно наш первый опыт работы на нем. В плане архитектуры, сейчас это два абсолютно разных проекта.

                  +1
                  Так то я автор статьи с, наверное, самой минималистичной реализацией данной задачи на Svelte. И хотя я всегда топлю за то, что код должен быть простым, фреймворк максимально ненавязчивым, а бандл легковесным, все же я понимаю что Angular — это фреймворк из другой весовой категории в принципе. Он хорош для суровых энтерпрайзов и ребят пришедших во фронтенд из Java или .NET. Это его ниша и он, полагаю, в ней хорош. Так что давайте не будем судить так строго.
                    +1

                    Я не понимаю, зачем писать лишний код там, где это не имеет никакого смысла. Есть определение модуля:


                    @NgModule({
                      declarations: [AppComponent, UserComponent, ErrorComponent],
                      imports: [BrowserModule, ReactiveFormsModule, HttpClient],
                      providers: [GithubService],
                      bootstrap: [AppComponent]
                    })
                    export class AppModule {}

                    Здесь создается пустой класс лишь для того чтобы было куда навесить декоратор. Зачем так сделано, почему нельзя было сделать NgModule просто функцией? Создается лишний вес в бандле на ровном месте.

                      0
                      Модуль angular, это всего-лишь граф из которых состоит такая структура, как «приложение angular». Это приложение могло быть ui-библиотекой, которую подключили бы в виде зависимости к другому приложению-модулю. Если возникнет вопрос «для чего вообще нужно подключать модули таким образом», то ответ будет прост — для DI.
                        0

                        Зачем модули нужны в принципе, я понимаю. Вопрос был про их громоздкий синтаксис. Есть ли ситуации когда класс AppModule оказывается не пустым? А если он всегда пустой, то зачем он вообще нужен?

                          0

                          Например, в момент подключения модуля, как минимум, вызывается конструктор, в нем можно проверить текущее окружение и подстроиться под него. Или, например, в RouterModule содержатся статические методы, которые на основе параметров формируют декларацию подключаемого модуля. Но причины применения декоратора, на пустой класс я вижу немного в другом.


                          Мета-программирование в ангуляре является частью общей концепции. Исключительно на основе мета-данных, фреймворк связывает части приложения. В коде ангуляровских стандартных и сторонних библиотек — модули, компоненты, сервисы и т. д. используют декораторы для мета-описания, так как это единственный способ обеспечения связей. Совокупность всех декораторов образует вполне конкретный интерфейс и единый механизм описания связей. Как результат, любые связи в приложении можно конфигурировать практически без ограничений, например, одной строчкой в мета-описании модуля, можно подменить сервис в ядре фреймворка и в этом нет ничего криминального.


                          Понятно, что сами по себе декораторы, это лишь синтаксический сахар, и код что за ними скрывается, может иметь множество алтернатив. Но помимо решения технической задачи, декораторы формируют способ взаимодействия пользовательского кода и фреймворка, формируют один из базовых принципов. Я думаю, пустой класс модуля, допустимый компромисс, но согласен, что выглядит это коряво.

              0
              Но целью этой статьи было показать, что он пригоден для быстрого написания небольших приложений и у него есть свои плюсы.

              Боюсь, вы лишь показали, что это не так. Слишком много кода для такой простой задачи. Пожалуй, я бы не рекомендовал Angular для средних и небольших приложений.

              В частности четкое разделение ответственности, масштабируемость и строгая типизация.

              Все, кроме строго типизации, это вопросы архитектуры, а не фреймворка. Да и типизация там от TS, который напрямую с Angular как бы не связан. Кроме того строгая типизация — это штука на любителя и опять же редко приносит реальную пользу на небольших и средних проектах.

              А лишние 100кб бандла в наше время не самая большая проблема. ИМХО

              Пожалуй это не так.

                0
                Все, кроме строго типизации, это вопросы архитектуры, а не фреймворка. Да и типизация там от TS, который напрямую с Angular как бы не связан. Кроме того строгая типизация — это штука на любителя и опять же редко приносит реальную пользу на небольших и средних проектах.

                Имел в виду, что Angular предоставляет готовые архитектурные решения, и если вы пришли с одного angular проекта на другой, то они скорей всего будут похожи. И там не будет очень плохого кода.

                К примеру в моей практике был случай когда по наследству перешел проект на нативном JS в котором был один файл на 9000 строк и метод render более 500 и единственным возможным решением в этом аду было вставление очередного if/else. И все это выросло из «маленького проекта в котором не нужна архитектура».

                Поэтому если вы пишите TODO-app, то Angular действительно не для вас, а если это стартап который предположительно разростется до большого приложения, то стоит задуматься.

                Типизация в Angular благодаря TS, но сам Angular написан на TS поэтому большинство приложений тоже написаны на TS.
                  0
                  Боюсь, вы лишь показали, что это не так. Слишком много кода для такой простой задачи. Пожалуй, я бы не рекомендовал Angular для средних и небольших приложений.

                  бОльшая часть этого кода это каркас сгенерированный angular-cli. А код написаный разработчиком это: верстка 3х компонентов и запрос на сервер, и подписка на изменение инпута.
                    0
                    Хм, а модель вам тоже angular-cli описал? Еще раз глянул код из статьи и хочу отдельно отметить, что я под капот ваших сорцов если что не смотрел. Вывод сделал исходя лишь из кода, представленного в самой статье. И он реально многословен))) ± такой же многословный как код оригинальной статьи c React/Rx/Recompose. Если в сорцах еще что-то есть, то сочувствую. Даже с angular-cli…
                      0
                      В данной ситуации можно было сделать тип any, но описание моделей считается хорошей практикой. В данном случае я взял её из респонса ответа сервера, либо её можно брать из свагера.

                      Но безусловно в любом случае в Angular будет больше чем в SvelteJS ))

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое