Angular Getting Started
specific for react / vue developers
angular v11 / 2021-03
Will not teach you
typescript
rxjs
react
vue
what is dependency injection
how decorator work
...
Just like a summary
please still see the official documentation for more details
Angular in my opinion
reactive programing(rxjs) + component + dependency injection
// html
<input id="text" value="hello world" />
<span id="foo"></span>
<span id="bar"></span>
// ts
import { fromEvent } from 'rxjs';
import { map, distinctUntilChanged, startWith } from 'rxjs/operators';
const input = document.getElementById('text');
const foo = document.getElementById('foo');
const bar = document.getElementById('foo');
fromEvent(input, 'input')
.pipe(
map(event => event.target.value),
distinctUntilChanged(),
startWith(input.value)
)
.subscribe(text => {
foo.innerText = text;
bar.innerText = text;
});
Props in my opinion
easily manage complicated data flow
easily build form and separate logics and layouts
power of rxjs
Cons in my opinion
learning curve extremely high
extremely depends on rxjs and there are some pitfalls if u are not familiar w/ rxjs
difficultly to manipulate complicated UI
So
Be careful to choose the appropriate scene to use angular
Basic UI
Template / Directive / Component
Text Interpolation
To react developers: similar to jsx but it is html
To react developer:
1. {{ not {
2. do not write logic on template
Directive / Component
Selector
same as document.querySelector()
@Directive({
selector: 'button[foo],a[foo]'
})
export class FooDirective {}
// template
// w/ foo directive instance
<button foo>
// no foo directive instance
<div foo>
@Directive({
selector: 'foo'
})
export class FooComponent {}
// template
<foo>bar</foo>
Template & Styles
Styles
Support non-css files: .sass/.scss/.less/.styl
ChangeDetectionStrategy
To react developers:
OnPush -> PureComponent
Default -> Component
ViewEncapsulation
Emulated similar to
1. css modules
2. scoped in vue
Property Binding
How to pass data from parent to child
To vue developers: v-bind
To react/vue developers: props
If the type is string
<foo name="bar"></foo>
===
<foo [name]="'bar'"></foo>
Rename
<child [master]=""></child>
Getter / Setter
Event Binding
How to emit data from child to parent
To react developers: callback props
To vue developers: v-on/emit
Output
Two Way Binding
<ng-content>
Content Projection
To react developers: just children
In Angular
const Foo = ({ children, footer }) => (
<div>
{children}
<div>
{footer}
</div>
</div>
);
//
<Foo
footer={
<div>bar</div>
}
>
foo!!!
</Foo>
In React
<ng-content></ng-content>
<div>
<ng-content select="[slot='footer']"></ng-content>
</div>
//
<foo>
foo!!!
<div slot="footer"></div>
</foo>
same as document.querySelector
View vs Content
// foo
@Component({
selector: 'foo',
template: `
<div>foo title</div>
<ng-content></ng-content>
<div>foo footer</div>
`
})
export class FooComponent {}
// bar template
<foo>
<div>bar</div>
</foo>
View of Foo
Content of Foo
Host Element
angular always wrap your template into a html element called host
To react developer: yes, no fragment :)
// foo
@Component({
selector: 'foo',
template: `
<div>foo</div>
<ng-content></ng-content>
`
})
export class FooComponent {}
// bar template
<foo> ---> will create a real html element
<div>bar</div>
</foo>
// bar html result
<foo>
<div>foo</div>
<div>bar</div>
</foo>
Get HTMLElement of Host
Dynamic Component
see doc yourself
const componentMap = {
foo: Foo,
bar: Bar,
baz: Baz
};
const Parent = ({ type, ...rest }) => {
const Component = componentMap[type];
return (
<Component {...rest} />
);
};
In React
Attribute/Class/Style
Attribute
Class
[ngClass]
Style
[ngStyle]
Style Precedence
@HostBinding
bind attribute/class/style to host
@HostListener
bind listener to host
Lifecycle
ngOnInit
Initialize the directive or component after
1. first displays the data-bound properties
2. sets the directive or component's input properties
Called once after first ngOnChanges
ngOnDestroy
Clean up just before Angular destroys the directive or component
ngOnChanges
Respond when Angular sets or resets data-bound input properties.
The method receives a SimpleChanges object of current and previous property values.
Called before ngOnInit and whenever one or more data-bound input properties change.
ngDoCheck
custom change detection hook
Do not use this if possible
ngAfterContent(Init|Checked)
Init:
Respond after Angular projects external content into the component's view, or into the view that a directive is in.
Checked:
Respond after Angular checks the content projected into the directive or component.
ngAfterView(Init|Checked)
Init:
Respond after Angular initializes the component's views and child views, or the view that contains the directive.
Checked:
Respond after Angular checks the component's views and child views, or the view that contains the directive.
constructor
OnChanges
OnInit
DoCheck
AfterContentInit
AfterContentChecked
AfterViewChecked
AfterViewInit
OnDestroy
mount
changes detection
No default value properties will be undefined before first ngOnChanges called
Directive
Component is a directive w/ template and styles
Attribute Directive
Built-ins: ngClass/ngStyle/...
Component Directive
@Directive({
selector: 'button[myButton]'
})
export class MyButtonDirective {
@HostBinding('class.my-button')
readonly bindHostClass = true;
}
//
<button myButton>click me</button>
Structural Directive
*ngIf
return (
<div>
{items && items.map(...)}
{items ? items.map(...) : null}
</div>
)
In React
<ng-container>
*ngSwitch
*ngFor
items.map(item => (
<div>
{item.name}
</div>
))
In React
items.map(item => (
<ItemDetail item={item} />
))
In React
items.map((item, i) => (
<div>
{i + 1} - {item.name}
</div>
))
In React
items.map(item => (
<div key={item.id}>
{item.id} - {item.name}
</div>
))
In React
Structure Shorthand
*xxx
Template Variables
To react developers: similar to ref
exportAs
Get instance of specific directive
@Directive({
selector: 'myButton',
})
class MyButtonDirective {
}
@Component({
selector: 'app-root',
template: `
// #button will get html element of button
<button myButton #button>click me</button>
`
})
class AppComponent {
}
@Directive({
selector: 'myButton',
exportAs: 'myButton',
})
class MyButtonDirective {
}
@Component({
selector: 'app-root',
template: `
// #button will get the instance of MyButtonDirective
<button myButton #button="myButton">click me</button>
`
})
class AppComponent {
}
Scope
NgTemplate
<ng-template>
Template block for *ngIf
TemplateOutlet
In Angular
const Foo = () => {
const bar = (
<div>bar</div>
);
return (
<div>
foo
{bar}
</div>
);
};
In React
<ng-template #bar>
<div>bar</div>
</ng-template>
foo
<ng-template [ngTemplateOutlet]="bar"></ng-template>
In Angular
const Foo = () => {
const renderBar = (bar, { baz: qux }) => (
<div>
{bar}
<div>
{qux}
</div>
</div>
);
return (
<div>
foo
{renderBar('hello', {
baz: 'world'
})}
</div>
);
};
In React
<ng-template #bar let-bar let-qux="baz">
<div>
{{ bar }}
<div>
{{ qux }}
</div>
</div>
</ng-template>
foo
<ng-template
[ngTemplateOutlet]="bar"
[ngTemplateOutletContext]="{
$implicit: 'hellow',
baz: 'world'
}"
></ng-template>
TemplateRef From Input
@Component({ ... })
export class MyButton {
@Input()
loadingIcon?: TemplateRef<any>;
@Input()
loading: boolean = false;
}
// template
<ng-template #defaultLoadingIcon>
...
</ng-template>
<ng-template [ngTemplateOutlet]="loadingIcon || defaultLoadingIcon"></ng-template>
<ng-content></ng-content>
Interaction
w/ content and view
Interact child via variable
@(Content|View)Child/@(Content|View)Children
Query the content/view
Get HTMLElement of component in view/content
To react developers: no need to add forwardRef everywhere
@ViewChild(FooComponent, { read: ElementRef })
fooElement: ElementRef;
Get directive in view/content
@Directive({
selector: '[foo]'
})
export class FooDirective {}
// bar template
<div foo #foo></div>
// bar
@Component({ ... })
export class BarComponent {
@ViewChild('foo')
fooElement: ElementRef;
@ViewChild('foo', { read: FooDirective })
foo: FooDirective;
}
const MyList = ({ children, items }) => (
<ul>
{items.map(item => (
<div key={item.id}>
{children(item)}
</div>
))}
</ul>
);
In React
Template From Content
To react developers: render props
In Angular
export interface MyListItem {
// ...
}
@Directive({
selector: '[myList]',
})
export class MyListDirective {
@Input()
items: MyListItem[];
@Content(TemplateRef)
itemTemplate: TemplateRef<{ $implicit: MyListItem }>;
trackByItem(_, item: MyListItem) {
return item.id;
}
}
// MyList template
<ng-container *ngFor="let item of items; trackBy: trackByItem">
<div>
<ng-template *ngTemplateOutlet="itemTemplate; context: { $implicit: item }"></ng-template>
</div>
<ng-container>
// Other template
<ul myList [items]="...">
<ng-template let-item>
...
</ng-template>
</ul>
Pipe
Transform data
Unwrapping Observable
Coercion
type will lie you
In Angular
<Icon loading />
--->
<Icon loading={true} />
In React
<my-icon loading></my-icon>
--->
<my-icon [loading]="''"></my-icon>
since html
class Icon {
// for template type checking
static ngAcceptInputType_loading: boolean | '';
private _loading: boolean;
get loading(): boolean {
return this._loading;
}
// for correct type
set loading(value: boolean) {
this._loading = (value === '') || value;
}
}
class Icon {
// really boolean ?
loading: boolean = false;
}
CDK
official component development kit
React developers:
Or just always write
<my-icon [loading]="true"></my-icon>
upon to you
Dependency Injection
Injection Token/Provider
To react developer: just like context
import { createContext } from 'react';
export const TitleContext = createContext<string>('title');
<TitleContext.Provider value="Hero of the Month">
{children}
</TItleContext.Provider>
const title = useContext(TitleContext);
description not default value
anything
The class placed on useClass should be injectable
no need to use @Inject
Token === Class
Alias
Service
To react developers: for modularity and usability, just like hooks
Service Lifecycle
only support constructor/ngOnDestroy
Injector
To react developer: a thing to provide multiple contexts
resolves and creates dependencies
maintains a container of dependency instances
NgModule
Injectable Scope
root
no other configuration, just inject
Injectable Scope
to specific module
Injectable Scope
to directive/component
ModuleInjector
Special ModuleInjectors
ElementInjector
providers on directive/component
Hierarchy
@Injectable({
providedIn: 'root'
})
class Foo {}
ReactDOM.render(
<PlatformModule>
<RootInjector>
<App />
</RootInjector>
</PlatformModule>
);
// foo module
@NgModule({
// ...
providers: [
// ...
],
declarations: [
FooComponent
],
// ...
})
class FooModule {}
// routes
{
loadChildren: () => import('./foo.module').then(m => m.FooModule)
}
const withModule = (providers) => (Component) => (props) => {
// ...
return (
<ModuleInjector>
<Component {...props} />
</ModuleInjector>
);
}
// foo module
export default withModule([])(Foo);
// routes
const Foo = lazy(() => import('./FooModule'));
@Component({
// ...
providers: [
// ...
]
// ...
})
class FooComponent {}
const withProviders = (providers) => (Component) => (props) => {
// ...
return (
<ElementInjector>
<Component {...props} />
</ElementInjector>
);
}
// foo
export default withProviders([])(Foo);
To react developers: like hierarchy of context
Resolution Rules
how to inject things for directive/component
where to inject things from
Examples from Official
assume
Provider
View Provider
only for view of component
child
inspector
root
from content
in view
Modifiers
modify resolution rules
@Optional
In Angular
export const FooContext = createContext('foo');
// some component
// assume there is no any ancestor provide the context
const foo = useContext(FooContext);
In React
export const FooToken = new InjectionToken<string>('foo');
// some component
// assume no one provide the token
// ...
constructor(
@Inject(FooToken)
foo: string
) {}
// ...
Throw error
default value
// ...
constructor(
@Inject(FooToken)
@Optional()
foo?: string
) {}
// ...
@Self
@SkipSelf
@Host
resolve until host of this directive/component
@Component({
providers: [
FooService
],
template: `
<bar></bar>
`
})
class Foo {
}
@Component({
})
class Bar {
constructor(
@Host()
fooService: FooService
){}
}
@Component({
providers: [
FooService
],
})
class Foo {
}
@Directive({
})
class Bar {
constructor(
@Host()
fooService: FooService
){}
}
// some template
<foo bar></foo>
forwardRef
Allow to refer to references which are not yet defined
@Injectable()
export class FooService {
constructor(
@Inject(forwardRef(() => BarService)) barService: BarService
){}
}
@Injectable()
export class BarService {
constructor(
@Inject(forwardRef(() => FooService)) fooService: FooService
){}
}
To react developer: not the forwardRef u often use
Scenarios
parent provide data to children
export interface FooGroupContextValue {
// ...
}
export const FooGroupContextToken = new InjectionToken<FooGroupContextValue>('foo context');
// foo group component
@Component({
// ...
providers: [
{
provide: FooGroupContextToken,
useExisting: forwardRef(() => FooGroupComponent),
},
],
// ...
})
export class FooGroupComponent implements FooGroupContextValue {
// ...
}
// foo component
@Component({
// ...
})
export class FooComponent {
// ...
constructor(
@SkipSelf()
@Optional()
@Inject(FooGroupContextToken)
fooGroup?: FooGroupContextValue,
) {}
}
Scenarios
reusable logics need following lifecycle of component
import { Injectable, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable()
export class DestroyService extends Subject<void> implements OnDestroy {
ngOnDestroy() {
this.next();
this.complete();
}
}
import { takeUntil } from 'rxjs/operators';
@Component({
// ...
providers: [
DestroyService
],
// ...
})
class ArticlesComponent implement OnInit {
// ...
constructor(
private articleService: ArticleService,
private destroy$: DestroyService
) {}
ngOnInit() {
this.articles = this.articleService.getAll()
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
// ...
});
}
// ...
}
Router
To react developers: outlet / layout, not component
ActivatedRoute
To react developers: useLocation / useRouteMatch / useParams
Router
To react developers: similar to history
Router Events
Route Configuration
To react developers: no '/' prefix for configuration
Parameters
To react developers: same
const routes: Routes = [
{ path: 'article/:articleId', component: ArticleComponent }
];
Wildcard
To react developers: ** not *
Redirect
To react developers: seem to have in react-router v6 but
Nesting
To react developers: do not need to add prefix to path of each children
Lazy Loading
In react router
const Customers = lazy(() => import('./Customers'));
RouteLink
<a [routerLink]="['/team', teamId, 'user', userId]">link</a>
// /team/11/user/33
Relative
same as unix
State
Active Class
In react router
class will be added to element if the path active
Route Middleware
Guard
CanActivate
Resolve
pre-fetching data
RouterOutlet
Auxiliary Routes
/article/10/edit(left:some/left/path//right:/some/right/path)
separate outlets by //
Forms
To react developers: redux form / final form / react hook form ?
no, just one in angular provided by official
NgModel
<label>
<input type="radio" name="colors" value="yellow" [(ngModel)]="color">
Yellow
</label>
<label>
<input type="radio" name="colors" value="green" [(ngModel)]="color">
Green
</label>
<label>
<input type="radio" name="colors" value="red" [(ngModel)]="color">
Red
</label>
Do not need to set attribute checked
Can use on nested
Change Listener
Template Driven Form
see doc yourself, just use [(ngModel)]
Reactive Form
FormControl
control the value and validation status of a form field
control.setValue('foo');
setValue vs patchValue
replace all
partial update
FormGroup
control group of FormControls
Nested FormGroup
FormBuilder
each field is a tuple, [value,[validators | options]?]
FormArray
control array of FormControls
Validation
for reactive form
Built Ins
Custom Validator
Cross Field Validator
Async Validator
similar to custom validator but
wrap the return result w/ Promise / Observable
Configure updateOn
usually for optimizing performance for async validators
ControlValueAccesor
An interface as a bridge between
angular forms api and native element in the DOM.
For custom ngModel or FormControl
Please see doc to know how to implement
After implement ControlValueAccessor
<address-form formControlName="address"></address-form>
Custom FormControl
Roadmap
strict typing for forms
Animation
FSM
Web Animation Api
animation of angular is similar to
similar to css transition
Wildcard
glob / regex ?
Void State
Disabled
Callback
Keyframes
offset
Parameters
Not documented in
official
But in comments
state('foo', style({
maxHeight: '{{ maxHeight }}px',
overflow: 'hidden'
}), { params: { maxHeight: 1 } }),
<div [@bar]="{
value: isBar,
params: { maxHeight: foo * 10 }
}">
bar
</div>
Auto calculation
transition on height between 0 and 'auto' not work
export const collapseMotion: AnimationTriggerMetadata = trigger('collapseMotion', [
state('expanded', style({ height: '*' })),
state('collapsed', style({ height: 0, overflow: 'hidden' })),
// ...
]);
Transition
Transition
import { Transition } from 'react-transition-group';
const duration = 300;
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
}
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
exited: { opacity: 0 },
};
const Fade = ({ in: inProp }) => (
<Transition in={inProp} timeout={duration}>
{state => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
content
</div>
)}
</Transition>
);
<CSSTransition in={inProp} timeout={300} classNames="fade">
<div>
content
</div>
</CSSTransition>
// css
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 200ms;
}
react transition group
will add additional <div />
or use cloneElement / render props to control but still need to handle ref of children since Transition also need to pass ref to children
Transition
angular
export const fadeMotion: AnimationTriggerMetadata = trigger('fade', [
transition(':enter', [
style({ opacity: 0 }),
animate('200ms', style({ opacity: 1 }))
]),
transition(':leave', [
animate('200ms', style({ opacity: 0 }))
]),
]);
<div @fade *ngIf="open">
content
</div>
Api
Restful
HttpClient
Options
Observe Result
Headers
Params
query string
Handle error
Interceptor
Use Cases
authorization
Use Cases
logging
Tracking Progress
File Upload
this.http.post('/api/upload', file, { observe: 'events', reportProgress: true })
.pipe(
map(event => {
switch (event.type) {
case HttpEventType.Sent:
return `Uploading file "${file.name}" of size ${file.size}.`;
case HttpEventType.UploadProgress:
const percentDone = Math.round(100 * event.loaded / event.total);
return `File "${file.name}" is ${percentDone}% uploaded.`;
case HttpEventType.Response:
return `File "${file.name}" was completely uploaded!`;
default:
return `File "${file.name}" surprising upload event: ${event.type}.`;
}
})
)
.subscribe(() => {
// ...
});
Optimizing
GraphQL
apollo
Query
by client
Query
inject query
Query
variables
QueryRef
Query
refetch
Query
fetchMore
Query
update variables imperative
Query
variables from observable
@Component({ ... })
class PostsComponent {
variables$ = new BehaviorSubject({ ... });
posts$: Observable<Post[]>;
ngOnInit() {
this.posts$ = this.variables$
.pipe(
startWith(this.variables$.value),
switchMap(variables =>
this.apollo
.watchQuery({
query: GET_POSTS,
variables
})
.valueChanges
),
map(({ data }) => data.posts)
);
}
}
Mutation
by client
Mutation
inject mutation
Subscription
by client
this.apollo.subscribe({
query: // ...,
variables: {
// ...
}
})
Subscription
inject subscription
Subscription
QueryRef.subscribeToMore
Code Generation
dto (data transfer object) - object transferred from network
Rx in Angular
Turn everything to stream
If possible
this.currentHeroId$ = this.route.paramMap
.pipe(
map(params => Number(params.get('id')))
);
this.selectedHero$ = currentHeroId$
.pipe(
switchMap(id =>
this.heroService.getHeroById(id)
.pipe(shareReplay())
)
);
this.currentHeroId$ = this.route.queryParams
.pipe(
map(params => Number(params.get('id')))
);
If currentHeroId changed from param to query, just
But sometimes need promise
for complicated flow control
Cold Observable
each subscriber create a new stream after subscribing
one stream to one subscriber
Hot Observable
singleton stream shared by every subscribers
one stream to many subscribers
Warm Observable
cold -> hot
similar to hot observable but stream start after first subscription
Pitfalls
Forget to unsubscribe
To avoid from memory leak
Do not need to unsubscribe cold observables or observables only used on async pipe
Or use takeUntil() operator
To react developers: similar to forget to cleanup in effect
HttpClient is cold
nothing happens
remember to subscribe
HttpClient is cold
share: make a cold observable warm
Avoid using multiple async pipe on same observable in one template
<div *ngIf="(articles$ | async) as articles">
// ...
</div>
<div *ngIf="(articles$ | async) as articles">
// ...
</div>
<ng-container *ngIf="(articles$ | async) as articles>
<div>
// ...
</div>
<div>
// ...
</div>
</ng-container>
Keep observable completion
nothing happens
stay at the original screen and the UI does not get updated
this.lessonsService
.findLessonByUrl(route.params['id'])
.pipe(
first()
// take(1)
);
Architecture
w/ simple example
Single Responsibility Principle
Only do one thing
Aspect Oriented Programming
api
state
component
author
comment
article
view layer
core layer
state management
facade
data layer
abstraction layer
service layer
presentation
container
Core Layer
Domain / Application Logic
Data Layer
communicate w/ http, just api
@Injectable()
export class ArticleApi {
readonly url = '/api/cashflowCategories';
constructor(private readonly http: HttpClient) {}
getArticles(options = {}): Observable<Article[]> {
const { authorId } = options;
return this.http.get<Article[]>(this.url, { authorId });
}
getArticleById(id: string): Observable<Article> {
return this.http.get<Article>(this.url, { id });
}
}
State Management
maintains a lot of BehaviorSubjects/Observables and methods
import * as jwtDecode from 'jwt-decode';
@Injectable({
providedIn: 'root'
})
export class AuthState {
private readonly accessToken$$ = new BehaviorSubject<string | null>(localStorage.getItem('accessToken'));
accessToken$ = this.accessToken$$.asObservable();
isAuthenticated$ = this.accessToken$
.pipe(
map(token => !!token)
);
get accessToken() {
return this.accessToken$$.value;
}
get payload() {
return jwtDecode(this.accessToken);
}
setTokens(accessToken, refreshToken) {
this.accessToken$.next(accessToken);
// ...
}
removeTokens() {
this.accessToken$.next(null)
// ...
}
}
Do not expose BehaviorSubjects to avoid from mutating data by outside methods
conditional, sometimes just in service
Service Layer
domain business logics, like auth/form
@Injectable()
export class EditArticleForm {
readonly form = this.fb.group({
// ...
});
constructor(private readonly fb: FormBuilder) {}
validate() {
// ...
}
validateSomeField() {
// ...
}
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(
private readonly authApi: AuthApi,
private readonly authState: AuthState
) {}
login(email, password) {
this.authApi.login(email, password)
.subscribe(({ accessToken, refreshToken }) => {
this.authState.setTokens(accessToken, refreshToken);
});
}
logout() {
this.removeTokens();
// ...
}
updateAccessToken() {
const { refreshToken } = this.authState;
if (refreshToken) {
this.authApi.updateAccessToken(refreshToken)
.subscribe(
({ accessToken, refreshToken: newRefreshToken }) => this.authState.setTokens(accessToken, newRefreshToken),
() => this.logout()
);
}
}
}
@Injectable({
providedIn: 'root'
})
export class ArticleService {
// ...
getArticles$() {
return this.articleApi.getArticles()
.pipe(shareReplay());
}
getArticlesOfAuthor$(authorId: string) {
return this.articleApi.getArticle({ authorId })
.pipe(shareReplay());
}
// ...
}
Facade
Facade pattern
Compose specific logics/flow control
Control router
Cache
...
@Injectable()
export class AuthorPageFacade {
author$ = this.authorService.getAuthorProfile$();
articles$ = this.author$
.pipe(
switchMap(author =>
author
? this.articlesService.getAritclesOfAuthor$(author.id)
: of([])
)
);
constructor(
private readonly authorService: AuthorService,
private readonly articleService: ArticleService
) {}
navigateToAriclePage(artcile: Article) {
this.router.navigate(['/article', article.id]);
}
}
Example in wiki
View Layer
Presentation
export class AritcleListComponent {
@Input()
articles: Article[];
@Output()
articleSelect = new EventEmitter<Article>();
onArticleSelect(article: Article) {
this.articleSelect.next(article);
}
}
//
<article-list-item
*ngFor="let article of articles"
[article]="article"
(select)="onArticleSelect($event)"
></article-list-item>
Do not know anything except @Input and @output
Container
export class AuthorPageComponent {
author$ = this.authorPageFacade.author$;
articles$ = this.authorPageFacade.articles$;
constructor(
private readonly authorPageFacade: AuthorPageFacade
) {}
onArticleSelect(article: Article) {
this.authorPageFacade.navigateToArticlePage(article);
}
}
//
<author-profile [author]="author$ | async"></author-profile>
<article-list
[articles]="articles$ | async"
(articleSelect)="onArticleSelect($event)"
></article-list>
Communicate w/ facade and presentational views
author page facade
author service
article service
author profile component
author page component (container)
article-list
author$
articles$
getArticles(author.id)
getAuthor
@Input()
author$ | async
@Input()
articles$ | async
Whatever facade or service changes, it won't affect view layer if the facade provide same things to container.
References
Cheatsheet
Copy of Angular Getting Started
By Jay Chou
Copy of Angular Getting Started
- 318