I can't make a component delete itself with angular.
Let's say that the rendered HTML represents the current state of an application. There is a state where the component exists, and then there is a state where it doesn't exist. Don't think of it as an act of deleting, but that the state changed and afterwards the component is no longer rendered.
- Type your name
The state doesn't have any greetings.
- Child component is created that is greeting you.
The state has at least one getting.
- Child component contains button to delete itself
The state returns to not having any greetings.
Currently i fulfilled the first two steps and everything works fine. But i have no idea how i can make the child component delete itself. Coming from React i know, that there was the possibility to delete a "component" with the lifecycle methods somehow.
If you deleted the component forcefully, then the components on the page would no longer represent the current state of the application. This is fine where you have microstates and an example would be something like a modal dialog. Where the dialog itself has its own state and the displaying of the dialog has a life-cycle relative to that microstate.
I don't think this is the case given your example.
currentUsers: any[] = [];
The above is your state variable. The value of that array represents what will be rendered in the HTML. You can ask some questions and the answers help guide you.
- who owns the state?
- who can mutate that state?
- how do I ask the state owner to change it?
Let's answer these questions
- who owns the state?
The GreeterServiceComponent
component is the owner of the state, and in this case the state is represented as an array. In web components we refer to this as the component's state. It is an internal thing to the component.
- who can mutate that state?
We only want GreeterServiceComponent
to make changes to this state. Source code outside of the class should not access it directly and mutate it. We can say that the component handles the storage and life-cycle of this internal state.
- how do I ask the state owner to change it?
This is where we get into Angular specifics. In Angular we have multiple ways to communicate between components. In this case, we want the child component to communicate to the parent component that it should change it's internal state. We want to tell the parent not to show the greeting anymore. Each approach to this problem has it's benefits and drawbacks, and it's up to you to decide which approach works for you.
@Output() bindings
A component in Angular can have an @Output()
that executes an expression in the parent component's template. This is the same as passing a callback function to the properties of a React component.
In the WasGreetedComponent
you would add:
@Output()
public closed: Subject<void>() = new Subject();
deleteMe() { this.closed.next(); }
In the GreeterServiceComponent
template you would change:
<div class="column" *ngFor="let user of currentUsers">
<app-was-greeted [user]="user"
(closed)="currentUsers = currentUsers.filter(u=>u!==user)">
</app-was-greeted>
</div>
Parent injection
A child component in Angular can inject its own parent component via the constructor, and you can then notify the parent directly.
This approach by-passes the template, and any changes made in the parent might require the view to be updated. Therefore, it's recommended that the parent use ChangeDetectorRef
to mark it's view as dity.
In the GreeterServiceComponent
you would add:
public deleteUser(user: any) {
this.currentUsers = this.currentUsers.filter(u=>u !== user);
this.changeDetectorRef.markForCheck();
}
In the WasGreetedComponent
you would add:
constructor(parent: GreeterServiceComponent) {}
deleteMe() { this.parent.deleteUser(this.user); }
Global state via reactive programming
The final approach uses reactive programming where observables are used that allow consumers to watch for changes in the applications state. This has the advantage that the state is owned and managed by an external service. Popular services such as Redux/NGRX/NGXS are used heavily in Angular/React/Vue frameworks. There are many advantages to using a state store, but these are difficult frameworks to master and for a small project it's often overkill. Once you start using them they are difficult to quit using.
We can create our own small version as a demonstration.
You would add a service to represent your application state.
@Injectable({provideIn: 'root'})
export class StateService {
public users: BehaviorSubject<any[]> = new BehaviorSubject([]);
public addUser(user: any) {
this.users
.pipe(first())
.subject(users => this.users.next([...users, user]));
}
public removeUser(user: any) {
this.users
.pipe(first())
.subject(users => this.users.next(users.filter(u=>u !== user)));
}
}
Now, in your GreeterServiceComponent
it will no longer be the owner of the state. We will inject the above service and allow that service to manage it.
@Component({...})
export class GreeterServiceComponent implements OnInit {
constructor(public state: StateService) {}
greetingFunc(newUser : string) {
if(newUser) {
this.state.addUser(newUser);
}
}
In the GreeterServiceComponent
template we will use the async
pipe to display the current state of users directly from the StateService
. We do this because the service holds the truth about the current state. The GreeterServiceComponent
component will not care about how it changes, because it does not own or manage it.
<div class="column" *ngFor="let user of state.users | async">
<app-was-greeted [user]="user"></app-was-greeted>
</div>
The WasGreetedComponent
component will use the StateService
to make changes to the current state. This component does not care about it's parent component. You can move this component around in your application and it will still function correctly. This is an important benefit, because the other above approaches depend upon the structure of the DOM itself. So moving a component would require changes elsewhere to keep the component working.
@Component({...})
export class WasGreetedComponent implements OnInit {
constructor(public state: StateService) {}
deleteMe() {
this.state.removeUser(this.user);
}