So have you tried to be a React developer and noticed something on react components? yes, react components have their own states and it's really easy to monitor the changes happening insides the state having react allows the accessible of state simple and plain.
We might ask that hey, can Angular components have its own state as well? it would be easier if would have the capability to detect the changes of the properties such as input and output bindings, well the answer is angular components have its own state and we can also listen to the changes and it is done by the notorious life cycle hooks provided by Angular, let us look at the life cycle sequence first when an angular component is created up to where it is destroyed.
let us say for example we have an angular component which has an input binding of message with a type string, we can use the ngOnChanges hook to detect of there are any changes in the message bindings but there is one problem here, ngOnChanges listens only to input bindings and it can be triggered when the value comes from a template binding like
<app-sample-component [message]="message"><app-sample-component/>
and another issue we can encounter is when the component grows and we need to manage a big state, this would be not a clean code.
So whats an easier solution for us Angular developers to have an access to states, well Angular Effects are just in the corner, this will helps us easily access the states of angular components using decorators and listening to states as an observable. let us look deeper in Angular Effects.
Installation and Setup
let's start using Angular Effects, lets assume we have created our Angular project using angular-cli, if you still haven't generated, execute the command
ng new <angular-project-name>
and wait for the cli to generate all the boilerplate codes. After successful creation of your Angular app let us install Angular effects by executing the command.
npm install ng-effects --save
this command will add the ng-effects library to your project and thats it! we are ready to use the powers of Angular effects to listen to component states.
Using Effect in the Component
now let us create two components with the name of parent and name-changer having the name-changer being the child component, we will listen to the states of the name changer component as we manipulate its properties from the parent component and within the component itself.
Inside the name-changer component we will provide the Effects from the ng-effects component and will connect our component instance.
import { Component, Input } from '@angular/core';
import { connect, Effects, Effect } from 'ng-effects';
@Component({
selector: 'app-name-changer',
templateUrl: './name-changer.component.html',
styleUrls: ['./name-changer.component.scss'],
providers: [Effects]
})
export class NameChangerComponent {
@Input() username = '';
constructor() {
connect(this);
}
and we are now ready to listen to our states inside the component.
Input Bindings
in the example above, we can see that I have declared a username component as an input binding. we can listen to the change of the specific input binding (username) using the @Effect decorator, let us check the example below
import { Component, Input } from '@angular/core';
import { connect, Effects, Effect, State, changes } from 'ng-effects';
@Component({
selector: 'app-name-changer',
templateUrl: './name-changer.component.html',
styleUrls: ['./name-changer.component.scss'],
providers: [Effects]
})
export class NameChangerComponent {
@Input() username = '';
constructor() {
connect(this);
}
@Effect()
logUsernameChange(state: State<NameChangerComponent>) {
return changes(state.username).subscribe((data) => {
console.log(data)});
}
}
I have added logUsernameChange() function which has an effect decorator, we can also see the i have used a changes operator which allows us to listen only to the changes and ignore the initial value of the username input binding.
when we used the name-changer component in our parent component:
parent.component.html
<app-name-changer [username]="user" ></app-name-changer>
parent.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.scss']
})
export class ParentComponent implements OnInit {
user = 'user1';
constructor() { }
ngOnInit(): void {
this.user = 'user2';
}
}
we can see that we have bind the user variable of parent component to the username input of name-changer, in this scenario, this will log the user2 value as the parent component has changed the value of the user variable in the ngOnInit hook, now let us add a function named changeUsername() inside the name-changer component.
import { Component, Input } from '@angular/core';
import { connect, Effects, Effect, State, changes } from 'ng-effects';
@Component({
selector: 'app-name-changer',
templateUrl: './name-changer.component.html',
styleUrls: ['./name-changer.component.scss'],
providers: [Effects]
})
export class NameChangerComponent {
@Input() username = '';
constructor() {
connect(this);
}
@Effect()
loguUsernameChange(state: State<NameChangerComponent>) {
return changes(state.username).subscribe((data) => { console.log(data) });
}
changeUsername() {
this.username = 'user3';
}
}
in this case, we are not binding the username property from outside the component as we are directly modifying the value inside it, if the function is executed this will still trigger the logUsernameChange() as we have subscribed to the changes of the username property. this is a very good advantage compared to the ngOnChanges as we still need to use ChangeDetectorRef to listen to changes that are not template bound.
What if we have removed the input binding and treat the username as a simple variable, can we still listen to the changes? the answer Yes! the effects can also listen to the changes of non Input bindings.
Output Bindings
effects can also be connected in the output bindings of an Angular component, if a particular state has changed, we can use the Context from ng-effects to connect the output binding to a specific state having the EventEmitter will be triggered every time the state changes.
@Input() username = '';
@Output() usernameEmitter = new EventEmitter(true);
@Effect()
emitUserName(state: State<NameChangerComponent>, context: Context<NameChangerComponent>) {
return state.username.subscribe(context.usernameEmitter)
}
In the example above, the usernameEmitter is connected to the username input binding, this means that every change that will occur on the username will be emitted from the component with the usernameEmitter.
We can also use HostEmitter in exchange with EventEmitter
@Input() username = '';
@Output() usernameEmitter = new HostEmitter(true);
@Effect('usernameEmitter')
emitUserName(state: State<NameChangerComponent>) {
return state.username;
}
Bind to Host Listeners
effects are not just for listening to input and bindings, it also has the capability to detect if an event has been executed in our component. let us see the example below.
@HostListener('click', ['$event'])
clicked = new HostEmitter<MouseEvent>()
@Effect()
logClick(state: State<NameChangerComponent>) {
return state.clicked.subscribe(event => console.log(event));
}
we have defined a HostListener for a clicked event having that when the component is clicked, the effects listens to the event as we have subscribed to the clicked HostEmitter.
Template Listeners
when we bind template events with a HostEmitter, we can also listen if the template event is executed.
name-change.component.html
<button (click)="clickTemplate($event)">Click!</button>
name-changer.component.ts
clickTemplate = new HostEmitter<MouseEvent>();
@Effect()
logClickTemplate(state: State<NameChangerComponent>) {
return state.clickTemplate.subscribe(event => console.log(event));
}
Listening to the multiple states
in a single effect, we can listen to multiple states or even the entire state, we can use the latest operator of ng-effects so we can pass the state object.
@Input() username = '';
@Input() fullname = '';
@Effect()
logState(state: State<NameChangerComponent>) {
return latest(state).subscribe(({ username, fullname}) => console.log(username, fullname))
}
and that's it! these are the basics on how to use Angular Effects to listen on state changes of components, you can check out my sample code in my repository.
Commentaires