React v16.3.0: нові життєві цикли та API контексту.

Кілька днів тому ми написали повідомлення про майбутні зміни наших застарілих методів життєвого циклу, включаючи поступові стратегії міграції. У Реакції 16.3.0 ми додаємо кілька нових методів життєвого циклу, щоб допомогти цій міграції. Ми також представляємо нові API для довго запрошених функцій: офіційний API контексту, API пересилання, а також ергономічний API-інтерфейс.

Прочитайте далі, щоб дізнатися більше про випуск.

Офіційний API-контекст

Протягом багатьох років React пропонував експериментальний API для контексту. Незважаючи на те, що це був потужний інструмент, її використання було затьмарене через невід’ємні проблеми в API, і тому ми завжди мали намір замінити експериментальний API кращим.

Версія 16.3 представляє новий контекстний API, який є більш ефективним і підтримує перевірку статичного типу та глибокі оновлення.

Примітка.
Старий контекстний API продовжуватиме працювати на всіх випусках React 16.x, тому ви матимете час для переміщення.

Ось приклад, який показує, як можна вписати “тему” за допомогою нового контекстного API:

const ThemeContext = React.createContext('light');

class ThemeProvider extends React.Component {
  state = {theme: 'light'};

  render (){
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}

class ThemedButton extends React.Component {
   render () {
      return (
         <ThemeContext.Consumer>
            {theme => <Button theme = {theme} />}
         </ThemeContext.Consumer>
      );
   }
}

Дізнайтеся більше про новий контекстний API тут.

createRef API

Раніше React пропонував два способи управління refs: застарілою API string ref і API зворотного виклику. Хоча API string ref був більш зручним з двох, він мав кілька недоліків, і тому наша офіційна рекомендація полягала в тому, щоб використовувати замість форми форми зворотного виклику.

Версія 16.3 додає нову опцію для управління рефами, що пропонує зручність рядка без будь-яких недоліків:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}

Примітка:
Функція зворотного виклику продовжуватиме підтримуватися на додаток до нового createRef API.

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

Дізнайтеся більше про новий createRef API тут.

forwardRef API

Компоненти вищого порядку (або HOCs) є поширеним способом повторного використання коду між компонентами. Розділяючи на прикладі контексту теми згори, ми можемо створити HOC, який вводить поточну “тему” як приклад:

function withTheme(Component) {
  return function ThemedComponent(props) {
    return (
      <ThemeContext.Consumer>
         {theme => <Component {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  };
}

Ми можемо використовувати вказане вище HOC для компонування проводів до контексту теми без використання ThemeContext безпосередньо. Наприклад:

class FancyButton extends React.Component {
  buttonRef = React.createRef();

  focus() {
    this.buttonRef.current.focus();
  }

  render() {
    const {label, theme, ...rest} = this.props;
    return (
      <button
        {...rest}
        className={`${theme}-button`}
        ref={this.buttonRef}>
        {label}
      </button>
    );
  }
}

const FancyThemedButton = withTheme(FancyButton);

// We can render FancyThemedButton as if it were a FancyButton
// It will automatically receive the current "theme",
// And the HOC will pass through our other props.
<FancyThemedButton
  label="Click me!"
  onClick={handleClick}
/>

HOCs зазвичай передають реквізити до компонентів, які вони обертають. На жаль, посилання не передаються. Це означає, що ми не можемо прикріпити посилання, FancyButton якщо ми використовуємо FancyThemedButton – так що нам не можна зателефонувати focus().

Новий forwardRef API вирішує цю проблему, надаючи спосіб перехопити ref та переслати його як звичайну підтримку:

function withTheme(Component) {
  // Note the second param "ref" provided by React.forwardRef.
  // We can attach this to Component directly.
  function ThemedComponent(props, ref) {
    return (
      <ThemeContext.Consumer>
        {theme => (
          <Component {...props} ref={ref} theme={theme} />
        )}
      </ThemeContext.Consumer>
    );
  }

  // These next lines are not necessary,
  // But they do give the component a better display name in DevTools,
  // e.g. "ForwardRef(withTheme(MyComponent))"
  const name = Component.displayName || Component.name;
  ThemedComponent.displayName = `withTheme(${name})`;

  // Tell React to pass the "ref" to ThemedComponent.
  return React.forwardRef(ThemedComponent);
}

const fancyButtonRef = React.createRef();

// fancyButtonRef will now point to FancyButton
<FancyThemedButton
  label="Click me!"
  onClick={handleClick}
  ref={fancyButtonRef}
/>;

Зміни компонентного життєвого циклу

API-клас компонентів React-у не мав протягом багатьох років великих змін. Однак, оскільки ми додаємо підтримку для більш просунутих функцій (таких як error boundaries і майбутній async rendering mode ), ми розтягуємо цю модель таким чином, що вона не була спочатку призначена.

Наприклад, за допомогою поточного API, занадто легко заблокувати початкове відтворення з несуттєвою логікою. Частково це пов’язано з тим, що занадто багато способів виконувати певне завдання, і це може бути незрозумілим, що є найкращим. Ми помітили, що перериваюча поведінка обробки помилок часто не враховується і може призвести до витоку пам’яті (щось що також вплине на майбутній режим рендеринга асинхронного типу). Поточний компонент API класів також ускладнює інші зусилля, як наша робота над прототипом компілятора React.

Багато з цих проблем поглиблюються підмножина компонентів (життєвий цикл componentWillMount, componentWillReceiveProps і componentWillUpdate). Це також стає життєвими циклами, які викликають найбільше плутанини в спільноті React. З цих причин ми будемо зневажати ці методи на користь кращих альтернатив.

Ми визнаємо, що ця зміна вплине на багато існуючих компонентів. Через це міграційний шлях буде настільки поступовим, як і можливе, і забезпечить втечу. (У Facebook ми підтримуємо більш ніж 50 000 компонентів React. Ми також залежимо від поетапного циклу випуску!)

Примітка:
Попередження попередження буде включено з майбутнім випуском 16.x, але застарілі життєві цикли будуть продовжувати працювати до версії 17.

Навіть у версії 17, вони все одно зможуть їх використовувати, однак вони будуть згладжуватись з префіксом "UNSAFE_", щоб вказати, що вони можуть викликати проблеми. Ми також підготували автоматичний скрипт для перейменування їх у існуючий код.

Окрім того, що ви втрачаєте небезпечні життєві цикли, ми також додаємо пару нових життєвих сюрпризів:

  • getDerivedStateFromProps додається як більш безпечна альтернатива спадщині componentWillReceiveProps.
  • getSnapshotBeforeUpdate додається для підтримки безпечного читання властивостей, наприклад, DOM, до того, як будуть оновлені.

Дізнайтеся більше про ці зміни життєвого циклу тут.

StrictMode Компонент

StrictMode це інструмент для виявлення потенційних проблем у програмі. Любіть Fragment, StrictMode не відображає видимого інтерфейсу. Він активує додаткові перевірки та попередження для своїх нащадків.

Примітка:
StrictMode чеки виконуються тільки в режимі розробки; вони не впливають на виробництво.

Хоча для суворого режиму неможливо усунути всі проблеми (наприклад, певні типи мутацій), це може допомогти багатьом. Якщо ви бачите попередження в строгому режимі, ці речі, швидше за все, призведуть до помилок для асинхронного візуалізації.

У версії 16.3 StrictMode допомагає:

  • Визначення компонентів із небезпечними життєвими циклами
  • Попередження про використання застарілих API рядків
  • Виявлення несподіваних побічних ефектів

Додаткові функціональні можливості будуть додані в майбутніх версіях React.

Дізнайтеся більше про StrictMode компонент тут.

Переклад статті “React v16.3.0: New lifecycles and context API

7 хаків для ES6 розробників

7 хаків для ES6 розробників

Після оригінальних хакв JavaScript, ось деякий новий синтаксичний цукор.
Кодування JavaScript в 2018 році це насправді весело!

Хак №1 — Swap змінні.

Використовуйте Array Destructuring для зміни значень.

let a = 'world', b = 'hello'
[a, b] = [b, a]
console.log(a) // -> hello
console.log(b) // -> world
// Yes, it's magic

Хак №2 — Async / Await при деструкції.

Ще раз, Array Destructuring чудово поєднується з async/await і обіцяє зробити складний потік – простим.

const [user, account] = await Promise.all([
  fetch('/user'),
  fetch('/account')
])

Хак №3 — Налагодження.

Для тих, хто любить налагоджувати використовуючи console.log, ось щось дивовижне (і так, я чув про console.table):

const a = 5, b = 6, c = 7
console.log({ a, b, c })
// outputs this nice object:
// {
//    a: 5,
//    b: 6,
//    c: 7
// }

Хак №4 — Одна лінія.

Синтаксис може бути набагато компактнішим для операцій масиву.

// Find max value
const max = (arr) => Math.max(...arr);
max([123, 321, 32]) // outputs: 321
// Sum array
const sum = (arr) => arr.reduce((a, b) => (a + b), 0)
sum([1, 2, 3, 4]) // output: 10

Хак №5 — Зчеплення масиву.

Оператор розповсюдження може використовуватися замість concat:

const one = ['a', 'b', 'c']
const two = ['d', 'e', 'f']
const three = ['g', 'h', 'i']
// Old way #1
const result = one.concat(two, three)
// Old way #2
const result = [].concat(one, two, three)
// New
const result = [...one, ...two, ...three]

Хак №6 — Клонування.

Клонувати масиви та об’єкти з легкістю:

const obj = { ...oldObj }
const arr = [ ...oldArr ]

Примітка. Це створює неглибокий клон.

Хак №7 — Названі параметри.

Створення функцій і функціональних викликів більш читабельних при руйнуванні:

const getStuffNotBad = (id, force, verbose) => {
  ...do stuff
}
const getStuffAwesome = ({ id, name, force, verbose }) => {
  ...do stuff
}
// Somewhere else in the codebase... WTF is true, true?
getStuffNotBad(150, true, true)
// Somewhere else in the codebase... I ❤ JS!!!
getStuffAwesome({ id: 150, force: true, verbose: true })

Переклад статті “7 Hacks for ES6 Developers

Angular 5: Unit тести

Angular 5: Unit тести

За допомогою unit тестів ми можемо переконатися, що окремі частини програми працюють саме так, як ми від них очікуємо.

Це в деякій мірі рятує від поломок існуючий код, допомагає прояснити – як він буде працювати в тих чи інших випадках. І, врешті-решт, дозволяє подивитися на код, так скажімо, з боку, щоб побачити його слабкі сторони.

Навіть існує думка, що складно тестований код – претендент на переписування.

Мета даної статті – допомогти в написанні unit тестів для Angular 5+ додатки. Нехай це буде захоплюючий процес, а не головний біль.

Ізольовані або Angular Test Bed?

Що стосується unit тестування Angular додатки, то можна виділити два види тестів:

  • Ізольовані – ті, які не залежать від Angular. Вони простіше в написанні, їх легше читати і підтримувати, так як вони виключають всі залежності. Такий підхід хороший для сервісів та пайпов.
  • Angular Test Bed – тести, в яких за допомогою тестової утиліти TestBed здійснюється настройка і ініціалізація середовища для тестування. Утиліта містить методи, які полегшують процес тестування. Наприклад, ми можемо перевірити, створився чи компонент, як він взаємодіє з шаблоном, з іншими компонентами і з залежностями.

Ізольовані

При ізольованому підході ми тестуємо сервіс як звичайнісінький клас з методами.

Спочатку створюємо екземпляр класу, а потім перевіряємо, як він працює в різних ситуаціях.

Перш ніж переходити до прикладів, необхідно позначити, що я пишу і запускаю тести за допомогою jest, так як сподобалася його швидкість. Якщо ви віддаєте перевагу karma + jasmine, то приклади для вас також будуть актуальні, оскільки відмінностей в синтаксисі зовсім небагато.

Jasmine / jest відмінності

jasmine.createSpy ( 'name') -> jest.fn () 
and.returnValue () -> mockReturnValue () 
spyOn (...). and.callFake (() => {}) -> jest. spyOn (...). mockImplementation (() => {})

Розглянемо приклад сервісу для модального вікна. У нього всього лише два методи, які повинні розсилати певне значення для змінної popupDialog. І зовсім немає залежностей.

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

@Injectable()
export class PopupService {

  private popupDialog = new ReplaySubject<{popupEvent: string, component?, options?: {}}>();

  public popupDialog$ = this.popupDialog.asObservable();

  open(component, options?: {}) {
    this.popupDialog.next({popupEvent: 'open', component: component, options: options});
  }

  close() {
    this.popupDialog.next({popupEvent: 'close'});
  }

}

При написанні тестів не потрібно забувати про порядок виконання коду. Наприклад, дії, які необхідно виконати перед кожним тестом, ми поміщаємо в beforeEach. Так, створений в коді нижче екземпляр сервісу нам знадобиться для кожної перевірки.

import { PopupService } from './popup.service';
import { SignInComponent } from '../components/signin/signin.component';

describe('PopupService', () => {
  let service: PopupService;
  // создаем экземпляр PopupService
  beforeEach(() => { service = new PopupService(); });
  // done нужно, чтобы тест не завершился до получения данных
  it('subscribe for opening works', (done: DoneFn) => {
    // вызываем метод open
    service.open(SignInComponent, [{title: 'Попап заголовок', message: 'Успешно'}]);
    // при изменении значения popupDialog$ должен сработать subscribe
    service.popupDialog$.subscribe((data) => {
      expect(data.popupEvent).toBe('open');
      done();
    });

  });
  it('subscribe for closing works', (done: DoneFn) => {
    service.close();
    service.popupDialog$.subscribe((data) => {
      expect(data.popupEvent).toBe('close');
      done();
    });
  });
});

Angular Test Bed тести

Простий компонент

А тепер подивимося на всю потужність утиліти TestBed. Як приклад для початку візьмемо найпростіший компонент:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

Файл шаблону:

<h1>
  Welcome to {{ title }}!
</h1>

Файл тестів розберемо по шматочках. Для початку ставимо TestBed конфігурацію:

 beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

compileComponents – метод, який робить винесені в окремі файли стилі і шаблон вбудованими.

Цей процес є асинхронним, так як компілятор Angular повинен отримати дані з файлової системи.

Іноді compileComponents не потрібен

Якщо ви використовуєте WebPack, то цей виклик і метод async вам не потрібен. Справа в тому, що WebPack автоматично перед запуском тестів вбудовує зовнішні стилі і шаблон.

Відповідно, і при прописуванні стилів і шаблону всередині файлу компонента компілювати самостійно не треба.

Для тестів необхідно, щоб компоненти скомпілювати до того, як через метод createComponent () будуть створені їх екземпляри.

Тому тіло першого BeforeEach ми помістили в asynс метод, завдяки чому його вміст виконується в спеціальній асинхронної середовищі. І поки не буде виконаний метод compileComponents (), наступний BeforeEach не запуститься:

beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    comp = fixture.componentInstance;
  });

Завдяки винесенню в beforeEach всіх загальних даних, подальший код виходить значно чистіше.

Для початку перевіримо створення екземпляра компонента і його властивість:

it('should create the comp',  => {
  expect(comp).toBeTruthy();
});
it(`should have as title 'app'`, () => {
   expect(comp.title).toEqual('app');
});

Далі ми хочемо перевірити, що змінна компонента title вставляється в DOM. При цьому ми очікуємо, що їй присвоєно значення ‘app’. А це привласнення відбувається при ініціалізації компонента.

Запустивши за допомогою detectChanges CD цикл, ми инициализируем компонент.
До цього виклику зв’язок DOM і даних компонента не відбудеться, а отже тести не пройдуть.

it('should render title in a h1 tag', () => {
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent)
      .toContain('Welcome to app!');
  });

Повний код тесту компонента

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {

  let comp: AppComponent;
  let fixture: ComponentFixture;
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    comp = fixture.componentInstance;
  });

  it('should create the comp', () => {
    expect(comp).toBeTruthy();
  });
  it(`should have as title 'app'`, () => {
    expect(comp.title).toEqual('app');
  });
  it('should render title in a h1 tag', () => {
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent)
      .toContain('Welcome to app!');
  });
});

Компонент з залежностями

Давайте усложним наш компонент, запровадивши в нього сервіс:

export class AppComponent {
  constructor(private popup: PopupService) { }
  title = 'app';
}

Начебто поки не особливо ускладнили, але тести вже не пройдуть. Навіть якщо ви не забули додати сервіс в providers AppModule.

Тому що в TestBed ці зміни теж потрібно відобразити:

TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [PopupService]
    });

Ми можемо вказати сам сервіс, але зазвичай краще замінити його на клас або об’єкт, який описує саме те, що нам необхідно для тестів.

Чому?

А ви уявіть сервіс з купою залежностей і вам все доведеться при тестуванні прописати. Не кажучи вже про те, що ми тестуємо в даному випадку саме компонент. Взагалі, тестувати щось одне – це як раз про unit тести.

Отже, прописуємо стаб наступним чином:

const popupServiceStub = {
    open: () => {}
};

Методи задаємо тільки ті, які тестуємо.

Якщо хочемо описати стаб як клас

class popupServiceStub {
    open() {}
}
providers: [{provide: PopupService, useClass: popupServiceStub } ]

У TestBed конфігурацію додаємо providers:

providers: [{provide: PopupService, useValue: popupServiceStub } ]

Не варто плутати PopupService і PopupServiceStab. Це різні об’єкти: перший – клон другого.

Відмінно, але ми ж сервіс впроваджували не просто так, а для використання:

ngOnInit() {
    this.popup.open(SignInComponent);
}

Тепер варто переконатися, що метод дійсно викликається. Для цього спочатку отримаємо екземпляр сервісу.

Так як в даному випадку сервіс заданий в providers кореневого модуля, то ми можемо зробити так:

popup = TestBed.get(PopupService);

А як ще?

Якби мова йшла про сервіс, який прописаний в providers компонента, то довелося б отримувати його так:

popup = fixture.debugElement.injector.get(PopupService);

Нарешті сама перевірка:

it('should called open', () => {
    const openSpy = jest.spyOn(popup, 'open');
    fixture.detectChanges();
    expect(openSpy).toHaveBeenCalled();
  });

Наші дії:

  1. Встановлюємо шпигуна на метод open об’єкта popup.
  2. Запускаємо CD цикл, в ході якого виконається ngOnInit з перевіряється методом
  3. Переконуємося, що він був викликаний.

Зауважте, що перевіряємо ми саме виклик методу сервісу, а не те, що він повертає або інші речі, що стосуються самого сервісу. Їх, щоб зберегти розум, варто тестувати в сервісі.

Сервіс з http

Зовсім недавно (в Angular 4) файл тестів сервісу з запитами міг виглядати воістину страхітливо.

Згадати, як це було

beforeEach(() => TestBed.configureTestingModule({
    imports: [HttpModule],
    providers: [
      MockBackend,
      BaseRequestOptions,
      {
        provide: Http,
        useFactory: (backend, defaultOptions) => new Http(backend, defaultOptions),
        deps: [MockBackend, BaseRequestOptions]
      },
      UserService
    ]
  }));

Втім, і зараз в інтернеті повно статей з цими прикладами.

А між тим розробники Angular не сиділи склавши руки, і ми тепер можемо писати тести набагато простіше. Просто скориставшись HttpClientTestingModule і HttpTestingController.

Розглянемо сервіс:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { ReplaySubject } from 'rxjs/ReplaySubject';

import { Game } from '../models/gameModel';
import { StatisticsService } from './statistics.service';

@Injectable()
export class GameService {
  gameData: Array;
  dataChange:  ReplaySubject;
  gamesUrl = 'https://any.com/games';

  constructor(private http: HttpClient, private statisticsService: StatisticsService) {
    this.dataChange  = new ReplaySubject();
  }

  getGames() {
    this.makeResponse()
      .subscribe((games: Array) => {
        this.handleGameData(games);
      });
  }

  makeResponse(): Observable {
    return this.http.get(this.gamesUrl);
  }
  handleGameData(games) {
    this.gameData = games;
    this.doNext(games);
    this.statisticsService.send();
  }

  doNext(value) {
    this.dataChange.next(value);
  }

}

Для початку описуємо всіх наших глобальних героїв:

let http: HttpTestingController;
let service: GameService;
let statisticsService: StatisticsService;
const statisticsServiceStub = {
    send: () => {}
};

Тут з цікавого – стаб statisticsService. Ми за аналогією з компонентом Стабія залежності, так як тісто зараз тільки конкретний сервіс.

Як бачите, я просто прописала саме те, що знадобиться в цьому тесті. Просто уявіть, що в StatisticsService насправді величезна кількість методів і залежностей, а використовуємо в даному сервісі ми тільки один метод.

Далі оголосимо дані, які будемо підкидати у відповідь на запит:

 const expectedData = [
    {id: '1', name: 'FirstGame', locale: 'ru', type: '2'},
    {id: '2', name: 'SecondGame', locale: 'ru', type: '3'},
    {id: '3', name: 'LastGame',  locale: 'en', type: '1'},
  ];

У TestBed необхідно імпортувати HttpClientTestingModule і прописати всі сервіси:

TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
      ],
      providers: [GameService, { provide: StatisticsService, useValue: statisticsServiceStub }]
    });

Наступний крок – отримання примірників всіх сервісів, які нам знадобляться:

service = TestBed.get(GameService);
    statisticsService = TestBed.get(StatisticsService);
    http = TestBed.get(HttpTestingController);

Не завадить відразу ж прописати в afterEach перевірку на те, що немає відкладених запитів:

afterEach(() => {
    http.verify();
});

І переходимо до самих тестів. Найпростіше, що ми можемо перевірити – створився чи сервіс. Якщо ви забудете в TestBed вказати будь-яку залежність, то цей тест не пройде:

it('should be created', () => {
    expect(service).toBeTruthy();
  });

Далі вже цікавіше – перевіряємо, що по очікуваному запитом отримаємо певні дані, які самі ж і підкидає:

it('should have made one request to GET data from expected URL', () => {

    service.makeResponse().subscribe((data) => {
      expect(data).toEqual(expectedData);
    });

    const req = http.expectOne(service.gamesUrl);
    expect(req.request.method).toEqual('GET');
    req.flush(expectedData);
  });

Не завадить перевірити ще й як працює ReplaySubject, тобто чи будуть відловлювати у передплатників отримані гри:

it('getGames should emits gameData', () => {

    service.getGames();

    service.dataChange.subscribe((data) => {
      expect(data).toEqual(expectedData);
    });
    
    const req = http.expectOne(service.gamesUrl);
    req.flush(expectedData);

  });

І нарешті останній приклад – перевірка, що statisticsService метод send буде викликаний:

 it('statistics should be sent', () => {
    const statisticsSpy = jest.spyOn(statisticsService, 'send');
    service.handleGameData(expectedData);
     expect(statisticsSpy).toHaveBeenCalled();
  });

Повний код тестів

import { TestBed } from '@angular/core/testing';

import { GameService } from './game.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { StatisticsService } from './statistics.service';

import 'rxjs/add/observable/of';

describe('GameService', () => {
  let http: HttpTestingController;
  let service: GameService;
  let statisticsService: StatisticsService;
  const statisticsServiceStub = {
    send: () => {}
  };

  const expectedData = [
    {id: '1', name: 'FirstGame', locale: 'ru', type: '2'},
    {id: '2', name: 'SecondGame', locale: 'ru', type: '3'},
    {id: '3', name: 'LastGame',  locale: 'en', type: '1'},
  ];


  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
      ],
      providers: [GameService, { provide: StatisticsService, useValue: statisticsServiceStub }]
    });

    service = TestBed.get(GameService);
    statisticsService = TestBed.get(StatisticsService);
    http = TestBed.get(HttpTestingController);

  });

  afterEach(() => {
    http.verify();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should have made one request to GET data from expected URL', () => {

    service.makeResponse().subscribe((data) => {
      expect(data).toEqual(expectedData);
    });

    const req = http.expectOne(service.gamesUrl);
    expect(req.request.method).toEqual('GET');
    req.flush(expectedData);
  });


  it('getGames should emits gameData', () => {

    service.getGames();

    service.dataChange.subscribe((data) => {
      expect(data).toEqual(expectedData);
    });
    
    const req = http.expectOne(service.gamesUrl);
    req.flush(expectedData);

  });

  it('statistics should be sent', () => {
    const statisticsSpy = jest.spyOn(statisticsService, 'send');
    service.handleGameData(expectedData);
    expect(statisticsSpy).toHaveBeenCalled();
  });

});

Як полегшити тестування?

  1. Вибирайте той тип тестів, який підходить в даній ситуації і не забувайте про суть unit тестів
  2. Переконайтеся, що знаєте все можливості вашої IDE в плані допомоги при тестуванні
  3. При генерації сутностей за допомогою Angular-cli автоматично генерується і файл тестів
  4. Якщо в компоненті безліч таких залежностей, як директиви і дочірні компоненти, то можна відключити перевірку їх визначення. Для цього в TestBed конфігурації прописуємо NO_ERRORS_SCHEMA:
TestBed.configureTestingModule({
    declarations: [ AppComponent ],
    schemas:      [ NO_ERRORS_SCHEMA ]
  })

Переклад статті “Angular 5: Unit тесты

Підручник Webpack 4

Webpack 4

Команда розробників Webpack цей раз добряче попрацювала над новим поколінням популярного збирача модулів (Бандлера) – webpack 4.

Репозиторій з використовуваним кодом тут.

Webpack 4 як збирач модулів з нульовою конфігурацією

Ніхто не сперечається: у нього є потужні плюси, велика кількість можливостей і налаштувань, проте головним болем є файл конфігурації.

Написання конфіга не складає проблеми для середніх і великих проектів, без цього їм важко існувати. Проте, невеликі проекти це може дратувати, особливо якщо хочеться створити додаток-іграшку.

Шон і команда webpack поліпшили життя всім нам: webpack 4 більше не вимагає файлу конфігурації за замовчуванням!

Що ж, протестуємо.

Створіть нову директорію і перейдіть туди:

mkdir webpack-4-quickstart && cd $_

Ініціалізуйте package.json:

npm init -y

Тепер пускаємо в бій webpack 4 (Версія зараз знаходиться в стадії beta, тому потрібно додати next):

npm i webpack@next --save-dev

Додамо webpack-cli, що живе своїм життям в окремому пакеті:

npm i webpack-cli --save-dev

Відкриваємо package.json і прописуємо скрипт збірки:

"scripts": { 
   "build": "webpack" 
}

Збережіть файл, закрийте. запустіть:

npm run build

Що ж трапилося?

ERROR in Entry module not found: Error: Can't resolve './src' in '~/webpack-4-quickstart'

Webpack 4 шукає вхідні точку прикладання ./src! Якщо не знаєте, чому так вийшло, то опишу коротко: вхідні точка – це файл, з якого webpack починає збірку. У ранніх версіях потрібно було оголосити її безпосередньо в webpack.config.js.

Але починаючи з 4-ої версії вам не потрібно вказувати вхідні точку. Вона буде взята з ./src/index.js за замовчуванням!

Перевіримо. Створіть ./src/index.js:

console.log('Test');

Знову запустіть збірку:

npm run build

Ви отримаєте файл ~/webpack-4-quickstart/dist/main.js .

Невже нам не потрібно ставити і точку виходу теж? Саме! Ні точку входу, ні виходу. Тим більше, не потрібен файл конфігурації .

Я знаю, що для більшості це не дивно: сила webpack в поділі коду. Але повірте: нуль конфігурації прискорює розробку в рази.

Режими production і development

Режими production і development

Дуже часто можна зустріти поділу конфіга на кілька файлів.

Типовий проект може мати:

  • Конфігурацію для розробки (development), з webpack-dev-server і іншими іграшками розробників.
  • Конфігурація для продакшена з UglifyJSPlugin, картами сайту і іншим.

Поки великі проекти продовжують використовувати поділ конфігов, ми з webpack 4 зробимо все одним рядком.

Як так? Зустрічайте режими production і development.

Якщо ви звернете увагу на висновок npm run build, то побачите красиву помилку:

Опція 'mode' (режим) була задана. Увімкніть режим в 'development' або 'production', щоб застосувати настройки за замовчуванням.
Опція ‘mode’ (режим) була задана. Увімкніть режим в ‘development’ або ‘production’, щоб застосувати настройки за замовчуванням.

Що це означає? Будемо розбиратися. Відкрийте package.json і допишіть об’єкт скриптів, як показано нижче:

"scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  }

І тепер спробуємо:

npm run dev

Погляньмо на ./dist/main.js. Що ви бачите? Так, я знаю, нудний бандл … не зменшений. А якщо:

npm run build

Що тепер? Файл зборки був зменшений. Так! Режим 'production' використовує всі види оптимізації самостійно. І там не тільки мінімізація.

З іншого боку режим розробника (development mode) оптимізує швидкість програми і нічого більше.

Таким чином, з 4-х webpack’ом ви можете змінювати всю збірку одним рядком. Просто додайте прапорець --mode і отримаєте результат абсолютно безболісно.

Переклад статті “Webpack 4 tutorial: All You Need to Know, from 0 Conf to Production Mode

Typescript. Властивості доступні тільки для читання

У версії Typescript 2.0 був доданий модифікатор readonly. Властивостями поміченим модифікатором readonly значення може бути присвоєно тільки в момент ініціалізації, або в конструкторі того ж класу. Будь-які інші присвоювання значення заборонені.

Давайте подивимося на приклад. Тут представлений простий тип Point, описуваний двома властивостями, доступними тільки для читання:

type Point = {
  readonly x: number;
  readonly y: number;
};

Тепер ми можемо створити об’єкт являє собою початок координат, і ініціалізувати x і y багатозначно 0:

const origin: Point = { x: 0, y: 0 };

Однак, так як властивості x і y позначені readonly, ми не можемо змінити їх значення згодом:

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
origin.x = 100;

Більш реалістичний приклад

Наведений вище приклад може здатися надуманим, давайте розглянемо наступну функцію:

function moveX(point: Point, offset: number): Point {
  point.x += offset;
  return point;
}

Функція moveX не повинна змінювати властивість xпереданого об’єкта point. Компілятор TypeScript обов’язково почне лаятися, якщо ви спробуєте це зробити, так як властивість позначено модифікатором readonly. Замість цього moveX повинна повертати новий об’єкт зі зміненими значеннями:

function moveX(p: Point, offset: number): Point {
  return {
    x: p.x + offset,
    y: p.y
  };
}

Тепер компілятор буде щасливий, більше немає спроб привласнити значення властивостям поміченим readonly. Ми створили новий об’єкт, який ініціалізується за оновленими значеннями.

Властивості класу, доступні тільки для читання

Ви також можете застосовувати модифікатор readonlyдо властивостей описаних в класі. Тут представлений клас Circle з readonly полем radius і властивістю area, яке непрямим чином реалізує доступність тільки для читання, тому що не має сетера:

class Circle {
    readonly radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

    get area() {
        return Math.PI * this.radius ** 2;
    }
}

Зверніть увагою, що area обчислюється за допомогою оператора піднесення до степеня. І radius, і area доступні ззовні класу для читання, тому що жоден з них не позначений як private, але не для запису:

const unitCircle = new Circle(1);
unitCircle.radius;  // 1
unitCircle.area;    // 3.141592653589793

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.radius = 42;

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.area = 42;

Доступні тільки для читання поля інтерфейсів

Поля інтерфейсів також можуть позначені, як доступні тільки для читання. Наприклад, тип ReadOnlyArray<T> запобігає запис значень в описані властивості:

interface ReadonlyArray<T> {
  readonly length: number;
  // ...
  readonly [n: number]: T;
}

Наступне присвоювання буде невалідним:

const primesBelow10: ReadonlyArray<number> = [2, 3, 5, 7];

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
primesBelow10[4] = 11;

Readonly vs іммутабельність

Модифікатор readonly – це частина системи типів TypeScript. Він використовується тільки компілятором для перевірки незаконних присвоєнні значень. Як тільки TypeScript код компілюється в JavaScript таке поняття, як readonl піде геть.

Так як модифікатор readonly – це тільки артефакт при компіляції, він не є захистом від присвоювання значень під час виконання коду. Проте це ще одна особливість TypeScript, яка допоможе вам писати правильний код, залишивши компілятору роботу по перевірці ненавмисних присвоєнь значень.

Переклад статті “TypeScript 2.0: Read-Only Properties

Angular оновився до версії 5.1

Вийшло невелике оновлення JS-фреймворка Angular 5.1. У список поліпшень входять стабільні версії Angular Material і CDK, підтримка Service Worker API в CLI і підтримка TypeScript 2.5.

Після виходу мажорного релізу Angular 5.0 минулого місяця команда оголосила про випуск коригуючого поновлення Angular 5.1.0, в якому з’явилося кілька дрібних фич і виправлені деякі помилки. Також був випущений Angular CLI 1.6 і представлена перша стабільна версія Angular Material.

Що нового?

  • стабільні версії Angular Material і CDK;
  • підтримка Service Worker API в CLI;
  • поліпшена підтримка Universal і AppShell в CLI;
  • доопрацьовані повідомлення про помилки декоратора;
  • підтримка TypeScript 2.5.

Повний список функцій і виправлень можна подивитися на сторінках Angular, Material, і CLI.

Стабільні версії Angular Material і CDK

Angular Material заснований на візуальному мовою Material Design від Google. Angular Material пропонує 30 компонентів для користувача інтерфейсу для створення додатків. Angular CDK являє собою набір компонентів для створення власних елементів, без необхідності вирішувати заново спільні завдання. Ці компоненти вже використовуються у виробництві поряд додатків Google, включаючи Google Analytics Suite , Google Cloud Platform Developer Console , і Google Shopping Express .

Починаючи з поточної версії, Angular Material буде дотримуватися тієї ж філософії SemVer, що і Angular. Основні версії Angular Material і Angular CDK виходитимуть одночасно з рештою платформою. Патч-релізи будуть випускатися з тижневими інтервалами, а другорядні випуски стануть доступні у міру готовності функцій.

Документація і посібник для початківців доступні на офіційному сайті. Також стежити за ходом роботи команди можна на сторінці проекту на GitHub.

Підтримка Service Worker в CLI 1.6

У Angular 5.0.0 була включена підтримка Service Worker, адаптованої для Angular-додатків. Angular CLI 1.6 підтримує функції, які залежать нову функцію. За твердженням команди розробників, використання @angular/service-worker може підвищити продуктивність завантаження додатків в браузерах, які підтримують даний API.

Детальніше про початок роботи c Angular Service Worker можна ознайомитися на сайті.

Покращена підтримка Universal і App Shell в CLI 1.6

З виходом Angular CLI 1.6 доопрацьована можливість впровадження Universal в існуючі проекти через Schematics. Також додана підтримка App Shell.

Angular Universal

Наступна команда дозволяє додати підтримку Universal в існуюче CLI-додаток:

ng generate universal

Це створить модуль Universal в поточному додатку і автоматично сконфигурирует файл angular-cli.json.

Створити Universal додаток:

ng build --app=

Підтримка App Shell

З додаванням підтримки архітектури App Shell, з’явилася можливість створити оболонку додатки, яка створить статичний «перший екран» під час завантаження програми.

Спочатку необхідно імпортувати RouterModule в NgModule додатки, а <router-outlet></router-outlet> в шаблон компонента програми. App Shell використовує маршрутизатор для рендера додатки.

Потім запускається команда:

ng generate app-shell [ --universal-app ] [ --route ]

Далі необхідно зібрати додаток використовуючи ng build і маршрут з вашого застосування має активізуватися в index.html файл.

Доопрацьовані повідомлення про помилки декораторів

Діагностика компілятора значно покращилася, особливо в тих випадках, коли декоратори містять не підтримуються або некоректні висловлювання.

Наприклад, виклик функції для створення шаблону не тримається

@Component({
  template: genTemplate()
})
export class MyComponent {}

Раніше це викликало б наступну помилку:

Error encountered resolving symbol values statically. Calling function ‘genTemplate’, function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in components.ts, resolving symbol MyComponent in components.ts

Повідомлення про помилку було доопрацьовано і тепер пояснює джерело і причину проблеми:

component.ts(9,16): Error during template compile of 'MyComponent'.  Function calls are not supported in decorators but 'genTemplate' was called.

Підтримка TypeScript 2.5

Додана підтримка TypeScript 2.5. Цей випуск TypeScript включає кілька корисних розширених функцій. Оновити TypeScript можна, запровадивши таку команду:

yarn add typescript@'~2.5.3'or npm install typescript@'~2.5.3'.

Але дана дія не є обов’язковим, оскільки TypeScript 2.4 продовжує підтримуватися Angular 5.xx Підтримку TypeScript 2.6 команда планує додати в наступному оновленні.

Переклад статті “Angular 5.1 & More Now Available

Всі основні браузери тепер підтримують WebAssembly

Всі розробники веб-браузерів всього лише протягом 2 років змогли впровадити в свої продукти підтримку WebAssembly.

Робота над WebAssembly почалася в 2015 році, коли розробники основних веб-браузерів об’єднали зусилля для створення бінарного формату (байт-коду) для мережі Інтернет.

Переваги WebAssembly

Перевага байт-коду полягає в тому, що він в кілька разів менше в розмірі, ніж код, з якого він був скомпільований. Це означає, що бінарний формат може бути використаний для економії трафіку і збільшення швидкості завантаження сторінки сучасних сайтів і веб-додатків.

Інший плюс використання байт-коду полягає в легкості, з якою сучасні веб-браузери можуть його прочитати. Тим самим забезпечуються краще структурування формату даних і швидкий парсинг коду.

На додаток до всього вищесказаного, розробники зможуть писати код на С, С ++ або Rust і компілювати його в байт-код безпосередньо без необхідності проміжного перекладу в JavaScript-код.

Популярність

З моменту анонса єдиного бінарного формату WebAssembly став шалено популярний в індустрії онлайн-ігор. З його допомогою стало можливим створення більш просунутих ігрових движків, які могли конкурувати зі своїми настільними аналогами.

Саме тому розробники Mozilla оголосили WebAssembly фактором, що змінює правила гри в світі інтернету. Позитивні риси нового стандарту і потенційний прибуток, яку він міг принести, стали каталізатором всієї роботи.

Всі учасники в грі

Незважаючи на деякий скепсис щодо нового стандарту і його життєздатності, в жовтні 2016 року Google, Microsoft і Mozilla вже представили превью-версії своїх браузерів з підтримкою байт-коду. А в березні 2017 року роботу з впровадження стандарту була закінчена. Таким чином, браузери почали використовувати WebAssembly за замовчуванням, щоб протестувати його в реальних умовах.

Протягом літа 2017 року Firefox і Chrome стали першими великими браузерами, які отримали підтримку WebAssembly в своїх стабільних версіях. В Opera і Vivaldi також була впроваджена функціональність, як тільки вона була додана в стабільні версії Chromium. Одними з останніх, хто ввів WebAssembly в стабільні гілки браузерів Safari 11.0 і Edge, стали відповідно Apple і Microsoft.

В даний час стандарт має гучний успіх – він уже впроваджений у багатьох іграх Facebook завдяки потужним ігровим движкам, випущеним такими компаніями, як Unity і Epic. Однак WebAssembly вже зробив собі ім’я і в світі зловредів. Як стверджують експерти, Майнер криптовалюта, такі як Coinhive і CryptoLoot, не були б можливі без впровадження нового стандарту в усі браузери.

Дізнатися більше про бінарному форматі можна з цього відео:

Переклад статті “All Major Browsers Now Support WebAssembly

Реліз бібліотеки TensorFlow 1.4

У TensorFlow 1.4 розробники додали кілька нових функцій, а також підтримку Dataset API і Estimator API.

Реліз бібліотеки TensorFlow 1.4

7 листопада компанія Google анонсувала вихід нової версії TensorFlow, яка включає в себе кілька нових функцій і підтримку нижчезгаданих API.

Keras

У TensorFlow 1.4 Keras переїхала з tf.contrib.kerasв ядро пакета tf.keras. Keras – це популярна бібліотека для машинного навчання, написана на Python. Вона включає в себе високорівневі API для спрощення реалізації ідей розробників. Бібліотека сумісна з функціональними можливостями TensorFlow, включаючи API Estimator. Розробники можуть створювати Estimator’и, використовуючи будь-яку модель Keras, за допомогою функції tf.keras.estimator.model_to_estimator.

Datasets

Google повідомила, що Dataset API також був доданий в ядро пакета tf.datatf.contrib.data). Цей API привносить підтримку генераторів Python. Розробники рекомендують використовувати його при створенні вхідних конвеєрів для моделей TensorFlow, оскільки він дозволяє використовувати більше можливостей, працює краще і простий у використанні. Google зосередиться на розвитку Dataset, а не інших аналогічних API.

Розподілені навчання і оцінка Estimator

У TensorFlow 1.4 була додана функція tf.estimator.train_and_evaluate, яка спрощує навчання, оцінку і експорт моделей Estimator. Вона дозволяє виконувати процеси навчання і оцінки як локально, так і розподілений.

Установка TensorFlow 1.4

Нову версію можна встановити за допомогою pip.

# Note: the following command will overwrite any existing TensorFlow
# installation.
$ pip install --ignore-installed --upgrade tensorflow
# Use pip for Python 2.7
# Use pip3 instead of pip for Python 3.x

Також розробники оновили документацію на сайті проекту.

Переклад статті “Announcing TensorFlow r1.4

Зустрічайте нову версію Angular – Angular 5

Ми раді оголосити версію 5.0.0 Angular, pentagonal-donut. Це основний реліз, що містить нові функції та виправлення помилок. Цей реліз продовжує концентруватися на тому, щоб зробити Angular меншим, швидшим і простішим у використанні.

Angular 5

Ось розбивка деяких найбільших змін у v5. Для повного списку перегляньте історію змін.

Oптимізатор збірки

Станом на 5.0.0, виробничі збірки, створені за допомогою CLI, за замовчуванням застосують оптимізатор збірки.

Оптимізатор збірки – це інструмент, який входить до нашого CLI для зменшення розшарування, використовуючи наше семантичне розуміння Вашого кутового застосування.

Оптимізатор збірки має дві основні завдання. По-перше, ми можемо відзначити частину вашої програми, оскільки pure це покращує струс сорочки, що забезпечується існуючими інструментами, видалення додаткових частин вашої програми, які не потрібні.

Друга справа, що робить оптимізатор збірки, полягає в тому, щоб видалити Angular декоратори з коду виконання програми. Декоратори використовуються компілятором, і не потрібні під час виконання, і їх можна видалити. Кожна з цих завдань зменшує розмір ваших пакетів JavaScript і збільшує швидкість завантаження вашої програми для ваших користувачів.

Angular Universal State Transfer API та підтримка DOM

Тепер ви можете легше розділити стан програми між сторонніми та клієнтськими версіями вашої програми.

Angular Universal – це проект, спрямований на те, щоб допомогти розробникам виконувати серверну обробку (SSR) кутових програм. Після того, як ви зробите свої Angular програми на сервері, а потім завантажите їх у горі сгенерированного HTML, ви можете додати підтримку скребків і сканерів, які не підтримують JavaScript, і ви можете збільшити сприйману продуктивність вашої програми.

У 5.0.0 команда додала ServerTransferStateModuleі відповідну BrowserTransferStateModule. Цей модуль дозволяє генерувати інформацію як частину вашого рендеринга на платформі-сервері, а потім перенести його на сторону клієнта, щоб ця інформація не потребувала регенерації. Це корисно для випадків, наприклад, коли ваша програма отримує дані через HTTP. Передаючи стан з сервера, це означає, що розробникам не потрібно робити другий HTTP-запит, як тільки програма передасть її клієнту. Документація для State Transfer має відбутися протягом наступних кількох тижнів.

Іншою зміною від команди Angular Universal є додавання Domino на платформер-сервер. Domino означає, що ми підтримуємо більше маніпуляцій DOM з коробки в контексті серверної сторони, покращуючи нашу підтримку сторонніх JS та компонентних бібліотек, які не відомі на стороні сервера.

Поліпшення компілятора

Ми вдосконалили Angular компілятор, щоб підтримувати поступову компіляцію. Це забезпечує швидке перебудову, особливо для виробничих збірок та збірок з AOT. Ми також додали функції для наших декораторів та дозволили поставити менші пакети, видаливши пробіл.

Перетворення TypeScript

Під капотом Angular компілятор тепер працює як перетворення TypeScript, що робить поступові перебудови значно швидше. TypeScript перетворення були новою функцією, представленою як частина TypeScript 2.3, що дозволяє нам зачепитись до стандартного контуру компіляції TypeScript.

Ви можете скористатись цим, запустивши ng serve прапорець AOT.

ng serve --aot

Ми рекомендуємо кожному спробувати це. Це стане типовим для майбутнього випуску CLI. Існують деякі відомі проблеми зі швидкістю з проектами з більш ніж тисячею компонентів. Ми хочемо бути впевненими, що проекти будь-якого розміру зазнають цих покращень.

Під час виконання інкрементного побудови AOT з https://angular.io, новий конпілятор компілятора зберігає 95% часу збирання (від більш ніж 40 секунд до менше 2 секунд на наших машинах розробки).

Нашою метою було зробити компіляцію AOT достатньо швидко, щоб розробники могли використовувати її для розробки, усунувши ті відмінності, які розробники іноді стикаються, коли вперше намагаються перейти до виробництва. Команда набрала свої 2-секундні інкрементні AOT перебудовувані цільові показники, і за замовчуванням вона буде перетворювати AOT у майбутній випуск CLI.

У рамках цього переходу на перетворення ми більше не потребуємо genDir і outDir змінилися: тепер ми завжди випромінюємо створені файли для пакетів в node_modules.

preserveWhitespaces

Історично вклади, нові рядки та пробіли у ваших шаблонах були точно відтворені та включені в вашу збірку компілятором. Тепер ви можете вибрати, чи зберегти пробіл, що йде від ваших компонентів та вашої програми.

Ви можете вказати це як частину декоратора кожного компонента, де на даний момент він за замовчуванням true.

@Component({
  templateUrl: 'about.component.html',
  preserveWhitespaces: false
}
export class AboutComponent {}

Або ви можете вказати його широке застосування у своєму tsconfig.json, де воно також за замовчуванням true.

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "es2015",
    "types": []
  },
  "angularCompilerOptions": {
    "preserveWhitespaces": false
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ]
}

Загалом, специфікації на рівні компоненту перевищують загальні специфікації. Надалі команда сподівається на дефолт, щоб за замовчуванням false економити місце для розробників. Не турбуйтеся про свої <pre> теги, вони розумно обробляються.

Дізнайтеся більше про preserveWhitespaces на нашому сайті docs.

Покращена підтримка декоратора

Тепер ми підтримуємо expression lowering в декоратор для лямбда і вартості useValue, useFactory і data в об’єкті літералах. Це дозволяє використовувати значення, які можна обчислити лише під час виконання в декораторах для виражених полів.

Тепер ви можете використовувати лямбда замість названої функції, тобто ви можете виконати код, не впливаючи на ваш d.ts або ваш загальнодоступний API.

Component({
  provider: [{provide: SOME_TOKEN, useFactory: () => null}]
})
export class MyClass {}

Ми також знизимо вирази в рамках useValue.

Component({
  provider: [{provide: SOME_TOKEN, useValue: SomeEnum.OK}]
})
export class MyClass {}

Internationalized Number, Date та Currency Pipes

Ми створили нові Number, Date та Currency Pipes, які підвищують стандартизацію через браузери та усувають потребу в i18n polyfills.

У Angular ми покладалися на браузер, щоб надавати форматування Number, Date та Currency Pipes за допомогою API i18n браузера. Це призвело до того, що більшість розробників потребували заповнення polyfills, що означало, що користувачі бачили в різних веб-переглядачах суперечливі результати, і ми отримали коментарі, які звичайні формати (наприклад, Currency Pipes) не відповідали очікуванням розробника.

У 5.0.0 ми оновили pipes, щоб використовувати нашу власну реалізацію, спираючись на CLDR, щоб забезпечити велику підтримку та конфігурацію мов для будь-яких локалей, які ви хочете підтримати. Ми підготували документ, який порівнює поведінку pipes між v4 та v5.

Якщо ви не готові до нових pipes, ви можете імпортувати, DeprecatedI18NPipesModule щоб отримати доступ до старої поведінки.

Дізнайтеся більше про зміни в наших pipes i18n в журналі змін.

Замініть ReflectiveInjector за допомогою StaticInjector

Для того, щоб видалити ще більше поліфліків, ми замінили ReflectiveInjector на StaticInjector. Цей інжектор більше не вимагає Reflect polyfill, зменшуючи розмір програми для більшості розробників.

Раніше

ReflectiveInjector.resolveAndCreate(providers);

Після

Injector.create(providers);

Покращення швидкості Zone

За умовчанням ми зробили Zone швидше, і ми дозволили повністю обходити Zone для програм, орієнтованих на продуктивність.

Щоб обійти Zone, завантажте вашу програму з “noop” як ваш ngZone.

platformBrowserDynamic().bootstrapModule(AppModule, {ngZone: 'noop'}).then( ref => {} );

Для повного прикладу, перегляньте приклад проекту ng-component-state.

exportAs

Ми додали підтримку для декількох імен для ваших компонентів / директив. Це може бути дуже корисним для того, щоб допомогти користувачам мігрувати без порушення змін. Виводячи директиву з декількома іменами, ви можете створювати нові імена в Angular microsyntax, не порушуючи існуючий код. Це було використано як частина проекту Angular Material при міграції префікса, а також може допомогти іншим авторам компонентів.

Приклад

@Component({
  moduleId: module.id,
  selector: 'a[mat-button], a[mat-raised-button], a[mat-icon-button], a[mat-fab], a[mat-mini-fab]',
  exportAs: 'matButton, matAnchor',
  .
  .
  .
}

HttpClient

У версії 4.3 ми поставили HttpClient @angular/common як менший, простий та потужний спосіб зробити веб-запити на Angular. Новий HttpClient отримав велику похвалу від розробників, тому ми зараз рекомендуємо HttpClient для всіх програм і припиняємо попередню @angular/http library.

Для поновлення HttpClient, вам необхідно замінити HttpModule з HttpClientModule від @angular/common/http кожного з ваших модулів, вводять послугу HttpClient і видаліть всі map(res => res.json()) виклики, які більше не потрібно.

CLI v1.5

Починаючи з v1.5 Angular CLI, ми додали підтримку для Angular v5.0.0 і за замовчуванням генеруємо v5 проекти.

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

Ми також оновлюємо спосіб, яким ми використовуємо .tsconfig файли для більш точного дотримання стандартів TypeScript. Раніше, коли ми виявили лінький завантажений маршрут, і ви вручну вказали список files або include у вашому списку tsconfig.json, ми автоматично додамо ці маршрути, але тепер ми дотримуємось специфікації TypeScript і далі виконуємо це. За замовчуванням CLI налаштовує TypeScript без files або include розділів, тому більшість розробників не будуть залежати від цього.

Angular Forms додає updateOn Blur / Submit

Тепер ви можете запускати перевірку та оцінку оновлень на blur або на submit, а не на кожній вхідній події.

Форми є дуже важливою частиною багатьох застосувань, і якщо у вас є якісь перевірки на стороні сервера або будь-які більш важкі процеси, що спрацьовують при перевірці або змінах значення, які ви хочете виконувати рідше, тепер ви можете контролювати зміну валідації та значення час на контрольному рівні або вкажіть його для всієї форми.

Крім того, тепер ви можете вказати asyncValidators безпосередньо в об’єкті параметрів, а не вказувати його як третій параметр.

Шаблонні форми

до

<input name="firstName" ngModel>

після

<input name="firstName" ngModel [ngModelOptions]="{updateOn: 'blur'}">

або

<form [ngFormOptions]="{updateOn: 'submit'}">

Реактивні форми

до

new FormGroup(value);
new FormControl(value, [], [myValidator])

після

new FormGroup(value, {updateOn: 'blur'}));
new FormControl(value, {updateOn: 'blur', asyncValidators: [myValidator]})

RxJS 5.5

Ми оновили наше використання RxJS до версії 5.5.2 або пізнішої версії. Цей нещодавній випуск RxJS повністю надає розробникам можливість уникнути побічних ефектів від попереднього механізму імпорту з новим способом використання RxJS під назвою “lettable operators“. Ці нові оператори усувають побічні ефекти та проблеми розбиття коду / дерева, які існували за допомогою попереднього методу імплементації операторів.

Замість

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
names = allUserData
.map(user => user.name)
.filter(name => name);

Тепер ти можеш

import { Observable } from 'rxjs/Observable';
import { map, filter } from 'rxjs/operators';
names = allUserData.pipe(
  map(user => user.name),
  filter(name => name),
);

Крім того, RxJS тепер розподіляє версію за допомогою модулів ECMAScript. Новий кутовий CLI буде запускати цю версію за замовчуванням, значно заощаджуючи розмір розшарування. Але якщо ви не використовуєте Angular CLI, ви все одно повинні вказувати на новий розповсюдження. Документацію можна знайти в розділі “Build and Treeshaking” документації операторів, що надають путівник.

Нові події життєвого циклу маршрутизаторів

Ми додали маршрутизаторам нові етапи життєвого циклу, що дозволяє розробникам відстежувати цикл маршрутизатора від початку роботи охоронців до завершення активації. Ці події можуть бути використані для таких речей, як показує обертання на певному outlet маршрутизатора, коли child оновлюється, або для вимірювання ефективності guards та / або resolvers.

Нові події (в послідовності) є GuardsCheckStart, ChildActivationStart, ActivationStart, GuardsCheckEnd, ResolveStart, ResolveEnd, ActivationEnd, ChildActivationEnd. Приклад використання цих подій для запуску / зупинки поворотника може виглядати наступним чином:

class MyComponent {
  constructor(public router: Router, spinner: Spinner) {
    router.events.subscribe(e => {
      if (e instanceof ChildActivationStart) {
        spinner.start(e.route);
      } else if (e instanceof ChildActivationEnd) {
        spinner.end(e.route);
      }
    });
  }
}

Як я можу оновити?

Ми створили керівництво по оновленню Angular, щоб допомогти вам проаналізувати цей процес і дізнатись про будь-які зміни, які ви хочете зробити у своєму коді перед початком процесу оновлення, інструкціями щодо оновлення додатка та інформацією про підготовку майбутніх версій Angular.

Ми видалили багато застарілих інтерфейсів API (як OpaqueToken) і випустили кілька нових застарілих API. Цей посібник допоможе вам проаналізувати зміни, які вам доведеться внести до вашої програми.

Відомі проблеми

https://github.com/angular/angular/issues/19840

Переклад статті “Version 5.0.0 of Angular Now Available

React з використанням ряду особливостей ES6+

За цей рік, в процесі реорганізації Instagram Web, ми насолодилися використанням ряду особливостей ES6+, при написанні нашх React компонентів. Дозвольте мені зупинитися на тих моментах, коли нові можливості мови можуть вплинути на те як ви пишете React додатки, і зроблять цей процес легше і веселіше, ніж коли-небудь.

Класи

До цих пір найбільш помітним з видимих змін в тому, як ми пишемо наші React компоненти, використовуючи ES6 + є те, що ми вирішили використовувати синтаксис визначення класу. Замість того щоб використовувати метод React.createClass для визначення компонента, ми можемо визначити справжній ES6 клас, який розширює клас React.Component:

class Photo extends React.Component {
  render() {
    return <img alt={this.props.caption} src={this.props.src} />;
  }
}

Ви відразу ж ви помітите невелике розходження – вам стає доступним більш лаконічний синтаксис визначення класу:

// The ES5 way
var Photo = React.createClass({
  handleDoubleTap: function(e) { … },
  render: function() { … },
});
// The ES6+ way
class Photo extends React.Component {
  handleDoubleTap(e) { … }
  render() { … }
}

Варто зазначити, що ми відкинули дві дужки і завершальну крапку з комою, а для кожного оголошеного методу ми опускаємо двокрапка, ключове слово function і кому.

Всі методи життєвого циклу компонента, крім одного можуть бути визначені, як можна було б очікувати, з використанням нового синтаксису визначення класів. Конструктор класу в даний час виступає в ролі раніше використовуваного методу componentWillMount:

// The ES5 way
var EmbedModal = React.createClass({
  componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
  constructor(props) {
    super(props);
    // Operations usually carried out in componentWillMount go here
  }
}

Ініціалізатор властивостей

У світі класів ES6 +, типи властивостей і значення за замовчуванням можуть існувати як статичні властивості цього класу. Ці змінні, а також початковий стан компонента, можуть бути визначені з використанням ES7 ініціалізаторів властивостей:

// The ES5 way
var Video = React.createClass({
  getDefaultProps: function() {
    return {
      autoPlay: false,
      maxLoops: 10,
    };
  },
  getInitialState: function() {
    return {
      loopsRemaining: this.props.maxLoops,
    };
  },
  propTypes: {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  },
});
// The ES6+ way
class Video extends React.Component {
  static defaultProps = {
    autoPlay: false,
    maxLoops: 10,
  }
  static propTypes = {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  }
  state = {
    loopsRemaining: this.props.maxLoops,
  }
}

Ініціалізатор властивостей ES7 працюють усередині конструктора класу, де this відноситься до поточного екземпляру класу перед його створенням. Завдяки цьому, початковий стан компонента може залежати від this.props. Примітно, що ми більше не повинні визначати значення props за замовчуванням і початковий стан об’єкта в термінах getter функції.

Arrow функції

Метод React.createClass використовується для виконання деяких додаткових робіт по прив’язці до методів екземпляра компонента, щоб переконатися, що всередині них, ключове слово this буде ставитися до примірника компонента.

// Autobinding, brought to you by React.createClass
var PostInfo = React.createClass({
  handleOptionsButtonClick: function(e) {
    // Here, 'this' refers to the component instance.
    this.setState({showOptionsModal: true});
  },
});

Так як ми не пов’язані використанням методу React.createClass, при визначенні компонентів синтаксисом класів ES6+, здавалося б, що нам потрібно вручну прив’язати методи екземпляра, туди де ми хочемо їх використовувати:

// Manually bind, wherever you need to
class PostInfo extends React.Component {
  constructor(props) {
    super(props);
    // Manually bind this method to the component instance...
    this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
  }
  handleOptionsButtonClick(e) {
    // ...to ensure that 'this' refers to the component instance here.
    this.setState({showOptionsModal: true});
  }
}

На щастя, шляхом об’єднання двох можливостей ES6+ – arrow функції і ініціалізатор властивостей – відмова від прив’язки до примірника компонента стає дуже легким:

class PostInfo extends React.Component {
  handleOptionsButtonClick = (e) => {
    this.setState({showOptionsModal: true});
  }
}

Тіло ES6 arrow функцій використовує той же лексичне this як і код, який її оточує. Це дозволяє нам досягти бажаного результату через те, що ES7 ініціалізатор властивостей знаходяться в області видимості. Загляньте під капот, щоб зрозуміти чому це працює.

Динамічні імена властивостей і шаблонів рядки

Одним з удосконалень литералов об’єктів включає в себе можливість призначати похідні імена властивостями. Спочатку ми могли б зробити щось подібне для установки деякої частини стану компонента:

var Form = React.createClass({
  onChange: function(inputName, e) {
    var stateToSet = {};
    stateToSet[inputName + 'Value'] = e.target.value;
    this.setState(stateToSet);
  },
});

Тепер у нас є можливість створювати об’єкти, в яких імена властивостей визначаються виразом JavaScript під час виконання. Тут ми використовуємо шаблонні рядки, для того щоб визначити, яке властивість встановити в стані компонента:

class Form extends React.Component {
  onChange(inputName, e) {
    this.setState({
      [`${inputName}Value`]: e.target.value,
    });
  }
}

Деструктуризація і поширення атрибутів

Часто при створенні компонентів, ми могли б передати більшість властивостей батьківського компонента до дочірньому компоненту, але не всі з них. У поєднанні ES6 + деструктурірованіе і поширення атрибутів JSX, це стає можливим без танців з бубном:

class AutoloadingPostsGrid extends React.Component {
  render() {
    var {
      className,
      ...others,  // contains all properties of this.props except for className
    } = this.props;
    return (
      <div className={className}>
        <PostsGrid {...others} />
        <button onClick={this.handleLoadMoreClick}>Load more</button>
      </div>
    );
  }
}

Так само ми можемо поєднувати JSX поширення атрибутів з регулярними атрибутами, користуючись простим правилом пріоритету для реалізації перевизначення значень і установки значень атрибута за замовчуванням. Цей елемент отримає перевизначення значення атрибута className, навіть якщо властивість className існує в this.props:

<div {...this.props} className="override">
  …
</div>

Атрибут className цього елемента за замовчуванням має значення «base», якщо не існує властивості className в this.props щоб перевизначити його:

<div className="base" {...this.props}>
  …
</div>

Переклад статті “React on ES6+