Занятие 9.

сервисы

и Dependency injection

SIS.Angular.2018

Михаил

Павлов

План занятия

1) Паттерн Dependency Injection(DI)

2) Реализация DI внутри Angular

3) Сервисы

4) Практика

Пример самостоятельного создания зависимостей

class Car {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.HORSE);
    this.tires = new Tires(TireTypes.WOOD_WHEEL);
  }
}

Интерфейсы

this.myCar = new Car();

Создаем машину (снаружи)

После компиляции...

Спустя некоторое время

class SimpleCar {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.HORSE);
    this.tires = new Tires(TireTypes.WOOD_WHEEL);
  }
}
class FantasyCar {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.DRAGON);
    this.tires = new Tires(TireTypes.MAGIC_WHEEL);
  }
}
this.car1 = new SimpleCar();
this.car2 = new FantasyCar();

Создание экземпляров:

Спустя еще некоторое время

Появляется новый тип двигателя

Нужно вручную обновить каждый класс

class SimpleCar {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.HORSE);
    this.tires = new Tires(TireTypes.WOOD_WHEEL);
  }
}
class FantasyCar {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.DRAGON);
    this.tires = new Tires(TireTypes.MAGIC_WHEEL);
  }
}
class CarWith2Horses {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.DOUBLE_HORSE);
    this.tires = new Tires(TireTypes.WOODEN_WHEEL);
  }
}
class WinterCar {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.POLAR_BEAR);
    this.tires = new Tires(TireTypes.SNOW_RUNNER);
  }
}

Итого

1. Получаем много ручной работы (долго)

2. Много изменений в коде (потенциальные баги)

3. Код обладает сильным зацеплением

(сложнее поддерживать)

Зацепление vs Связность

Полезные ссылки по связности и зацеплению

1) Связность кода

https://ru.wikipedia.org/wiki/Связность_(программирование)

2) Зацепление

https://ru.wikipedia.org/wiki/Зацепление_(программирование)

Как быть?

Поможет Dependency Injection!

Dependency Injection

Шутка :) Этого слайда не будет

Dependency Injection

Ключевые моменты паттерна Dependency Injection

1) Зависимостями ( dependencies) являются сервисы или объекты, которые требуются классу для работы

 

2) Суть подхода в получении зависимостей извне, вместо того, чтобы создавать экземпляры зависимостей самому

Пример без Dependency Injection

class Car {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.HORSE);
    this.tires = new Tires(TireTypes.WOOD_WHEEL);
  }
}
this.myCar = new Car();

Пример с использованием Dependency Injection

class Car {

  constructor(
      public engine: IEngine, 
      public tires: ITires) {
  }
}
this.myCar = new Car(
                   new Engine(EngineTypes.HORSE), 
                   new Tires(TireTypes.WOOD_WHEEL));

Пример с использованием Dependency Injection

this.myCar = new Car(
                   new Engine(EngineTypes.HORSE), 
                   new Tires(TireTypes.WOOD_WHEEL));
class Car {

  constructor(
      public engine: IEngine, 
      public tires: ITires) {
  }
}
class Car {

  public engine: IEngine;
  public tires: ITires;

  constructor(engine: IEngine, tires: ITires) {
    this.engine = engine;
    this.tires = tires;
  }
}

Спустя некоторое время

class Car {

  constructor(
      public engine: IEngine, 
      public tires: ITires) {
  }
}
class FantasyCar {

  public engine: IEngine;
  public tires: ITires;

  constructor() {
    this.engine = new Engine(EngineTypes.DRAGON);
    this.tires = new Tires(TireTypes.MAGIC_WHEEL);
  }
}
this.car1 = new Car(
                   new Engine(EngineTypes.HORSE), 
                   new Tires(TireTypes.WOOD_WHEEL));

this.car2 = new Car(
                   new Engine(EngineTypes.DRAGON), 
                   new Tires(TireTypes.MAGIC_WHEEL));

Создание экземпляров

Класс

После компиляции...

Итого

1. Изменяем реализацию зависимости, нет необходимости менять сам класс (класс ожидает что-то, удовлетворяющее интерфейсу)

2. Изменения происходят в точке создания класса (затрагивают меньшую область)

3. Код стал менее зацепленным за счет использования внешних зависимостей (легче заменять отдельные компоненты системы)

Реализация DI внутри Angular

!Важно!

В сервисах должна находиться вся логика приложения, не связанная с отображением данных в компоненте

Не держать в сервисах данные о состоянии (стейты), это плохая практика

Пример DI внутри Angular

import { Injectable } from '@angular/core';

@Injectable()
export class SimpleService {
  getSomeData() { return SOME_DATA; }
}
import { Component }   from '@angular/core';
import { SimpleService } from './simple.service';

@Component({
  selector: 'app-some-data',
  template: `<p>{{someData}}</p>`,
  providers: [SimpleService]
})
export class SomeDataComponent implements OnInit {
  someData: string;

  constructor(private someServiceName: SimpleService) {
  }

  ngOnInit() {
    this.someData = this.someServiceName.getSomeData();
  }
}

Сервис

Компонент

* Потенциально тяжелые действия выполняем в ngOnInit вместо конструктора

@Component({
  selector: 'app-some-data',
  template: `
    <p>{{someData}}</p>
  `,
  providers: [SimpleService]
})
export class SomeDataComponent {
    ...
}

Способы указать сервис для DI

Способ №1. В компоненте или модуле

@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ SimpleService ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
import { Injectable } from '@angular/core';
import { SOME_DATA } from './mock-data';

@Injectable({
  providedIn: 'root',
})
export class SimpleService {
  getData() { return SOME_DATA; }
}

* Новая фича ангуляра. У нас пока не используется (отлично подходит для библиотек, но хуже для приложений)

Способы указать сервис для DI

Способ №2. В сервисе

Dependency Injector

Экземпляр сервиса

Иерархическая инъекция зависимостей

Практика

- Переносим работу с задачами в канбане - в сервис

- Работаем с рест-сервером на Java

Запуск jar-файла с сервером:

C:\git\my-angular-project\src\resources> java -jar <название файла>

Проверяем что установлена java

C:\git\my-angular-project\src\resources> java -version

Если не установлена, то скачиваем 8 версию (JRE)

http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html

Посмотреть содержимое базы данных

1. Перейти по адресу localhost:8080/h2

2. Проверить что в пункте JDBC URL указано значение:

jdbc:h2:~/kanban

3. Нажать на кнопку Connect

Посмотреть содержимое базы данных

4. В появившемся окне можно вводить SQL-запросы

1

2

3

Полезные ссылки

1) Статья о паттерне DI

https://habr.com/post/350068/

2) Документация Angular. Блок статей по DI

https://angular.io/guide/dependency-injection

3) Документация RxJS. Описание Subject

http://reactivex.io/rxjs/manual/overview.html#subject

Домашнее задание №7

1. Перенести логику работы с задачами (а также стадиями и досками) в канбане из компонентов в сервис (или несколько сервисов):

  - создание/редактирование/удаление задачи

  - создание/редактирование/удаление стадии

Опционально (по желанию):

  - создание/редактирование/удаление доски

  - отображение нескольких досок

2. Реализовать работу с рест-сервером из примера на лекции

* Не забыть прислать ссылку на pull-request на почту

checkhomework.sis@gmail.com

Спасибо за внимание!

Занятие 9

By Сибирские интеграционные системы