This summer, Roma and I launched a series of tweets with helpful Angular tips and tricks. This initiative was warmly welcomed by the community and I decided to write a summary article. Here are my 5 recommendations to share with developers. These tips will be backed up by specific examples from my twitter . They will help you improve your skills, or at least give you a couple of practical tips.
1. Understand the change checking mechanism
There are many great in-depth articles on the internet about checking changes in Angular. For example, this one. So let's just brush up on the basics and get to the tips.
The basics
Angular : Default
OnPush
. tick . Zone.js, . view , .
Default vs OnPush
, Default
. , , OnPush
. . , OnPush
.
@HostListener
Angular OnPush
. , RxJS? ChangeDetectorRef
markForCheck()
, . async
, . .
:
<div *ngIf="stream$ | async as result">
β¦
</div>
, falsy-? ngIf . , :
@Directive({
selector: "[ngLet]"
})
export class LetDirective<T> {
@Input()
ngLet: T;
constructor(
@Inject(ViewContainerRef) container: ViewContainerRef,
@Inject(TemplateRef) templateRef: TemplateRef<LetContext<T>>
) {
container.createEmbeddedView(templateRef, new LetContext<T>(this));
}
}
NgZone
OnPush
, . NgZone
.runOutsideAngular()
. . Default
. , mousemove
scroll
. RxJS- : β , β , :
class ZonefreeOperator<T> implements Operator<T, T> {
constructor(private readonly zone: NgZone) {}
call(observer: Observer<T>, source: Observable<T>): TeardownLogic {
return this.zone.runOutsideAngular(
() => source.subscribe(observer)
);
}
}
export function zonefull<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
return map(value => zone.run(() => value));
}
export function zonefree<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
return source => source.lift(new ZonefreeOperator(zone));
}
,
@HostListener
, βEventManagerPlugin
. open-source- ng-event-plugins. . .
2. RxJS
RxJS β . , . , β
β . , , :
, , . CSS RxJS:
@Directive({
selector: "[sticky]",
providers: [DestroyService]
})
export class StickyDirective {
constructor(
@Inject(DestroyService) destroy$: Observable<void>,
@Inject(WINDOW) windowRef: Window,
renderer: Renderer2,
{ nativeElement }: ElementRef<HTMLElement>
) {
fromEvent(windowRef, "scroll")
.pipe(
map(() => windowRef.scrollY),
pairwise(),
map(([prev, next]) => next < THRESHOLD || prev > next),
distinctUntilChanged(),
startWith(true),
takeUntil(destroy$)
)
.subscribe(stuck => {
renderer.setAttribute(
nativeElement,
"data-stuck",
String(stuck)
);
});
}
}
RxJS Angular- . RxJS , . , , .
, . , RxJS, β . - . ( ).
3. TypeScript
Angular- TypeScript. , , . strict: true
. . cannot read property of null undefined is not a function.
TypeScript β , , , . , API. . RxJS- fromEvent
:
// currentTarget
export type EventWith<
E extends Event,
T extends FromEventTarget<E>
> = E & {
readonly currentTarget: T;
};
// fromEvent
export function typedFromEvent<
E extends keyof GlobalEventHandlersEventMap,
T extends FromEventTarget<EventWith<GlobalEventHandlersEventMap[E], T>>
>(
target: T,
event: E,
options: AddEventListenerOptions = {},
): Observable<EventWith<GlobalEventHandlersEventMap[E], T>> {
return fromEvent(target, event, options);
}
, , currentTarget
β , .
API, , , . , .
TypeScript. , . : any
. , unknown
.
TypeScript, . . , , , : , , β number
. TypeScript , runtime :
export function assert<T, K extends keyof T>(
assertion: (input: T[K]) => boolean,
messsage: string
): PropertyDecorator {
return (target, key) => {
Object.defineProperty(target, key, {
set(this: T, initialValue: T[K]) {
let currentValue = initialValue;
Object.defineProperty(this, key, {
get(): T[K] {
return currentValue;
},
set(this: T, value: T[K]) {
console.assert(assertion(value), messsage);
currentValue = value;
}
});
}
});
};
}
, super()
? Angular , :
β . Web Audio API Angular, . .
4. Dependency Injection.
DI β , Angular . , . .
, DI, .
RxJS
, RxJS. , , , . Angular :
@Injectable()
export class DestroyService extends Subject<void> implements OnDestroy {
ngOnDestroy() {
this.next();
this.complete();
}
}
DI. requestAnimationFrame
. . , :
DI β . window
navigator
β Angular Universal . , . . . WINDOW
DOCUMENT
:
export const WINDOW = new InjectionToken<Window>(
'An abstraction over global window object',
{
factory: () => {
const {defaultView} = inject(DOCUMENT);
if (!defaultView) {
throw new Error('Window is not available');
}
return defaultView;
},
},
);
, open-source-, . Angular Universal - . , , - .
. DI . .
5. ,
Angular . . . : . , , .
β Β« Β»? ngOnChanges
. -, , , - .
β . - , .
β , . . , . , β , .
, , .
Template reference variables
Angular @ViewChild
. , :
<input #input>
<button (click)="onClick(input)">Focus</button>
template reference variable , . . .
, DOM-, ? @ViewChild(MyComponent, {read: ElementRef})
, , exportAs
:
@Directive({
selector: '[element]',
exportAs: 'elementRef',
})
export class ElementDirective<T extends Element> extends ElementRef<T> {
constructor(@Inject(ElementRef) {nativeElement}: ElementRef<T>) {
super(nativeElement);
}
}
ComponentFactoryResolver
. , ngComponentOutlet
? . β , Dependency Injection. ngComponentOutlet
Injector
, .
, , . . .
, . open-source- ng-polymorpheus. , , ngTemplateOutlet
, ngContentOutlet
. , ! .
. , . !