Components
Components are the main building block for Angular applications.
Lifecycle hooks
Hook method | Purpose | Timing |
---|---|---|
ngOnChanges | 当 Angular 设置或重新设置数据绑定的输入属性时响应。 该方法接受当前和上一属性值的 SimpleChanges 对象 | 在 ngOnInit() 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。 |
ngOnInit | 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。 | 在第一轮 ngOnChanges() 完成之后调用,只调用一次。 |
ngDoCheck | 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。 | 紧跟在每次执行变更检测时的 ngOnChanges() 和 首次执行变更检测时的 ngOnInit() 后调用。 |
ngAfterContentInit | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 | 第一次 ngDoCheck() 之后调用,只调用一次。 |
ngAfterContentChecked | 每当 Angular 检查完被投影到组件或指令中的内容之后调用。 | ngAfterContentInit() 和每次 ngDoCheck() 之后调用 |
ngAfterViewChecked | 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。 | ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。 |
ngOnDestroy | 每当 Angular 每次销毁指令/组件之前调用并清扫。 | 在 Angular 销毁指令或组件之前立即调用。 |
Component interaction
Intercept input property changes with a setter
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-name-child',
template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
@Input()
get name(): string { return this._name; }
set name(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
private _name = '';
}
Parent listens for child event
子组件暴露一个 EventEmitter
属性,当事件发生时,子组件利用该属性 emits
(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。
子组件的 EventEmitter
属性是一个输出属性,通常带有@Output 装饰器。
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-voter',
template: `
<h4>{{name}}</h4>
<button (click)="vote(true)" [disabled]="didVote">Agree</button>
<button (click)="vote(false)" [disabled]="didVote">Disagree</button>
`
})
export class VoterComponent {
@Input() name: string;
@Output() voted = new EventEmitter<boolean>();
didVote = false;
vote(agreed: boolean) {
this.voted.emit(agreed);
this.didVote = true;
}
}
父组件 VoteTakerComponent
绑定了一个事件处理器(onVoted()
),用来响应子组件的事件($event
)并更新一个计数器。
import { Component } from '@angular/core';
@Component({
selector: 'app-vote-taker',
template: `
<h2>Should mankind colonize the Universe?</h2>
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
<app-voter *ngFor="let voter of voters"
[name]="voter"
(voted)="onVoted($event)">
</app-voter>
`
})
export class VoteTakerComponent {
agreed = 0;
disagreed = 0;
voters = ['Narco', 'Celeritas', 'Bombasto'];
onVoted(agreed: boolean) {
agreed ? this.agreed++ : this.disagreed++;
}
}
Parent interacts with child via local variable
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: 'app-countdown-parent-lv',
template: `
<h3>Countdown to Liftoff (via local variable)</h3>
<button (click)="timer.start()">Start</button>
<button (click)="timer.stop()">Stop</button>
<div class="seconds">{{timer.seconds}}</div>
<app-countdown-timer #timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export class CountdownLocalVarParentComponent { }
父组件不能通过数据绑定使用子组件的 start
和 stop
方法,也不能访问子组件的 seconds
属性。
把本地变量(#timer
)放到(<countdown-timer>
)标签中,用来代表子组件。这样父组件的模板就得到了子组件的引用,于是可以在父组件的模板中访问子组件的所有属性和方法。
这个例子把父组件的按钮绑定到子组件的 start
和 stop
方法,并用插值来显示子组件的 seconds
属性。
Parent calls an @ViewChild()
<mat-horizontal-stepper #stepper>
</mat-horizontal-stepper>
export class BaseInstallComponent {
@ViewChild('stepper') stepper: MatHorizontalStepper;
}
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: 'app-countdown-parent-vc',
template: `
<h3>Countdown to Liftoff (via ViewChild)</h3>
<app-countdown-timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {
@ViewChild(CountdownTimerComponent)
private timerComponent: CountdownTimerComponent;
}
Parent and children communicate via a service
MissionService
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable()
export class MissionService {
// Observable string sources
private missionAnnouncedSource = new Subject<string>();
private missionConfirmedSource = new Subject<string>();
// Observable string streams
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
missionConfirmed$ = this.missionConfirmedSource.asObservable();
// Service message commands
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}
confirmMission(astronaut: string) {
this.missionConfirmedSource.next(astronaut);
}
}
Dynamic Component
// tslint:disable: directive-selector
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[adHost]',
})
export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
export class AdBannerComponent implements OnInit, OnDestroy {
@Input() ads: AdItem[];
currentAdIndex = -1;
@ViewChild(AdDirective, {static: true}) adHost: AdDirective;
interval: any;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngOnInit() {
this.loadComponent();
this.getAds();
}
ngOnDestroy() {
clearInterval(this.interval);
}
loadComponent() {
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
const adItem = this.ads[this.currentAdIndex];
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
const viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent<AdComponent>(componentFactory);
componentRef.instance.data = adItem.data;
}
getAds() {
this.interval = setInterval(() => {
this.loadComponent();
}, 3000);
}
}
Angular Elements
AppComponent
import { Component, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { PopupService } from './popup.service';
import { PopupComponent } from './popup.component';
@Component({
selector: 'app-root',
template: `
<input #input value="Message">
<button (click)="popup.showAsComponent(input.value)">Show as component</button>
<button (click)="popup.showAsElement(input.value)">Show as element</button>
`,
})
export class AppComponent {
constructor(injector: Injector, public popup: PopupService) {
// Convert `PopupComponent` to a custom element.
const PopupElement = createCustomElement(PopupComponent, {injector});
// Register the custom element with the browser.
customElements.define('popup-element', PopupElement);
}
}
PopupComponent
// tslint:disable: variable-name
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'my-popup',
template: `
<span>Popup: {{message}}</span>
<button (click)="closed.next()">✖</button>
`,
animations: [
trigger('state', [
state('opened', style({transform: 'translateY(0%)'})),
state('void, closed', style({transform: 'translateY(100%)', opacity: 0})),
transition('* => *', animate('100ms ease-in')),
])
],
styles: [`:host { position: absolute;bottom: 0;left: 0;right: 0;background: #009cff;height: 48px;padding: 16px;display: flex;justify-content: space-between;align-items: center;border-top: 1px solid black;font-size: 24px;}button {border-radius: 50%;} `]
})
export class PopupComponent {
@HostBinding('@state')
state: 'opened' | 'closed' = 'closed';
@Input()
get message(): string { return this._message; }
set message(message: string) {
this._message = message;
this.state = 'opened';
}
private _message: string;
@Output()
closed = new EventEmitter();
}
PopupService
import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector } from '@angular/core';
import { NgElement, WithProperties } from '@angular/elements';
import { PopupComponent } from './popup.component';
@Injectable()
export class PopupService {
constructor(private injector: Injector,
private applicationRef: ApplicationRef,
private componentFactoryResolver: ComponentFactoryResolver) {}
// Previous dynamic-loading method required you to set up infrastructure
// before adding the popup to the DOM.
showAsComponent(message: string) {
// Create element
const popup = document.createElement('popup-component');
// Create the component and wire it up with the element
const factory = this.componentFactoryResolver.resolveComponentFactory(PopupComponent);
const popupComponentRef = factory.create(this.injector, [], popup);
// Attach to the view so that the change detector knows to run
this.applicationRef.attachView(popupComponentRef.hostView);
// Listen to the close event
popupComponentRef.instance.closed.subscribe(() => {
document.body.removeChild(popup);
this.applicationRef.detachView(popupComponentRef.hostView);
});
// Set the message
popupComponentRef.instance.message = message;
// Add to the DOM
document.body.appendChild(popup);
}
// This uses the new custom-element method to add the popup to the DOM.
showAsElement(message: string) {
// Create element
const popupEl = document.createElement('popup-element') as any;
// Listen to the close event
popupEl.addEventListener('closed', () => document.body.removeChild(popupEl));
// Set the message
popupEl.message = message;
// Add to the DOM
document.body.appendChild(popupEl);
}
}
Templates
In Angular, a template is a chunk of HTML. Within a template, you can use special syntax to leverage many of Angular's features.
Pipes
{{ amount | currency:'EUR' }}
会把 amount
转换成欧元
{{ amount | currency:'EUR':'Euros '}}
会把第二个参数(字符串 'Euros '
)添加到输出字符串中
{{ birthday | date)| uppercase}}
会格式化之后的日期转换为大写字符
PipeTransform
import { Pipe, PipeTransform } from '@angular/core';
/*
* Raise the value exponentially
* Takes an exponent argument that defaults to 1.
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10 }}
* formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent?: number): number {
return Math.pow(value, isNaN(exponent) ? 1 : exponent);
}
}
Class binding
Binding Type | Syntax | Input Type | Example Input Values |
---|---|---|---|
Single class binding | [class.sale]="onSale" | boolean |undefined |nullg | true, false |
[class]="classExpression" | [class.sale]="onSale" | string | "my-class-1 my-class-2 my-class-3" |
{[key: string]: boolean | undefined | null} | {foo: true, bar: false} | ||
Array<string> | ['foo', 'bar'] |
Directives
Built-in directives
Getting the index
of *ngFor
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>
<div *ngFor="let item of items; trackBy: trackByItems"> ({{item.id}}) {{item.name}}</div>
Attribute directives
Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
structural directives
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
//Inject TemplateRef and ViewContainerRef in the directive constructor as private variables.
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
//Add an appUnless @Input() property with a setter.
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
<p *appUnless="condition" class="unless a">
(A) This paragraph is displayed because the condition is false.
</p>
<p *appUnless="!condition" class="unless b">
(B) Although the condition is true,
this paragraph is displayed because appUnless is set to false.
</p>
Dependency injection
Angular Dependency injection
import { Injectable } from '@angular/core';
@Injectable({
// declares that this service should be created
// by the root application injector.
providedIn: 'root',
})
export class HeroService {
constructor() { }
getHeroes() { return HEROES; }
}
The @Injectable()
decorator specifies that Angular can use this class in the DI system.
The metadata, providedIn: 'root'
, means that the HeroService
is visible throughout the application.
Injecting services
constructor(heroService: HeroService)
Using services in other services
import the Logger
service. Next, inject the Logger
service in the HeroService
constructor()
by specifying private logger: Logger
within the parentheses.
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor(private logger: Logger) { }
getHeroes() {
this.logger.log('Getting heroes ...');
return HEROES;
}
}
Dependency providers
Defining providers
The following example is the class provider syntax for providing a Logger
class in the providers
array.
providers: [Logger]
Angular expands the providers
value into a full provider object as follows.
[{ provide: Logger, useClass: Logger }]
Specifying an alternative class provider
[{ provide: Logger, useClass: EvenBetterLogger }]
@Injectable()
export class EvenBetterLogger extends Logger {
constructor(private userService: UserService) { super(); }
log(message: string) {
const name = this.userService.user.name;
super.log(`Message to ${name}: ${message}`);
}
}
Aliasing class providers
[ NewLogger,
// Alias OldLogger reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}]
Aliasing a class interface
providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],
Injecting an object
[{ provide: Logger, useValue: SilentLogger }]
// An object in the shape of the logger service
function silentLoggerFn() {}
export const SilentLogger = {
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
log: silentLoggerFn
};
Injecting a configuration object
export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
};
providers: [ UserService, { provide: APP_CONFIG, useValue: HERO_DI_CONFIG }],
Using an InjectionToken
object
import { InjectionToken } from '@angular/core';
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
constructor(@Inject(APP_CONFIG) config: AppConfig) { this.title = config.title;}
Using factory providers
constructor(
private logger: Logger,
private isAuthorized: boolean) { }
getHeroes() {
const auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
this.logger.log(`Getting heroes for ${auth} user.`);
return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}
const heroServiceFactory = (logger: Logger, userService: UserService) => {
return new HeroService(logger, userService.user.isAuthorized);
};
// You inject both Logger and UserService into the factory provider
export let heroServiceProvider =
{ provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService]
};
参考连接-https://angular.cn/ 参考连接-https://angular.io/
补充
大于号>
小于号 <