Jun 10th, 2017 - written by Kimserey with .
Last week I explained what was the NgModule in angular, we saw what were the parameters we could add and what those parameters could be composed of. Today I would like to talk about another main piece of Angular, the Component. We will see what argument it takes and how we can use it.
Component in Angular derives from directives. Some of the most used properties are:
providers
which defines a injectable values or services scoped only by the component and its children (compared to injectable provided to the module which is application wide)selector
which defines a CSS selector to use the componentHere are some of the parameter used to defined a component:
styles
for inline style in the componenttemplate
to define inline templateFor example here is a simple component:
1
2
3
4
5
6
7
@Component({
selector: 'app-square',
template: '<div></div>',
styles: [ '.square { width:100px; }'
]
})
export class SquareComponent {}
We can then use it in a parent component:
1
<app-square></app-square>
Instead of template
, templateUrl
can be used to use a template file. Also to define style, we can also specify files using styleUrls
.
In order to communicate, a component can make use of the input and ouput decorators.
The @Input()
decorator is used to specify the argument which the component expect the parent to pass in.
It will be passed by binding from the parent to the component.
1
<app-square [side]="4"></app-square>
Notice that we used []
which is the one-way from data source to view target, the source being the right side of =
and target being the left side.
In the component then we can use this decorator like so:
1
2
3
export class SquareComponent {
@Input() side: number;
}
The @Output()
decorator is used to bind an EventEmitter
which dispatch events from within the component to notify the parent of changes. Doing that allows the component to not have any reference to the parent.
1
<app-square (modified)="onModified($event)"></app-square>
Notice that we used ()
which is the one-way from view target to data source.
We can then use the decorator like so:
1
2
3
4
5
6
7
export class SquareComponent {
@Output() modified = new EventEmitter<number>();
modify(n) {
this.modified.emit(n);
}
}
Then in the parent:
1
2
3
4
5
export class SquareParentComponent {
onModified(n) {
// do something
}
}
Using input and output we can then handle inputs passed down to the component and outputs coming out of the component from the EventEmitter
.
Input
and Output
are set during the init life cycle of a component. Therefore If you wish to perform any action using the inputs or outputs values, you will need to implement OnInit
and perform your action within the ngOnInit(){}
life cycle hook.
It is also possible to use a template variable to get a reference to the component from the parent template and call the component actions.
1
2
<app-square #square></app-square>
With #square
we can get access to component from the parent template and use it.
This is limited for usage within the template.
If we need to have access to the child within the component, we can use ViewChild
which allows us to access a component. It will only return the first element matching the component selector.
1
@ViewChild(SquareComponent) square: SquareComponent;
In order to use it, we need to implement the AfterViewInit
interface single function ngAfterViewInit()
which the callback which occurs directly after the ViewChild
is set.
But if we need to modify members displayed in the template, we will need to set a timer for it otherwise we will face some Value changed after check
error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component({
selector: 'app-root',
template: `
<app-square [show]="true" [side]="4" #square></app-square>
<app-square [show]="true" [side]="5"></app-square>
<strong></strong>
`
})
export class AppComponent implements AfterViewInit {
@ViewChild(SquareComponent) square: SquareComponent;
surface = 0;
ngAfterViewInit() {
setTimeout(() => this.surface = this.square.surface, 0);
}
}
In order to communicate changes from parent to child, it is also possible to implement the OnChange
life cycle hook.
Do not confuse the on-change or (change) DOM event binding with the OnChange
life cycle hook.
When one of the input changes, the ngOnChanges(changes: SimpleChanges){}
callback will be called.
SimpleChanges
is a dictionary defined by angular:
1
2
3
export interface SimpleChanges {
[propName: string]: SimpleChange;
}
Where a SimpleChange
contains information about the change itself:
1
2
3
4
5
6
7
8
9
export declare class SimpleChange {
previousValue: any;
currentValue: any;
firstChange: boolean;
/**
* Check whether the new value is the first value assigned.
*/
isFirstChange(): boolean;
}
Using that we can detect changes in our SquareComponent example to recompute the surface:
1
2
3
4
5
6
7
8
9
10
11
12
13
export class SquareComponent implements OnChanges {
@Input() side: number;
ngOnChanges(changes: SimpleChanges) {
// the value already changed
this.computeSurface();
console.log(`side previous value: ${changes['side'].previousValue}, side current value: ${changes['side'].currentValue}`);
}
private computeSurface() {
this.surface = this.service.computeSurface(this.side);
}
This method is useful when many input can be changed and we wish to handle all changes in one single place and we need to know the details of the change, previous value and current value.
Setter and getter
Otherwise a simpler approach in handling changes would be to define a setter on the input:
1
2
3
4
5
6
@Input() set side(s: number) {
this._side = s;
}
get side() {
return this._side;
}
We can then intercept the change in the setter to perform an action.
Today we saw how we could define angular components. We saw how we could communicate between parent and child. Components need to be kept as simple as possible and must be broken down into small pieces. Communication must be taken very seriously into consideration as it can easily degenerate into an unmaintainable solution where changes become extremely hard to track. I hope you enjoyed this post as much as I enjoyed writing it, if you have any question, leave it here or hit me on Twitter @Kimserey_Lam, see you next time!