- Уроки
- Переменные
- Lifecycle
- Data binding
- Directives
- Pipe
- Components
- Templates
- Router
- Modules
- Services
- Dependency Injection
- Change Detection
- RxJS
- State Managment
- Unit test
- Быстрый старт:
- Начало работы:
- Оффлайн:
- Вопросы к собесу:
- Рекомендуемые ссылки:
- Быстрый старт:
- Начало работы:
- Оффлайн
<h2 #p2>Some text</h2>
<form #f="ngForm">
<button [disable]="!f.form.valid">Button</button>
</form>
Порядок выполнения "хуков" сохранен
NgOnChanges
- срабатывает каждый раз когда меняетсяInput
проперти когда мы передаем в нашу компоненту. Вызывается передNgOnInit
NgOnInit
- срабатывает один раз при послеNgOnChanges
и иинициализует какие-то данные, т.к. работает с данными (вызывает сервисы).NgDocheck
- в отличии отNgOnChnages
он отрабатывает при каждом прогонеNgOnAction
??? Например: есть форма, при каждом измении каждого элемента будет вызыватьNgDoCheck
- это доргая операция и имеет смысл при кастомных валидацияхNgAfterContentInit
-NgAfterContentChecked
-NgAfterViewInit
-NgAfterViewChecked
-NgOnDestroy
- используется перед тем как Ангуляр уничтожит компоненту. Используется для уничтожения всяких евентов и т.д. Чтобы обезопасить себя наmemory leaks
.
{{property}}
- Интерполяция - example:<p>{{message}}</p>
;[]="property"
- Property Binding - example:<h2 [textContent]="message"><h2>
()="method()"
- Event Binding - example:<button (click)="sayHello()">Say hello</button>
;[(ngModel)]="property"
- Two Way Binding- example:
<input [(ngModel)]="username"
; - bannas in the box
- It's =
[ngModel]="username" (ngModelChange)="...."
- example:
Questions:
- Что такое NgModel?
- Components
- Структурные - помечаются звездочкой - добавляют и удаляют элементы (ngIf and ngFor)
- Атрибуты - ничего не делают с дом деревом, но они меняют поведение наших элементов
- Оптмизирует расходы памяти
- ngIf
- ngSwitch
- ngFor - микросинтасксис:
index
,fist
,last
,even
andodd
<li *ngFor="let user of users; let i = index">
<p>name: {{user.name}} - index: {{i}}</p>
</li>
<template ngFor let-her [ngForOf]="heroes">
<div>{{hero}}</div>
</template>
- ngStyle
<span [style.background-color]="'red'"></span>
<span bind-style.background-color="'red'"></span>
<span [ngStyle]="{'background-color':'red'}"></span>
- ngClass
<div [ngClass]="string|Array|obj"></div>
<div [ngClass]="{class: true, class-two: false }"></div>
- ngNonBindable - игонрирование бандинга
<span ngNonBindable>some {{content}} text</span> --> some {{content}} text
- Кастоманая директива
@Directive({
selector: '[appHightLight]'
})
export class HighLigtDirective {
constructor(private el: ElementRef, private render: Render2) {
this.render.setStyle(this.el.nativeElement, 'background-color', 'yellow');
}
}
@Directive({
selector: '[appHightLightHover]'
})
export class HighLigtDirective {
@Input() hightlightColor: string;
// <span appHightLightHover [hightlightColor]="colorFromModel" >
@Input('appHightLightHover') hightlightColor: string;
// <span [appHightLightHover]="colorFromModel" >
constructor(private el: ElementRef, private render: Render2) {
}
@HostListener('mouseenter') onMouseEnter() {
this.hightlight('red');
}
@HostListener('mouseleave') onMouseEnter() {
this.hightlight(null);
}
private hightlight(color: string): void {
this.render.setStyle(this.el.nativeElement, 'background-color', color);
}
}
@Directive({
selector: '[appHide]'
})
export class AppHideDirective {
@Input() set appHide(condition: boolean) {
if(!condition) {
this.viewContainer.createEmberddedView(view.templateRef)
} else {
this.viewContainer.clear();
}
}
constructor(private templateRef: TemplateRef<any>,
private viewContainer: ViewContainterRef) {
// templateRef -
// viewContainer - ссылка куда будет вставлен компонент
}
// Реализация может быть ещё через `ngOnChanges`
// <div *appHide="propFromModel|true|false">Some text</div>
}
@NgModules({
declarations: {
HighLigtDirective,
HighLigtDirective,
AppHideDirective
}
})
// usage
<div appHightLight appHightLightHover></div>
- Зачем нужны - мотификация данных перед отображением пользователю. Примеры: сортировка, фильтрация и т.д.
- Pure - строго регламентируется когда будет работать|сработает. Бдуте работать когда он получит примитив или изменяется !ссылка! обьекта
- ImPure - при любом изменении в нашем компоненте он будет срабатывать, аля
NgOnChange
. Можно изменить черезpure: false|true
в декораторе Pipe при обьявлении класса.
<p>{{data | async }}<p>
<!-- Print `RECEIVIDE DATA` after 5 sec -->
export class AsyncPipeComponent {
public data = new Promise(resolve => {
setTimeout(() => {
resolve('RECEIVIDE DATA!')
}, 500);
})
}
<p>{{ now | date: 'dd.MM.yyyy' }}</p> -> format string
<p>{{expression | pipeName:parameter1:parameter2 }}</p>
<p>{{'Angular2' | slice:3 | uppercase }}</p>
@Component({
providers: [AgePipe, DatePipe],
})
class SomseComponent {
public currentDay: string = new DatePipe().transform(new Date(), 'yyyy/MM/dd');
constructor(private _agePipe: AgePipe) {
this.age = this._agePipe.transform(this.allEmployees, 30)
}
}
@Pipe({
name: 'sortByStatus',
})
export class SortPipe implements PipeTransform {
tranfsofrm(allTasks: any[], status: string): any {
if(!allTasks || !status) {
return allTask
}
return allTask.filter((task) => task.status === status);
}
}
@NgModule({
declarations: [
SortPipe,
]
})
// Example
<li *ngFor="let task of tasks | sortedByStatus: status" >{{task.name}} - {{task.status}}</li>
-
Компоненты
- это "строительные" блоки UI в приложении. Они простым TS классом и это декоратор который представляет собой набор мета информации о классе. Он наследуется отDirective
. -
Component
vsDirective
- всегда естьteamplate
(teamplateUrl
orteamplate
) -
Component Methods
- Определяеют его поведение -
Component Methods
- Определяеют его состояние
ng-content
- если из какой-то внешней компоненты вызываем нашу мы можем передать кусок разметкиng-template
-ng-
animation
- list of animation of this componentschangeDetection
-encapsultaion
- Говорит о том, как мы обрабатывать текущие компонент (!)entryComponents
-interpolation
- заменяется обычный кавычки для интеполяции. Example:[}}, {{]
->}}property{{
moduleId
- можно переопределить модуль в котором он будет использоваться, плохая штукаtemplate
- inline HTMLtemplateUrl
- относительный путь к шаблоныstyles
- inlint StylesstyleUrls
- относительный путь к стилямviewPropviders
-
Унаследовано от Directives:
* exportAs
-* host
-* input
-* outputs
-* providers
- Своего рода синглотн для компоненты. При каждом обьявлении нового компонента будет создан новый синглотон. Что являетсяV8 killers
* queries
-* selector
-
@Input
- связавыает родителя и дете@Output
- не поддерживает bubling
По-сути пришли с React
- Жестко привязанны к нашему приложению
- используют какие-то сервисы
- call to back-end
- Работают с данными
- Обычно связаны с
Service(s)
этого компонента
- Не имеют внутренной логики
- Реюзабельны
- Их поведение определяется теми св-вами которыми мы в них передаем
From HTML:
<a routerLink="../home"></a>
<a [routerLink]="['./pageWithParams', 10, 'video']" [routerLinkActive]="'active'"></a>
From Component
this.router.navigate(['pageWithParams', 2, 'video'], { queryParams: {
redirectedFromClass: true
}})
Base href:
<base href="/">
{ path: 'redirect', redirectTo: 'home', pathMatch: 'full' }
где:
pathMatch
- это
ActivatetRoute
- получаем доступ к тем параметрам которые сейчас есть на странице. При этом мы можем подписаться на изменение параметров
class SomeComponent {
public routerParams = {
};
constructor(private router: ActivetRouter) {}
ngOnInit() {
this.router.params.subsribe(data => {
this.routerParams.id = data['id'];
this.routerParams.type = data['type'];
})
this.router.data.subscribe(data => {
console.log(data);
})
}
}
ActivatetRouteSnapshot
- один раз пришли и больше не попадаем. Только при инициализации
- JIT - динамическая компиляция
- AOT - статическая компиляция - более производительтая, меньше бандл
!! Отдельно почитать
// Create export module
@NgModule({
import: [
CommonModule,
],
declarations: [
TodoListComponent,
TodoItemComponent,
TodoItemPipe,
TodoItemDirective,
],
providers: [
TodoService,
],
exports: [
TodoListComponent
],
})
export class TodoModule { }
// Import module to another module
// !!! Здесь также будет доступен TodoService
@NgModule ({
import: [
CommonModule,
// Import module here
TodoModule,
],
declarations: [
MainComponent,
],
bootstrap: [MainComponent]
})
export class MainModule { }
- BrowserModule - для работы в браузере
- FormsModule - для работы с формами
- HttpModule - для работы с back-end
DI - Dependency Injection - Иньекция зависимостей - это такая система которая связывает разные части приложения, которая позвооляет одной части приложения получить доступ к другой. Таким образом это позволяется создавать более гибку систему. При таком подходе к "компонента" снимается "необходимость" создавать свои зависимости, и все сводиться к тому, что только знает как с ними работать (API и т.д.)
Принцип:
- инверсия управления
- слабое связываения
// плохой пример
export class Car {
public tires: Tires;
public engine: Engine;
// 1. Констуктор очень тяжело тестировать
// 2. В случае если классы один из классов поменяется его необходимо будет
// вручную везде поменять
constructor() {
this.tires = new Tires();
this.engine = new Engine();
}
}
// хороший пример
export class Car {
// 1. Таким образом мы отвязали наш класс от зависимостей классов
// 2. Удобнее тестировать
constructor(
public tires: Tires,
public engine: Engine,
) { }
}
const car = new Car(new Tires(), new Engine());
DI включает в себя (в рамках angular):
- Token - id зависимости по которому мы сможем к ней обращаться. Может быть двух видов:
строка
иликласс
- Provider - он связывает токен со списком зависимостей и указывает ангуляру как создавать обьект по данному токена
- Injector - содержит в себе набор ссылкой который отвечает за разрешение зависимостей и их инжектирование при создании обьектов
- Dependency - сама зависимость, то что непосредственно инжектируется в компонент
Как зарегистировать зависимсоть Как обьявить список зависимостей которые необходимы
Когда мы обьявляем зависимость в компоненте (private _http: HTTP
в примере ниже) он ищет её в регистре зависимостей. Если он находить он выдает её экземпляр, а если её нет тогда создает новый экземпляр и инжектирует её в компонент
@Injectable()
export class ExampleService {
constructor(private _http: HTTP) {}
public getData(): void {
return { data: 'data example' }
}
}
Бывают:
- встроенные в Angular (HTTP and e.t.c)
- собственны:
- для создания собственной зависимости принимается декоратор
@Injectable
- для регистрациия собственно зависимости необходимо добавить её в
providers
. После этого она попадает вInjection Three
и там храниться
- для создания собственной зависимости принимается декоратор
- Provide streams (data models)
- Provide operations with data
- Helpers
Как можно обьвить сервисы в providers
:
providers: [ MyService ]
- короткая записьproviders: [ { provide: MyService, useClass: MyService } ]
- равноценну примеру выше, т.к. имена одинаковыеproviders: [ { provide: 'SecondInstanse', useClass: MyService } ]
providers: [ { provide: 'IS_PROD', useValue: true } ]
providers: [ { provide: MyService, useFactory: myFactory, deps: ['IS_PROD'] } ]
providers: [ { provide: 'MyServiceToken', useExisting: MyService }]
где:
useFactory
-function myfactory(IS_PROD) { return IS_PROD ? new MyService : new FakeMyService }
provide
- токен, про который говорилось выше. Пример использованияMyService
- класс,'SecondInstance'
- это строка- ``
удобно при тестировании - т.к. в useClass
- можно впихнуть другой сервис
декоратор @Optional
- говорит о том, что если зависимость на найдена - то мы попытаемся сами решить тарбла, в противном случае выдаст ошибку
есть ещё InjectionToken
- самому почитать
class MyService {
constructor(@Optional() private dataService: DataService) {
this.data = this.dataService ? this.dataService.getData() : 'local';
}
getData() {
return { data: this.data }
}
}
связка ng-content + Host()
- Application State Update - происходит после любого действия (ajax, click, калькуляция и т.д.)
- View State Propogation - проходит всю модель - срабатывает после разрблокировки Event Loop
Change Detector - дерево
Angular Change Detector проверяет все это дерево за один проход
Из чего он состоит: C * N
C
- Время на проверку на каждый байдинг (тут уже сделали все в ангуляре)N
- Количество байдингов (можем манипулировать только этим)
тут что-то про JIT and OAT
-
ng serve --aot
-
ng build --prod
-
Он позвляет компилировать шаблоны на этапе сборки
-
Позволяет отлавливать ошибки на этапе сборки
-
AOT
для прода
Плюсы:
- Проверка ошибок в шаблонах
- Маленький бандл
- Быстрая инициализация загрузки
Недостатки:
- Время сборки очень большое
- Для больших приложений нельзя использовать
OAT
для дева - Ограничение синтаксиса TS
WORKFLOW:
- dev:
JIT
- prod:
AOT
Как мы можем влиять на количество бандингов:
- Immutable Object
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MovieComponent {
constructor(private ref: ChangeDetectorRef) {
}
}
ChangeDetectionStrategy.OnPush
- этот компонент будет перерендироваться только когда инпут или output props были изменыны! А также его потомки
ChangeDetectorRef
- рефернс на change detection
класс. У него есть различные методы отвечающие за управление обновлением компонента (аля React)
по дефолту стоит глубокая проверка
;
- При событии
- XHR, Fetch
- Timers
Observables vs Promises
- Observable is not created when there are no subsribers
- Observable could be reused (Is be recreated for new subsriber)
- Observable could be synchrounous
Это функция которая берет Oбзеревбел и возвращает функции
const sourse = Obserable.create((obs) => {
obs.next('val');
obs.err('err');
return () => {
console.log('--Destroy--');
}
});
sourse.subscribe(
(val) => {},
(err) => {},
() => { console.log('--Compleated--')}
)
Methods:
- next()
- error()
- complete()
Callbacks:
- 3 function: val, err and complate
- Subject instance
Подписка / Отписка / Закрыть
sourse.subscribe();
sourse.unsubsribe();
sourse.compleate();
Obserable.of('tets')
- оборачивает результат и сразу compleateObserable.interval(100).take(3)
- каждые мс выполняет что-то, до 3 разObserable.timer(300)
- через мс вызовиться и закомплитаетсяObserable.throw(new Error())
- кидает сразу ошибкуObserable.empty()
- просто завершается, обычно используется сdefaultIsEmpty()
Obserable.never()
- ни коплитается, не делает ошибки, не уведомляетObserable.mapTo(null)
- мапает на какое-то значениеObserable.from([1, 2, 3])
- преобразовывает массив в последовательные нотификашки !! SYNC!!
concat
merge
forkJoin
- подписывается одновременно на все обьекты (мб вместо finally)zip
// re1 - 500mc
// re2 - 100mc
// re3 - 50mc
// reErr - error
Obserable.concat(re1, re2, re3) // 500, 100, 50 - 3 event
Obserable.concat(re1, re2, reErr, re3) // 500, 100, Err !!! without 50
Obserable.merge(re1, re2, re3) // 50, 100, 500 - 3 event
Obserable.merge(re1, re2, reErr, re3) // Err !!! without all
Obserable.forkJoin(re1, re2, re3) // [500, 100, 50] - 1 event склеит все
Obserable.forkJoin(re1, re2, reErr, re3) // Err !!! without all - 1 event
Obserable.zip(re1, re2, re3) // [500, 100, 50] - будет ждать по одному нотификации
Obserable.zip(re1, re2, reErr, re3) // Err !!! without all - 1 event
catch
retry
retryWhen
onErrorResumeNext
sourse
.catch((err) => {
// Пробуем обработать ошибку, тогда возвращаем обзеребл дальше
})
.retry(3) // Пробует переодписаться N раз (т.к. холодный)
.retryWhen((error) => {
// Принимает поток ошибок
})
.onErrorResumeNext(otherSubject) // Если ошибка переключаемся на другой оbserable
.subscribe(
(val) => {}
(err) => {}
() => {}
)
publish
share
shareReplay
multicast
connect
do
- логирование
Subject
- после завершения его уже нельзя переюзатьAsyncSubject
- подписчики получают инфу только после того как subject был закомплитенReplaySubject
- пытается проиграть последнюю нотификацию которая былаBehaviorSubject
- может передать дефолтное значение, он получает его как только подписался. А уже после получает next
- Hot - один продюсер для всех подписчиков (паттерн Proxy) и тут уже важен тайминг
- Cold - каждый новый подписчик создаст новый продюсер (пример с HTTP)
Cold -> Hot
.multicast() + new Subject() + .connect()
.multicast() + () => new Subject() + .refCount()
.multicast() + new Subject() === .publish()
.multicast() + () => new Subject() + .refCount() === .share()
-
Дополнительно:
NgRx
- Redux + RxJs + Angular
-
One Way Data Flow
-
Single Source Of Truth - Store только один
-
State is Readonly
-
Changes are mande with pure functions - Они же Reducers
Дополнительные:
Содержание:
- Тестирование DOM
- Тестирование вложенных компонентов
- Подход к тестам через HOST
- Stub
- Что в целом нужно тестировать
- fixture.detectedChanges();
- nativeElement = fixture.nativeElement;
- debugElement = fixture.debugElement;
- debugElement.query(By.css('.some-class'))
- component = fixture.componentInstance;
- Shallow Testing
- Stub Components
- NO_ERRORS_SCHEMA
- CUSTOM_ELEMENTS_SCHEMA
@Component({
template: `
<app-some-component
[publication]="publication" (like)="onLiked($event)"
></app-some-component>
`;
})
class TestHostComponent {
public publication: Publication = { id: 1, title: '11', ImageSrc: 'ss.jps' };
public likedPublication: Publication;
public onLiked(likedPublication: Publication) { this.likedPublication = likedPublication };
}
describe('Some Component Test', () => {
let testHost: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
let publication: Publication;
let publicationLogService: Partial<PublicationLogService>;
beforeEach(() => {
publicationLogService = {
logPublication: jasmine.createSpy('logPublication');
}
TestBed.configurateTestingModule({
declarations: [
SomeComponent, TestHostComponent,
],
providers: [
{
provide: PublicationLogService,
userValue: publicationLogService,
}
]
})
});
berforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent);
testHost = fixutre.componentInstance;
fixture.detectedChanges();
})
it('should raise publication like', () => {
const expectedLikedPublication = { id: 1, title: '11', ImageSrc: 'ss.jpg' };
const likeButton = fixture.debugElement.query(By.css('.like-button'));
likeButton.triggerEventHandler('click', null);
expect(testHost.likedPublication).toEqual(expectedLikedPublication);
});
it('should log publication', () => {
expect(publicationLogService.logPublication).toHaveBeenCalled();
});
});
beforeEach(() => {
logInService = { logInOnService: jasmine.createSpy('logInOnServer')};
TestBed.configurationTestingModules({
declarations: [
LoginWidgetTestbedAndDependencyComponent ],
providers: [{ proide: LogInService, useValue: logInService }]
]
})
});
it('should call server', () => {
const button = fixture.debugElement.quert(By.css('.date-button'));
button.triggerEventHandler('click', null);
expect(logInService.logInOnService).toHaveBeenCalled();
});
- ngIf, nfFor and e.t.c
- disable, hidden
- @Input(), @Output
- Component lifecycle hooks
- Pipes and how they affect DOM