Angular 2+ лучшие практики (style guide)

Решила перевести на русский style guide нового Angular, так как это материал из разряда того, что лучше знать с начала работы с фреймворком.

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

Ищешь четкое руководство по Angular синтаксису, соглашениям и структуре приложения?
Что ж, приглашаю к прочтению! Это руководство по стилю содержит предпочтительные договоренности, и что наиболее важно — объясняет почему следует делать именно так.

Словарь стиля

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

Делайте так — то, чему нужно следовать всегда. Всегда — возможно, слишком строгое слово. Указаний, которым нужно всегда следовать довольно мало. С другой стороны у вас должен быть действительно исключительный случай, чтобы нарушить указания такого рода.

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

Избегайте реализаций, которых вы никогда не должны делать. Примеры кода, который следует избегать, имеют характерный красный заголовок.

Почему? — дает пояснение, почему следует следовать данной рекомендации.

Соглашение по файловой структуре

Некоторые примеры кода отображают папку, которая содержит один или более файлов с аналогичным названием.
Например, hero.component.ts и hero.component.html.
Руководство использует ярлык hero.component.ts|html|css|spec, чтобы представить эти различные файлы. Использование такого сокращения делает файловую структуру данного гайда легче для чтения и более краткой.

Принцип единственной ответственности

Применяйте принцип единственной ответственности (SRP) ко всем компонентам, сервисам и другим сущностям.
Это помогает сделать приложение чище, легче для чтения и поддержки, а также более тестируемым.

Правило одного

Стиль 01-01

Определяйте только одну вещь, такую как сервис или компонент для каждого файла.
Рекомендуется ограничивать файл 400 строками кода.

Почему?

Один компонент для каждого файла делает его гораздо легче для чтения, поддержки, и помогает избежать коллизий с командой, работающей над тем же ресурсом.

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

Одиночный компонент может быть по-умолчанию экспортирован из своего файла, что облегчает lazy loading с роутером.

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

Следующий отрицательный пример определяет AppComponent, загружает приложение, определяет Hero модель и загружает героев с сервера в одном и том же файле.

Не делайте так.

app/heroes/hero.component.ts

/* avoid */
 
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Component, OnInit } from '@angular/core';
 
class Hero {
  id: number;
  name: string;
}
 
@Component({
  selector: 'my-app',
  template:`
    <h1>{{title}}</h1>
    <pre>{{heroes | json}}</pre>
 `,
 styleUrls: ['app/app.component.css']
})
class AppComponent implements OnInit {
  title = 'Tour of Heroes';

  heroes: Hero[] = [];

  ngOnInit() {
    getHeroes().then(heroes => this.heroes = heroes);
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  exports: [ AppComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

platformBrowserDynamic().bootstrapModule(AppModule);

const HEROES: Hero[] = [
  {id: 1, name: 'Bombasto'},
  {id: 2, name: 'Tornado'},
  {id: 3, name: 'Magneta'},
];

function getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES); // TODO: get hero data from the server;
}

Хорошая практика — помещать  компонент и вспомогательные классы в разные файлы.

Пример распределения по отдельным файлам.

По мере роста приложения соблюдение этого правила становится даже более важным.

Маленькие функции

Стиль 01-02

Определяйте маленькие функции

Рекомендуется лимит не более 75 строчек.

Почему?

Маленькие функции легче тестировать, особенно если они делают одну вещь и служат одной цели.

Маленькие функции способствуют повторному использованию кода.

Маленькие функции проще читать.

Маленькие функции проще поддерживать.

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

Именование

Соглашение по именованию весьма важно для поддержки и читабельности. Это руководство рекомендует соглашения об именовании для файлов и сущностей.

Общие принципы именования

Стиль 02-01

Будьте последовательны в именовании.

Следуйте паттерну, который описывает обозначение фичи, а затем ее тип.

Рекомендуемый паттерн — feature.type.ts.

Почему?

Соглашение об именовании помогает обеспечить последовательный способ определить содержание файла с первого взгляда.

Последовательность во всем проекте очень существенна. Последовательность во всей команде, компании приводит к огромной эффективности.

Соглашение об именовании должно помочь найти желаемый код быстрее и делает легче его понимание.

Имена папок и файлов должны ясно выражать их суть.

Например, app/heroes/hero-list.component.ts может содержать компонент, который управляет списком героев.

Разделяйте имена файлов точкой или тире

Стиль 02-02

Используйте тире для разделения слов в описательном имени.

Используйте точки для отделения описательного имени от типа.

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

Рекомендованный паттерн — feature.type.ts.

Используйте общепринятые имена, включающие .service, .component, .pipe, .module, и .directive.

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

Почему?

Тип в имени обеспечивает последовательный способ быстро идентифицировать, чем является файл.

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

Полное имя типа, такое как .service, наглядно и недвусмысленно. Такие аббревиатуры как .srv, .svc, and .serv могут запутать.

Тип в имени обеспечивает паттерн совпадения для любых автоматических задач.

Имена сущностей и файлов

Стиль 02-03

Используйте одно и то же именование для всех сущностей после обозначения в имени их особенности.

Используйте CamelCase стиль для именования классов.

В имени должна упоминаться сущность.

Добавляйте к имени общепринятый суффикс (такой как Component, Directive, Module, Pipe, или Service).

Также и к названиям файлов добавляйте общепринятые символы (такие как .component.ts, .directive.ts, .module.ts, .pipe.ts, или .service.ts).

Почему?

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

Например, у компонента

@Component({ ... })
export class HeroDetailComponent { }

файл будет называться hero-detail.component.ts.

Имя сервисов

Для именования всех сервисов после упоминания в имени его особенности используйте одно и то же именование.

Сопровождайте все названия классов сервисов приставкой Service.

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

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

Вы можете предпочесть назвать сервис логирования сообщений Logger, а не LoggerService. Решите насколько это исключение согласовывается с вашим проектом. Как обычно стремитесь к согласованности.

Почему?

Следование последовательному пути помогает быстро идентифицировать и ссылаться на сервисы.

Ясное имя сервиса, например, Logger не требует суффикса.

Имя сервиса Credit является существительным и требует суффикс, как и другие случаи когда не очевидно является ли сущность сервисом или чем-то иным.

Бутстрэппинг

Стиль 02-05

Помещайте бутсрэппинг и логику платформы приложения в файл main.ts

Включайте в логику бутсрэппинга обработку ошибок.

Избегайте помещения логики приложения в main.ts. Вместо этого полагается размещать ее в компонент или в сервис.

Почему?

Это следует из соответствующего соглашения о логике запуска приложения.

Этому соглашению следуют другие технологические платформы.

main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .then(success => console.log(`Bootstrap success`))
  .catch(err => console.error(err));

Селекторы директив

Стиль 02-06

Используйте lowCamelCase для именования селекторов директив.

Почему?

Определяя имена свойств в директивах, мы связывает ее с видом через соответствующее имя атрибута.

Angular HTML парсер чувствителен к регистру и распознает lowCamelCase.

Кастомный префикс к компонентам

Стиль 02-07

Пишите название селектора элемента через дефис, в нижнем регистре (например, admin-users)

Используйте для него кастомный префикс. Например, префикс toh представляет Tour of Heroes, а префикс admin — фичу для администрирования.

Используйте префикс, которые идентифицирует область фичи или само приложение.

Почему?

Это предотвращает коллизии с компонентами из других приложений и с нативными HTML элементами.

Это облегчает использование компонента в других приложениях.

Компонент проще идентифицировать в DOM.

app/heroes/hero.component.ts


/* avoid */

// HeroComponent is in the Tour of Heroes feature
@Component({
  selector: 'hero'
})
export class HeroComponent {}

 

app/users/users.component.ts


/* avoid */

// UsersComponent is in an Admin feature
@Component({
  selector: 'users'
})
export class UsersComponent {}

 

app/heroes/hero.component.ts


@Component({
  selector: 'toh-hero'
})
export class HeroComponent {}

 

app/users/users.component.ts


@Component({
  selector: 'admin-users'
})
export class UsersComponent {}

Кастомный префикс для директив

Стиль 02-08

Используйте для селекторов директив кастомный префикс (например, toh в Tour of Heroes)

Пишите некомпонентные селекторы в lowerСamelСase, за исключением селекторов, предназначенных для совпадения с нативными HTML атрибутами.

Почему?

Это предотвращает коллизию имен.

Директивы легко идентифицировать.

app/shared/validate.directive.ts

/* avoid */

@Directive({
  selector: '[validate]'
})
export class ValidateDirective {}

 

app/shared/validate.directive.ts

@Directive({
  selector: '[tohValidate]'
})
export class ValidateDirective {}

Имена доя пайпов (Pipes)

Стиль 02-09

Используйте одинаковое имя для всех пайпов после именования их особенности.

Почему?

Это обеспечивает последовательный путь быстро идентифицировать и упомянуть пайпы.

Имя пайпа Имя файла
@Pipe({ name: ‘ellipsis’ }) export class EllipsisPipe implements PipeTransform { } ellipsis.pipe.ts
@Pipe({ name: ‘initCaps’ }) export class InitCapsPipe implements PipeTransform { } init-caps.pipe.ts

Имена для файлов юнит-тестов

Стиль 02-10

Именуйте файлы с тестами так же как сами компоненты, для которых предназначен тест.

Добавляйте к этому имени суффикс .spec.

Почему?

Обеспечивает быструю индентификацию файлов тестов.

Обеспечивает паттерн совпадения для karma и для других инструментов запуска тестов.

Примеры: heroes.component.spec.ts, logger.service.spec.ts, ellipsis.pipe.spec.ts.

Имена для файлов тестов E2E

Стиль 02-11

Добавляет к файлам E2E тестов после именования фичи, которую они тестят, суффикс .e2e-spec.

Почему?

Это обеспечивает быструю индентификацию файлов end-to-end тестов.

Обеспечивает паттерн совпадения для инструментов запуска тестов и автоматизации.

Пример — heroes.e2e-spec.ts.

Имена для NgModule

Стиль 02-12

Сопровождайте имя модуля суффиксом Module.

Добавляйте именам файлов .module.ts расширение.

Именуйте модуль после фичи и папки, в которой она находится.

Почему?

  • Это обеспечивает надежный путь быстро идентифицировать и упомянуть модули.
  • UpperСamelСase регистр общепринят для идентификации объектов, которые могут быть реализованы с использованием конструктора.
  • Легко идентифицировать модуль как источник аналогично именованной функции.

Добавляйте к модулю роутинга суффикс RoutingModule.

Оканчивайте название файла модуля роутинга с -routing.module.ts.

Почему?

Модуль роутинга — модуль предназначенный исключительно для конфигурирования Angular роутера.

Последовательное именование класса и соглашение об имени файла помогают легко находить и проверять эти модули.

 

Имя сущности Имя файла
@NgModule({ }) export class AppModule { } app.module.ts
@NgModule({ }) export class HeroesRoutingModule { } heroes-routing.module.ts

Соглашения по коду

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

Классы

Стиль 03-01

Используйте UpperCamelCase регистр для именования классов.

Почему?

Это общепринятое соглашение для имен классов.

Классы могут быть реализованы и создавать экземпляр.

По соглашению UpperCamelCase регистр идентифицирует конструируемые сущности.

Константы

Стиль 03-02

Объявляйте переменные с const, если их значения не должны будут измениться в течении времени жизни приложения.

Почему?

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

Рекомендуется писать имя const переменных в lowerCamelCase регистре.

Почему?

  • lowetCamelCase (heroRoutes) легче для чтения и понимания, чем традиционные UPPER_SNAKE_CASE имена (HERO_ROUTES).
  • Традиционное именование констант UPPER_SNAKE_CASE отражает эру до современных IDE, которые быстро обнаруживают объявление const переменных.
  • TypeScript предотвращает случайное переназначение.

 

Будьте толерантны к существующим переменным константам, которые пишутся в UPPER_SNAKE_CASE.

Почему?

Традиционный UPPER_SNAKE_CASE регистр остается популярным и распространяющимся, особенно в сторонних приложениях. Редко когда стоит их менять, рискуя сломать существующий код.

export const mockHeroes   = ['Sam', 'Jill']; // prefer
export const heroesUrl    = 'api/heroes';    // prefer
export const VILLAINS_URL = 'api/villains';  // tolerate

Интерфейсы

Стиль 03-03

Задавайте имена интерфейсам в UpperCamelCase регистре.

Рекомендуется не использовать в имени префикс I.

Рекомендуется использовать класс вместо интерфейса.

Почему?

Руководство TypeScript не одобряет префикс I.

Одиночный класс — меньше кода, чем класс плюс интерфейс.

Класс может действовать как интерфейс (используйте implements вместо extends)

Интерфейс-класс может обеспечить lookup token в dependency injection.

app/shared/hero-collector.service.ts

/* avoid */
 
import { Injectable } from '@angular/core';
 
import { IHero } from './hero.model.avoid';
 
@Injectable()
export class HeroCollectorService {
  hero: IHero;
 
  constructor() { }
}

 

app/shared/hero-collector.service.ts

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

import { Hero } from './hero.model';

@Injectable()
export class HeroCollectorService {
  hero: Hero;

  constructor() { }
}

Свойства и методы

Стиль 03-04

Используйте lowerCamelCase регистр для имен свойств и методов.

Избегайте префиксов с нижним подчеркиванием для приватных свойств и методов.

Почему?

Это общепринятое соглашение о свойствах и методах.

JavaScript необходимы правильные приватные свойства и методы.

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

app/shared/toast.service.ts

/* avoid */
 
import { Injectable } from '@angular/core';
 
@Injectable()
export class ToastService {
  message: string;
 
  private _toastCount: number;
 
  hide() {
    this._toastCount--;
    this._log();
  }
 
  show() {
    this._toastCount++;
    this._log();
  }
 
  private _log() {
    console.log(this.message);
  }
}

 

app/shared/toast.service.ts

import { Injectable } from '@angular/core';
 
@Injectable()
export class ToastService {
  message: string;
 
  private toastCount: number;
 
  hide() {
    this.toastCount--;
    this.log();
  }
 
  show() {
    this.toastCount++;
    this.log();
  }
 
  private log() {
    console.log(this.message);
  }
}

Пустая строка в импорте

Стиль 03-06

Рекомендуется оставлять пустую строку между импортами из сторонних модулей и между импортами из текущего приложения.

Рекомендуется упорядочивать сущности в строке импорта из модуля в алфавитном порядке.

Почему?

Пустая строка разделяет ваши и чужие разработки.

Упорядочивание по алфавиту облегчает чтение и нахождение сущностей.

app/heroes/shared/hero.service.ts

/* avoid */

import { ExceptionService, SpinnerService, ToastService } from '../../core';
import { Http } from '@angular/http';
import { Injectable } from '@angular/core';
import { Hero } from './hero.model';

 

app/heroes/shared/hero.service.ts

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

import { Hero } from './hero.model';
import { ExceptionService, SpinnerService, ToastService } from '../../core';

Структура приложения и NgModules

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

Весь код приложения помещайте в папку src. Все фичи размещайте в их собственных папкаx, с их собственными NgModule.

Все содержимое одной сущности помещайте в один файл.

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

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

Используйте соглашение об именовании, рассмотренное в этой гайде.

LIFT

Стиль 04-01

Структурируйте приложение так, чтобы вы могли быстро отыскать (Locate) код и идентифицировать (Identify), взглянув мельком.

Держите настолько ровную структуру на сколько вы можете (Flattest) и старайтесь (Try) придерживаться DRY практики (не повторяйте код).

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

Почему?

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

Месторасположение

Стиль 04-02

Располагайте код интуитивно понятно, просто и быстро.

Почему?

Для эффективной работы вы должны быть способны находить файлы быстро, особенно когда вы не знаете (или не помните) имя файлов.

Размещение связанных файлов рядом с друг другом в интуитивно понятном месте сохраняет время.

Наглядная файловая структура сильно улучшает работу с проектом для вас и для тех, кто будет работать с ним после.

Идентификация

Стиль 04-03

Именуйте файл так, чтобы вы немедленно понимали, что он содержит и представляет.

Давайте файлам наглядные имена и держите внутри файла только один компонент.

Избегайте файлов с несколькими компонентами, сервисами или их сочетания.

Почему?

Тратя меньше времени на рысканье в поисках кода, вы становитесь более эффективны.

Длинные имена файлов значительно лучше, чем короткие, но неясные сокращенные.

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

Плоская файловая структура (без иерархий)

Стиль 04-04

Держите файловую структуру плоской так долго, как это возможно.

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

Рекомендуется настроить IDE на скрытие отвлекающих, неуместных файлов, таких как сгенерированные .js и .js.map файлы.

Почему?

Никто не хочет искать файл среди семи уровней папок.

Плоскую структуру легче просматривать.

С другой стороны психологи считают, что люди начинают напрягаться, когда число соседних вещей превышает девяти.

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

Примите решение, которое вам будет комфортно. Используйте плоскую структуру пока не почувствуйте очевидную ценность от создания новой папки.

T-DRY (Try to be DRY)

Стиль 04-05

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

Почему?

Не повторять код — важно, но не критично если это приносит в жертву другие элементы LIFT. Вот почему принцип называется T-DRY.

Например, излишне называть имя шаблона hero-view.component.html, так как с .html разрешением становится очевидно, что это вид.

Но если что-то не настолько ясно или/и отходит от соглашения, то пропишите это.

Обобщенные принципы структуры проекта

Стиль 04-06

Начинайте с малого, но обращайте внимание, когда приложение катится вниз по наклонной.

Имейте как краткосрочное виденье реализации, так и долгосрочное.

Помещайте весь код приложения в папку src.

Рекомендуется создавать папку для компонента, когда он содержит разнородные сопровождающие файлы (.ts, .html, .css и .spec)

Почему?

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

Компоненты часто состоят из четырех файлов (*.html, *.css, *.ts, и *.spec.ts) и могут быстро загрязнять папки.

Гибкая структура папок и файлов:

ангуляр структура

В то время как компоненты в отдельных папках в целом предпочтительней, другой вариант может лучше подходить для маленьких приложений — не разделять на папки. Это добавляет еще 4 файла в существующую папку, но уменьшает вложенность папок. Чтобы вы не выбрали, будьте последовательны.

Структура папок для сущностей

Стиль 04-07

Создавайте папки для сущностей, которые они представляют.

Почему?

  • Разработчик сможет находить и идентифицировать, что каждый файл представляет, взглянув мельком.
  • Структура настолько плоская насколько это возможно, и нет повторяющихся и излишних имен.
  • LIFT принципы покрывают все важное.
  • Организация и сохранение контента в соответствии с LIFT принципами помогает не стать приложению захламленным.
  • Когда много файлов, например, более 10, их легче находить с помощью последовательной файловой структурой и более сложно в плоской структуре.

 

Создавайте NgModule для каждой области функций.

Почему?

NgModule облегчает lazy load особенности маршрутизации.

NgModule облегчает изоляцию, тестирование и переиспользование сущностей.

Корневой модуль приложения

Стиль 04-08

Создавайте NgModule в корневой папке приложения, например, /src/app.

Почему?

Для каждого приложения требуется как минимум один корневой NgModule.

Рекомендуется называть корневой модуль app.module.ts.

Почему?

Это облегчает его поиск и идентификацию.

Функциональные модули (Feature modules)

Стиль 04-09

Создавайте NgModule для всех определенных функций приложения; например, для Heroes функций.

Помещайте функциональный модуль в папку с тем же именем, что область функции, например, в app/heroes.

Давайте файлу функционального модуля, такое имя, чтобы оно отражало область функции и папку; например, app/heroes/heroes.module.ts.

Давайте сущности функционального модуля такое имя, чтобы оно отражало функциональную область, папку и файл; например, app/heroes/heroes.module.ts определяет HeroesModule.

Почему?

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

Он идентифицирует четкую совокупность связанных компонентов, которые включены в функциональную область.

Функциональный модуль может легко подгружаться как сразу же, так и позже (lazy loading).

Функциональный модуль определяет четкие границы между специфичной функциональность и другими особенностями приложения.

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

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

Общий функциональный модуль (Shared feature module)

Стиль 04-10

Создавайте общий функциональный модуль с именем SharedModule в shared папке; например, app/shared/shared.module.ts определяет SharedModule.

Объявляйте компоненты, директивы и пайпы в shared модулях, когда они буду переиспользованны или на них будут ссылаться через компоненты, объявленные в других функциональных модулях.

Если содержимое shared модуля упоминается в рамках всего приложения, то рекомендуется использовать имя SharedModule.

Избегайте указания в списке providers shared модуля сервисов. Сервис — обычно одиночка (singleton), который вводится для всего приложения единожды или в конкретном функциональном модуле.

Импортируйте все модули необходимые для ресурсов в SharedModule; например, CommonModule и FormsModule.

Почему?

  • SharedModule будет содержать компоненты, директивы и пайпы, которым могут быть нужны функции из других обших модулей; например, ngFor из CommonModule.

 

Объявляйте все компоненты, директивы и пайпы в SharedModule.

Экспортируйте  из SharedModule все сущности, которые нужны другим функциональным модулям.

Почему?

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

 

Избегайте определения в providers SharedModule одиночки(singleton), используемого во всем приложении. Умышленный singleton — OK.
Будьте внимательны.

Почему?

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

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

Центральный функциональный модуль (Core feature module)

Стиль 04-11

Сбор многочисленных, вспомогательных, единожды используемых классов внутри core модуля облегчает ясность структуры функционального модуля.

Рекомендуется называть core модуль CoreModule.

Импорт CoreModule в корневой AppModule уменьшает сложность и подчеркивает его роль как организатора приложения в целом.

Создавайте функциональный модуль CoreModule в папке core ( app/core/core.module.ts определяет CoreModule).

Помещайте singleton service, чей экземпляр будет использоваться во всем приложении, в CoreModule.(например, ExceptionService и LoggerService).

Импортируйте все модули необходимые для сущностей приложения в CoreModule (например, CommonModule и FormsModule).

Почему?

  • CoreModule обеспечивает приложение одним или более singleton сервисов. Angular регистрирует провайдеры с app root инжектором, делая singleton экземпляр каждого сервиса доступным для любого компонента, что в них нуждается, когда бы это компонент не был бы загружен (сразу или после)
  • CoreModule будет содержать singleton сервис. Когда lazy loaded модули импортируют их, это будет новый экземпляр, не предназначенный быть одиночкой для всего приложения.

 

Собирая приложения, помещайте одиночно используемые компоненты в CoreModule.

Импортируйте их единожды (в AppModule), когда приложение стартует, и никогда не импортируйте где-либо еще (это, например, NavComponent и SpinnerComponent).

Почему?

  • Реальна ситуация, когда приложения могут иметь несколько одиночно используемых компонентов (например, спиннеры, модальные диалоги), которые упоминаются только в AppComponent шаблоне.
  • Они не импортируется везде, так что они не типа shared. Тем не менее они слишком большие и неопрятные, чтобы оставить из в корневой папке.

 

Избегайте импорта CoreModule где-либо кроме AppModule.

Почему?

  • Функциональные модули с отложенной загрузкой (lazily loaded), напрямую импортирующие CoreModule сделают собственные копии сервисов, что наверняка приведет к не желаемому результату.
  • Сразу же загруженные функциональные модули уже имеют доступ к инжектору AppModule, и таким образом и к CoreModule сервисам.

 

Экспортируйте все сущности, которые AppModule будет импортировать из CoreModul, делая доступными для других функциональных модулей.

Почему?

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

Вы хотите, чтобы все приложение использовало один singleton-экземпляр. Вы не хотите, чтобы каждый модуль имел свой собственный экземпляр singleton сервиса. Тем не менее есть реальная опасность, что это произойдет случайно, если CoreModule предоставляет сервис.

AppModule в результате немного меньше, так как многие app/root классы перемещаются в другие модули.

AppModule стабилен, так как вы будете добавлять будущие компоненты и провайдеры в другие модули, а не сюда.

AppModule скорее уполномочен импортировать модули, а не делать работу.

Он сфокусирован на этой основной задаче — управлять приложением в целом.

Предотвращение повторного импорта core module

Стиль 04-12

Только корневой AppModule должен импортировать CoreModule.

Создавайте guard (защиту) против повторного импорта CoreModule и через его логику вызывайте ошибку.

Почему?
Guards предотвращает повторный импорт CoreModule.

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

app/core/module-import-guard.ts

export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
  if (parentModule) {
    throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
  }
}

 

app/core/core.module.ts

import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
 
import { LoggerService } from './logger.service';
import { NavComponent } from './nav/nav.component';
import { throwIfAlreadyLoaded } from './module-import-guard';
 
@NgModule({
  imports: [
    CommonModule // we use ngFor
  ],
  exports: [NavComponent],
  declarations: [NavComponent],
  providers: [LoggerService]
})
export class CoreModule {
  constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
    throwIfAlreadyLoaded(parentModule, 'CoreModule');
  }
}

Lazy Loaded папки (папки для фич, подгружаемых отложено)

Стиль 04-13

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

Помещайте содержимое lazy loaded функций в lazy loaded папку.

Типичная lazy loaded папка содержит роутер-компонент, его дочерние компоненты и связанные с ними ассеты и модули.

Почему?

Отдельная папка облегчает идентификацию и изоляцию функционального контента.

Никогда не ипортируйте lazy loaded папки напрямую

Стиль 04-14

Избегайте позволять модулям в соседних и родительских папках напрямую импортировать lazy loaded модуль.

Почему?

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

Компоненты

Имя селектора компонентов

Стиль 05-02

Используйте dashed-case или kebab-case для именования селекторов элементов компонентов.

Почему?

Это держит название элементов совместимыми со спецификацией Custom Elements.

app/heroes/shared/hero-button/hero-button.component.ts

/* avoid */

@Component({
  selector: 'tohHeroButton',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}

 

app/heroes/shared/hero-button/hero-button.component.ts

@Component({
  selector: 'toh-hero-button',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}

Компоненты как элементы

Стиль 05-03

Давайте компонентам селектор противоположный атрибуту или селектору класса.

Почему?

Шаблоны компонентов содержат HTML и дополнительный Angular синтаксис шаблонов. Они отображают контент. Разработчики помещают компоненты на страницу как будто они найтивные HTML элементы и веб компоненты.

Взглянув на html шаблон, будет проще опознать компонент.

app/heroes/hero-button/hero-button.component.ts

/* avoid */

@Component({
  selector: '[tohHeroButton]',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}

Помещение шаблонов и стилей в отдельный файл

Стиль 05-04

Помещайте шаблоны и стили в отдельный файл, когда они включают в себя более 3 строк.

Давайте файлу шаблона имя [component-name].component.html, где [component-name] — имя компонента.

Давайте файлу стилей имя [component-name].component.css, где [component-name] — имя компонента.

Помечайте templateUrl префиксом ./.

Почему?

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

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

Angular TypeScript Language Service (forthcoming) обещает побороть этот недостаток для HTML шаблонов в тех редакторах, которые это поддерживают; с CSS стилями это не поможет.

Вам не потребуется менять templateUrl, если вы будете перемещать файлы компонента, пока файлы вместе.

Префикс ./ — стандартный синтаксис для относительных URLs; не зависимо от текущей способности Angular работать без этого префикса.

app/heroes/heroes.component.ts

/* avoid */
 
@Component({
  selector: 'toh-heroes',
  template: ` 
    <div>
      <h2>My Heroes</h2>
      <ul class="heroes">
        <li *ngFor="let hero of heroes | async" (click)="selectedHero=hero">
          <span class="badge">{{hero.id}}</span> {{hero.name}}
        </li>
      </ul>
      <div *ngIf="selectedHero">
        <h2>{{selectedHero.name | uppercase}} is my hero</h2>
      </div>
    </div>
  `,
  styles: [`
    .heroes {
      margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class HeroesComponent implements OnInit {
  heroes: Observable&amp;amp;amp;amp;lt;Hero[]&amp;amp;amp;amp;gt;;
  selectedHero: Hero;
 
 constructor(private heroService: HeroService) { }
 
  ngOnInit() {
    this.heroes = this.heroService.getHeroes();
  }
}

Декорируйте вводимые (input) и выводимые (output) свойства

Стиль 05-12

Используйте классы декораторы @Input() и @Output() вместо вводимых и выводимых свойств @Directive и @Component метаданных.

Рекомендуется размещать @Input() или @Output() на той же линии, что и свойство, которое декорируете.

Почему?

Это облегчает чтение и идентификацию — какие свойства в классе вводимые и выводимые.

Если вам понадобится переименовать свойство или имя события, связанного с @Input или @Output, вы можете это сделать в одном месте.

Объявление методанных, прикрепленных к директиве — короче и, следовательно, читабельней.

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

app/heroes/shared/hero-button/hero-button.component.ts

/* avoid */
 
@Component({
  selector: 'toh-hero-button',
  template: `<button></button>`,
  inputs: [
    'label'
  ],
  outputs: [
    'change'
  ]
})
export class HeroButtonComponent {
  change = new EventEmitter<any>();
  label: string;
}

 

app/heroes/shared/hero-button/hero-button.component.ts


@Component({
  selector: 'toh-hero-button',
  template:`<button>{{label}}</button>`
})
export class HeroButtonComponent {
  @Output() change = new EventEmitter<any>();
  @Input() label: string;
}

Избегайте алиасов для inputs и outputs

Стиль 05-13

Избегайте алиасов для input и outut, за исключением тех случаев, когда это служит важной цели.

Почему?

Два имени для одного и того же свойства (одно приватное, другое публичное) по сути сбивает с толку.

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

app/heroes/shared/hero-button/hero-button.component.ts


/* avoid pointless aliasing */

@Component({
  selector: 'toh-hero-button',
  template:  `<button>{{label}}</button>`
})
export class HeroButtonComponent {
  // Pointless aliases
  @Output('changeEvent') change = new EventEmitter<any>();
  @Input('labelAttribute') label: string;
}

 

app/app.component.html

<!-- avoid -->
<toh-hero-button labelAttribute="OK" (changeEvent)="doSomething()">
</toh-hero-button>

 

app/heroes/shared/hero-button/hero-button.component.ts

@Component({
  selector: 'toh-hero-button',
  template: `<button>{{label}}</button>`
})
export class HeroButtonComponent {
  // No aliases
  @Output() change = new EventEmitter<any>();
  @Input() label: string;
}

 
 

app/heroes/shared/hero-button/hero-highlight.directive.ts


import { Directive, ElementRef, Input, OnChanges } from '@angular/core';

@Directive({ selector: '[heroHighlight]' })
export class HeroHighlightDirective implements OnChanges {

  // Aliased because `color` is a better property name than `heroHighlight`
  @Input('heroHighlight') color: string;

  constructor(private el: ElementRef) {}

  ngOnChanges() {
    this.el.nativeElement.style.backgroundColor = this.color || 'yellow';
  }
}

app/app.component.html

<toh-hero-button label="OK" (change)="doSomething()">
</toh-hero-button>

<!-- `heroHighlight` is both the directive name and the data-bound aliased property name -->
<h3 heroHighlight="skyblue">The Great Bombasto</h3>

Последовательность членов класса

Стиль 05-14

Помещайте свойства вверху соответствующего метода.

Помещайте приватные члены после публичных, в алфавитном порядке.

Почему?

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

app/shared/toast/toast.component.ts

/* avoid */
 
export class ToastComponent implements OnInit {
 
  private defaults = {
    title: '',
    message: 'May the Force be with you'
  };
  message: string;
  title: string;
  private toastElement: any;
 
  ngOnInit() {
    this.toastElement = document.getElementById('toh-toast');
  }
 
  // private methods
  private hide() {
    this.toastElement.style.opacity = 0;
    window.setTimeout(() => this.toastElement.style.zIndex = 0, 400);
  }
 
  activate(message = this.defaults.message, title = this.defaults.title) {
    this.title = title;
    this.message = message;
    this.show();
  }
 
  private show() {
    console.log(this.message);
    this.toastElement.style.opacity = 1;
    this.toastElement.style.zIndex = 9999;
 
    window.setTimeout(() => this.hide(), 2500);
  }
}

 

app/shared/toast/toast.component.ts

export class ToastComponent implements OnInit {
  // public properties
  message: string;
  title: string;
 
  // private fields
  private defaults = {
    title: '',
    message: 'May the Force be with you'
  };
  private toastElement: any;
 
  // public methods
  activate(message = this.defaults.message, title = this.defaults.title) {
    this.title = title;
    this.message = message;
    this.show();
  }
 
  ngOnInit() {
    this.toastElement = document.getElementById('toh-toast');
  }
 
  // private methods
  private hide() {
    this.toastElement.style.opacity = 0;
    window.setTimeout(() => this.toastElement.style.zIndex = 0, 400);
  }
 
  private show() {
    console.log(this.message);
    this.toastElement.style.opacity = 1;
    this.toastElement.style.zIndex = 9999;
    window.setTimeout(() => this.hide(), 2500);
  }
}

Делегируйте сложную логику приложения сервисам

Стиль 05-15

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

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

Почему?

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

Логика в сервисе может быть легко изолирована в юнит тестах. В то же время вызов логики в компонентах может быть легко подменен (mocked).

Это удаляет зависимости и прячет от компонента реализацию деталей.

Держит компонент стройным, аккуратным и сфокусированным.

app/heroes/hero-list/hero-list.component.ts


/* avoid */
 
import { OnInit } from '@angular/core';
import { Http, Response } from '@angular/http';
 
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/map';
 
import { Hero } from '../shared/hero.model';
 
const heroesUrl = 'http://angular.io';
 
export class HeroListComponent implements OnInit {
  heroes: Hero[];
  constructor(private http: Http) {}
  getHeroes() {
    this.heroes = [];
    this.http.get(heroesUrl)
      .map((response: Response) => <Hero[]>response.json().data)
      .catch(this.catchBadResponse)
      .finally(() => this.hideSpinner())
      .subscribe((heroes: Hero[]) => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }
 
  private catchBadResponse(err: any, source: Observable<any>) {
    // log and handle the exception
    return new Observable();
  }
 
  private hideSpinner() {
    // hide the spinner
  }
}

 

app/heroes/hero-list/hero-list.component.ts

import { Component, OnInit } from '@angular/core';
 
import { Hero, HeroService } from '../shared';
 
@Component({
  selector: 'toh-hero-list',
  template: `...`
})
export class HeroListComponent implements OnInit {
  heroes: Hero[];
  constructor(private heroService: HeroService) {}
  getHeroes() {
    this.heroes = [];
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }
}

Не ставьте префикс перед output свойствами

Стиль 05-16

Именуйте события без префикса on.

Именуйте обработчики событий с префиксом on, за которым следует имя события.

Почему?

Это совместимо со встроенными событиями, такими как клик по кнопке.

Angular позволяет альтернативный синтаксис on-*. Если событие само с префиксом on, то это приведет к on-onEvent в связывающем выражении.


Помещайте логику представления в класс компонента

Стиль 05-17

Помещайте логику представления в класс компонента, а не в шаблон.

Почему?

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

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

app/heroes/hero-list/hero-list.component.ts

 

/* avoid */
 
@Component({
  selector: 'toh-hero-list',
  template: `

    <section>
      Our list of heroes:
      <hero-profile *ngFor="let hero of heroes" [hero]="hero">
      </hero-profile>
      Total powers: {{totalPowers}}
      Average power: {{totalPowers / heroes.length}}
    </section>

  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
}

 

app/heroes/hero-list/hero-list.component.ts

@Component({
  selector: 'toh-hero-list',
  template: `
    <section>
      Our list of heroes:
      <toh-hero *ngFor="let hero of heroes" [hero]="hero">
      </toh-hero>
      Total powers: {{totalPowers}}
      Average power: {{avgPower}}
    </section>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
 
  get avgPower() {
    return this.totalPowers / this.heroes.length;
  }
}

Директивы

Используйте директивы для прокачивания элементов

Стиль 06-01

Используйте атрибутные директивы, когда у вас есть логика представления без шаблона.

Почему?

Атрибутные директивы не связаны с шаблоном.

Элемент может иметь для применения более чем одну атрибутные директиву.

HostListener/HostBinding против host метаданных

Стиль 06-03

Рекомендуется предпочитать @HostListener и @HostBinding вместо свойств host декораторов @Directive и @Component.

Будьте последовательны в своем выборе.

Почему?

Свойство связанное с @HostBinding или метод, связанный с @HostListene, может быть изменен только в одном месте — в классе директивы. А если вы используете свойство методанных host, вы должны модифицировать свойства, объявленные внутри контроллера и метаданные, связанные с директивой.

app/shared/validator.directive.ts

import { Directive, HostBinding, HostListener } from '@angular/core';
@Directive({
  selector: '[tohValidator]'
})
export class ValidatorDirective {
  @HostBinding('attr.role') role = 'button';
  @HostListener('mouseenter') onMouseEnter() {
// do work
  }
}

Сравните с менее предпочтительной альтернативой — host метаданными.
Почему?
Host метаданные — единственный термин для запоминания и не требуется дополнительный ES импорт.

app/shared/validator2.directive.ts

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

@Directive({
  selector: '[tohValidator2]',
  host: {
    '[attr.role]': 'role',
    '(mouseenter)': 'onMouseEnter()'
  }
})
export class Validator2Directive {
  role = 'button';
  onMouseEnter() {
    // do work
  }
}

Сервисы

Сервисы и синглтоны (Singletons)

Стиль 07-01

Используйте сервисы и синглтоны внутри одного и того же инжектора. Используйте их для распространения данных и функционала.

Почему?
Сервисы идеальны для распространения методов в функциональной области или во всем приложении.

Сервисы идеальны для распространения данных с сохранением состояния.

app/heroes/shared/hero.service.ts

export class HeroService {
  constructor(private http: Http) { }

  getHeroes() {
    return this.http.get('api/heroes')
    .map((response: Response) => <Hero[]>response.json().data);
  }
}

Единственная ответственность

Стиль 07-02

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

Создавайте новый сервис, как только сервис начинает превышать свое единственное назначение.

Почему?

Когда у сервиса множество обязанностей он становится труден для тестирования.

Когда у сервиса множество обязанностей, то компоненты или сервисы (которые его инжектят) несут тяжесть каждой из них.

Обеспечение сервисом (Providing a service)

Стиль 07-03

Проводите сервис в компонент верхнего уровня, через который он будет распространен.

Почему? Angular инжектор иерархический.

При введении сервиса в компонент верхнего уровня, его экземпляр распространяется и доступен для всех дочерних компонентов этого компонента верхнего уровня.

Почему?

Сервис, распространяющий методы или состояния — идеально.

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

app/app.component.ts

import { Component } from '@angular/core';
 
import { HeroService } from './heroes';
 
@Component({
  selector: 'toh-app',
  template: `
      <toh-heroes></toh-heroes>
    `,
  providers: [HeroService]
})
export class AppComponent {}

 

app/heroes/hero-list/hero-list.component.ts

import { Component, OnInit } from '@angular/core';
 
import { Hero, HeroService } from '../shared';
 
@Component({
  selector: 'toh-heroes',
  template: `

<pre>{{heroes | json}}</pre>

    `
})
export class HeroListComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit() {
    this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes);
  }
}

Используйте класс декоратор @Injectable()

Стиль 07-04

Используйте класс декоратор @Injectable() вместо параметра декоратора @Inject, когда используете типы как токены для зависимостей сервиса.

Почему?

Angular Dependency Injection (DI) механизм разрешает собственные зависимости сервиса базирующиеся на объявленных параметрах конструктора сервиса.

Когда сервис принимает только зависимости связанные с типом токена, @Injectable() синтаксис значительно менее многословный по сравнению с использованием @Inject() для каждого индивидуального параметра конструктора.

app/heroes/hero-list/hero-list.component.ts

/* avoid */

export class HeroArena {
  constructor(
    @Inject(HeroService) private heroService: HeroService,
    @Inject(Http) private http: Http) {}
}

 

app/heroes/shared/hero-arena.service.ts

@Injectable()
export class HeroArena {
  constructor(private heroService: HeroService,
  private http: Http) {}
}

Данные сервисов

Общение с сервером через сервис

Стиль 08-01

Делайте рефакторинг логики для осуществления операций с данными и взаимодействия данных с сервисом.

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

Почему?

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

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

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

Детали управления данными, такие как заголовки, HTTP методы, кэширование, обработка ошибки и повторная логика неуместны в компонентах и в других потребителях данных.

Сервис инкапсулирует эти детали. Легче развивать их внутри сервиса без влияния на его клиентов.
Также потребителей легче тестировать с реализацией мок сервиса.

Жизненный цикл (Lifecycle hooks)

Используйте Lifecycle hooks для работы с важными событиями, которые Angular предоставляет.

Реализация lifecycle hook интерфейсов

Стиль 09-01

Реализуйте lifecycle hook интерфейсы.

Почему?

Lifecycle интерфейсы предписывают для метода типовые обозначения. Используйте их для избежания орфографических и синтаксических ошибок.

app/heroes/shared/hero-button/hero-button.component.ts

/* avoid */

@Component({
  selector: 'toh-hero-button',
  template: `<button>OK</button><button>`
})
export class HeroButtonComponent {
  onInit() { // misspelled
    console.log('The component is initialized');
  }
}

 

app/heroes/shared/hero-button/hero-button.component.ts

@Component({
  selector: 'toh-hero-button',
  template: `<button>OK</button>`
})
export class HeroButtonComponent implements OnInit {
  ngOnInit() {
    console.log('The component is initialized');
  }
}

Приложение

Полезные инструменты и подсказки для Angular.

Codelyzer

Стиль A-01

Следуя этому гайду, используйте codelyzer.
Установите в codelyzer правила, соответствующие вашим нуждам.

Файловые шаблон и сниппеты

Стиль A-02

Используйте файловые шаблоны или сниппеты для помощи в следовании стилям и паттернам.

Далее представлены шаблоны и/или снипетты для некоторых редакторов и IDEs.

Рекомендуется использовать сниппеты для Visual Studio Code, которые следуют этим стилям и предписаниям.

Рекомендуется использовать  сниппеты для Atom , которые следуют этим стилям и предписаниям.

Рекомендуется использовать  сниппеты для Sublime Text, которые следуют этим стилям и предписаниям.

Рекомендуется использовать сниппетты для Vim, которые следуют этим стилям и предписаниям.

Хотите быть в курсе новых статей?