How can I unsubscribe in this function after dialog's closeAll method? Is it possible to turn subscription to a variable and then somehow apply it to 'onProjectAdded' instance?
this.dialog
.open(GridAddDialogComponent)
.componentInstance.onProjectAdded.subscribe((projectData) => {
this._gridApi.setRowData([...this.rowData1, projectData]);
})
this.dialog.closeAll()
}
There are 2 ways:
1. Auto unsubscribe after component destroy:
export class ExampleComponentComponent implements OnInit, OnDestroy {
readonly _destroy$: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
constructor(
private readonly dialog: MatDialog
) { }
ngOnInit() {
this.dialog
.open(GridAddDialogComponent)
.componentInstance.onProjectAdded
.pipe(takeUntil(this._destroy$))
.subscribe((projectData) => {
this._gridApi.setRowData([...this.rowData1, projectData]);
})
this.dialog.closeAll();
}
ngOnDestroy(): void {
this._destroy$.next(null);
this._destroy$.complete();
}
}
Unsubscribe when you need it(in my case afret destroying component but you can do it everywhere):
export class ExampleComponentComponent implements OnInit, OnDestroy {
public subscriptions: Subscription[] = [];
constructor(
private readonly dialog: MatDialog
) { }
ngOnInit() {
const subscription = this.dialog
.open(GridAddDialogComponent)
.componentInstance.onProjectAdded
.subscribe((projectData) => {
this._gridApi.setRowData([...this.rowData1, projectData]);
});
this.subscriptions.push(subscription);
this.dialog.closeAll();
}
ngOnDestroy(): void {
this.subscriptions.forEach((sub) => sub.unsubscribe());
}
}
Also you can use single subscription instead of array of it.
Related
I hope not to get dom directly.Not use document.querySelectorćViewChild...
I need to create an Observable and mount the internal variables this.subscribe = subscribe to the component instance. I think this is not good, very jumping.
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Observable, Subscriber, Subscription } from 'rxjs';
#Component({
selector: 'app-root',
template: '<button (click)="onClick()">button</button>',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
subscribe!: Subscriber<void>
subscription!: Subscription
count: number = 0
ngOnInit(): void {
this.subscription = new Observable<void>((subscribe) => {
this.subscribe = subscribe
})
.subscribe(() => {
console.log('update', ++this.count)
})
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
onClick() {
this.subscribe.next()
}
}
It looks like you haven't discovered Subject yet because you have sort of reinvented it :-)
Basically Subject is an object that you can subscribe to as an Observable, but you can also push values through by calling its .next() method.
export class AppComponent implements OnInit, OnDestroy {
click$ = new Subject<void>()
subscription!: Subscription
count: number = 0
ngOnInit(): void {
this.subscription = this.click$.subscribe(() => {
console.log('update', ++this.count)
})
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
onClick() {
this.click$.next()
}
}
You could define your count as an observable by using the scan operator like this:
export class AppComponent implements OnInit, OnDestroy {
subscription!: Subscription
private click$ = new Subject<void>()
count$: Observable<number> = this.click$.pipe(
scan(previous => previous + 1, 0),
tap(count => console.log('update', count))
)
ngOnInit(): void {
this.subscription = this.count$.subscribe()
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
onClick() {
this.click$.next()
}
}
In many cases you don't need to subscribe in your component, you can use the async pipe in your template instead. This alleviates the need to keep track of Subscription and also doesn't require implementing OnInit and OnDestroy:
export class AppComponent {
private click$ = new Subject<void>()
count$ = this.click$.pipe(
scan(previous => previous + 1, 0),
tap(count => console.log('update', count))
);
onClick() {
this.click$.next()
}
}
Then in your template, do something like:
<p> {{ count$ | async }} </p>
Well you already have an observable, (click) is an event emitter which extends an RxJs subject. So (click)="onClick()" is telling your component to subscribe to the click event emitter of the button with your onClick function, what extra observable do you need?
I have a Project that uses HTTP Calls to fetch Data from API Endpoint and than display on Screen.
It's a simple ToDoList. So you can add Items to the list, see all Items in your List, delete items and so on.
The Project structure is this:
Items-Component (Holds the entire App basically)
Item-list-component
Item-detail-component
Item-edit-component
item-add-component
Item.service
The Items.component.html looks like this:
<div class="row">
<div class="col-md-5">
<app-item-list></app-item-list>
</div>
<div class="col-md-7">
<router-outlet></router-outlet>
</div>
So we can see that the item-list-component and the other 3 components (binded via router-outlet) are sibling components, that's what I think.
So my Problem is now:
I want that whenever a new Item is created the items[] in the items.list component should refresh automatically. Now I must click a "Fetch Items" button to refresh the items[].
When I add a new Item, it fires a method from my item.service, it holds a fetchItems Method that just returns an Observable of the API Endpoint, like this:
Item-add component.ts:
#Component({
selector: 'app-item-add',
templateUrl: './item-add.component.html',
styleUrls: ['./item-add.component.css']
})
export class ItemAddComponent implements OnInit {
constructor(private itemService: ItemService, private route: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
}
onCreatePost(item: Item) {
// Send Http request
this.itemService.createAndStorePost(item.description, item.isComplete);
//Here I want that the items[] in the items.list component refreshes when I add new Item
this.onCancel();
}
onCancel() {
this.router.navigate([''], {relativeTo: this.route});
}
}
And the item.service.ts:
#Injectable()
export class ItemService {
constructor(private http: HttpClient, private route: ActivatedRoute, private router: Router) {
}
fetchItems(): Observable<Item[]> {
return this.http.get<Item[]>('https://localhost:44321/api/TodoItems');
}
fetchItem(id: number): Observable<Item> {
return this.http.get<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
createAndStorePost(description: string, isComplete: boolean) {
var item = { description: description, isComplete: isComplete };
this.http.post('https://localhost:44321/api/TodoItems', item)
.subscribe(Response => {
});
}
deleteItem(id: number): Observable<Item> {
return this.http.delete<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
updateItem(id:number, item: Item) {
this.http.put<Item>('https://localhost:44321/api/TodoItems' + '/' + id, item).subscribe();
}
}
Then the items-list component catches that Observable and subscribes to it and sets the Response from that subscription to and items[] in the component itself:
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
constructor(private route: ActivatedRoute, private router: Router, private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
});
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
}
What can I do to trigger that the items.list should fetch Items again?
I can't use #ViewChild because it is no Parent-Child relation.
Can I implement and instance of item.list anywhere in the project and just call the onFetchItems Method?
Thanks!
you can use BehaviorSubject to share data between your different components.
Here is an example:
In your ItemService.
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ItemService {
private _itemsSource = new BehaviorSubject([]);
currentItems = this._itemsSource.asObservable();
constructor() { }
updateItems(items: []): void {
this._itemsSource.next(items)
}
}
In your ItemsComponent, you update the new value in the service after you get all the items,
#Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
items: Item[] = [];
constructor(private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
this.updateItems(this.items)
});
}
updateItems(newItems: []): void {
this.itemService.updateItems(newItems)
}
}
And in your ItemListComponent
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
subscription: Subscription;
constructor(private route: ActivatedRoute,
private router: Router,
private itemService: ItemService) { }
ngOnInit(): void {
this.subscription = this.itemService.currentItems.subscribe(items => this.items = items)
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
i want find the params in previous route in angular typescript .
i use this code :
private previousUrl: string = undefined;
private currentUrl: string = undefined;
constructor(private router: Router) {
this.currentUrl = this.router.url;
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.previousUrl = event.url;
this.currentUrl = this.currentUrl;
}
});
}
but i can not access to the params of this url :
http://localhost:4200/claims-manager/200/edit
i want ti access 200 . how can i find params in url ????
You can do it in your component file but It is a best practice to do it in a service (using rxjs) to pass data and call it in your component file
In your service
export class myService {
constructor() { }
private param = new BehaviorSubject("");
sharedParam = this.param.asObservable();
paramToPass(param:string) {
this.param.next(param)}
}
In your component class that set param
export class ComponentSetParam {
param: string
constructor(private myService: Service)
this.myService.setParam(this.param);
}
in your appModule
#NgModule({
declarations: [YourComponents]
imports: [ AppRoutingModule, YourModules...],
providers: [ShareService],
})
export class AppModule {}
Component that you want to pass data
export class ComponentGetParam {
paramFromService: string
constructor(private myService: Service) {
this.shareService.sharedData.subscribe(data : string => {
this.paramFromService = data;
})
}
}
Try this:
readonly _destroy$: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
constructor(
private activatedRoute: ActivatedRoute,
) {
this.activatedRoute.parent.paramMap
.pipe(
distinctUntilChanged(),
takeUntil(this._destroy$)
)
.subscribe((params: ParamMap) => {
const id = params.get('id');
});
}
ngOnDestroy() {
this._destroy$.next(true);
this._destroy$.complete();
}
Where 'id' is a name, that you use in the routing, e.g.
path: '/claims-manager/:id/'
Demo You can do it in service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ShareService {
constructor() { }
private paramSource = new BehaviorSubject("");
sharedData = this.paramSource.asObservable();
setParam(param:string) { this.paramSource.next(param)}
}
in constructors
constructor(private shareService: ShareService)
in component in ngOnDestroy set this like this.shareService.setParam(param);
in appmodule
providers:[ShareService ]
in new component in ngOnInit or in constructor get like
this.shareService.sharedData.subscribe(data=> { console.log(data); })
There is such structure of components:
Desired Behavior
child1_component - is a header.
child2_component - is a body.
There is a button inside child1_component.
Clicking on that button I want to invoke a method inside child2_component.
Question
What is the best way to implement this?
One way to approach this would be to use a service with rxjs subjects and observables.
When the user clicks on the button in child1_component then it calls a method that in turn calls a method inside the shared service.
When the method in the service is called it can emit a value as an observable via a subject.
child2_component then subscribes to the observable within the shared service and can operate some logic based on when it receives data from the service.
More on services here: https://angular.io/tutorial/toh-pt4
Great tutorial on subjects and rxjs: https://blog.angulartraining.com/rxjs-subjects-a-tutorial-4dcce0e9637f
On your general.component.html :
<app-child1 (clicked)="app1Clicked($event)"></app-child1>
<app-child2 #child2></app-child2>
On your general.component.ts:
#ViewChild('child2', {static: true}) child2: Child2Component;
app1Clicked($event) {
this.child2.doSomething()
}
On the child1.components.ts:
#Output() clicked = new EventEmitter<any>();
onClick() {
this.clicked.emit();
}
Finally on the child2.component.ts:
doSomething() {
alert('ok');
}
There are 2 ways to do it:
1.Service:
export class ActionService {
private someAction = new Subject();
someActionEmitted$(): Observable<unknown> {
return this.someAction.asObservable();
}
emitSomeAction(): void {
this.someAction.next();
}
}
//childComponent1
export class ChildComponent1 {
constructor(private actionService: ActionService) {
}
emitAction(): void {
this.actionService.emitSomeAction();
}
}
//childComponent2
export class ChildComponent2 implements OnInit, OnDestroy {
private destroy$ = new Subject();
constructor(private actionService: ActionService) {
}
ngOnInit(): void {
this.actionService.someActionEmitted$()
.pipe(takeUntil(this.destroy$)) // dont forget to unsubscribe, can cause memory leaks
.subscribe(() => this.doSomething());
}
doSomething(): void {
// your logic here
}
ngOnDestroy(): void {
this.destroy$.next();
}
}
2. Using Parent Component
<child-component1 (btnClicked)="childComponentBtnClick()"></child-component1>
<child-component2 [clickBtnSubject]="childBtnClicked"></child-component1>
Ts logic:
export class ParentComponent {
childBtnClicked = new Subject();
childComponentBtnClick(): void {
this.childBtnClicked.next();
}
}
//childComponent1
export class ChildComponent1 {
#Output() btnClicked = new EventEmitter();
emitAction(): void {
this.btnClicked.emit(); // you can pass value to emit() method
}
}
//childComponent2
export class ChildComponent2 implements OnInit, OnDestroy {
#Input() clickBtnSubject: Subject;
ngOnInit(): void {
this.clickBtnSubject
.pipe(takeUntil(this.destroy$)) // dont forget to unsubscribe, can cause memory leaks
.subscribe(() => this.doSomething());
}
doSomething(): void {
// your logic here
}
ngOnDestroy(): void {
this.destroy$.next();
}
}
I have a dynamic component loader, and I need to pass in data through a service. I can get the data to display if I fire the function on click for example, but not OnInit.
I have tried using AfterViewInit
Eventually the data will be coming from an API
Update:
Working StackBlitz
app.component.html
<app-answer-set></app-answer-set>
header.component.html
<ng-template appHeaderHost></ng-template>
header.component.ts
export class HeaderComponent implements OnInit {
#Input() component;
#ViewChild(HeaderHostDirective) headerHost: HeaderHostDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngOnInit() {
this.loadComponent();
}
loadComponent() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.component);
const viewContainerRef = this.headerHost.viewContainerRef;
viewContainerRef.createComponent(componentFactory);
}
}
header-host.directive.ts
export class HeaderHostDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
header-data.service.ts
export class HeaderDataService {
private headerDataSource = new Subject<any>();
headerData$ = this.headerDataSource.asObservable();
constructor() { }
setHeaderData(data: any) {
this.headerDataSource.next(data);
}
}
answer-set.component.html
<app-header [component]="component"></app-header>
<button (click)="setHeaderData()">click</button>
answer-set.component.ts
export class AnswerSetComponent implements OnInit {
component: any = AnswerSetHeaderDetailsComponent;
constructor(private headerDataService: HeaderDataService) { }
ngOnInit() {
this.setHeaderData();
}
setHeaderData() {
const data = [{name: 'Header stuff'}];
this.headerDataService.setHeaderData(data);
}
}
answer-set-header-details.html
<dt>First:</dt>
<dd>Description</dd>
<dt>Second</dt>
<dd>Description</dd>
<p>
data will show on click of button but not <code>onInit</code>:
</p>
<p>
{{headerData}}
</p>
answer-set-header-details.component.ts
export class AnswerSetHeaderDetailsComponent implements OnInit {
constructor(private headerDataService: HeaderDataService) { }
headerData: any;
ngOnInit() {
this.headerDataService.headerData$
.subscribe(data => {
this.headerData = data[0].name;
});
}
}
After running through the Angular documentation I found that I needed to pass the data to the componentRef instance, something like this:
(<IHeader>componentRef.instance).data = headerItems.data;
After a little refactoring I ended up passing both the component and the data through a service, see the updated StackBlitz for a working example.
Eventually I'll look to pass in multiple components, but for now this works.
header.component.html
<ng-template appHeaderHost></ng-template>
header.component.ts
/* ... */
export class HeaderComponent implements OnInit {
#ViewChild(HeaderHostDirective) headerHost: HeaderHostDirective;
subscriptionManager = new Subscription();
constructor(
private headerService: HeaderService,
private componentFactoryResolver: ComponentFactoryResolver
) {
const headerConfigSubscription = this.headerService.headerConfig$
.subscribe((headerItems: HeaderItem) => {
this.loadComponent(headerItems);
});
this.subscriptionManager
.add(headerConfigSubscription);
}
ngOnInit() {
}
loadComponent(headerItems: HeaderItem) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(headerItems.component);
const viewContainerRef = this.headerHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
(<IHeader>componentRef.instance).data = headerItems.data ? headerItems.data : null;
}
ngOnDestroy() {
/**
* #description Unsubscribe from all subscriptions
* to prevent memory leaks
*/
this.subscriptionManager.unsubscribe();
}
}
header.service.ts
/* ... */
export class HeaderService {
private headerConfigSource = new Subject<any>();
headerConfig$ = this.headerConfigSource.asObservable();
constructor() { }
configureHeaderItems(headerItems: HeaderItem) {
this.headerConfigSource.next(headerItems);
}
}
header.ts
/* ... */
export interface IHeader {
data: any;
}
export interface IHeaderItem {
component: Type<any>;
data?: any;
}
export class HeaderItem implements IHeaderItem {
constructor(
public component: Type<any>,
public data?: any
) {
this.component = component;
this.data = data;
}
}
main.component.html
<app-header></app-header>
main.component.ts
export class MainComponent implements OnInit {
headerItems: HeaderItem;
constructor(private headerService: HeaderService) { }
ngOnInit() {
this.configureHeaderItems();
}
configureHeaderItems() {
this.headerItems = new HeaderItem(MainHeaderDetailsComponent, {});
this.getHeaderItemData();
}
/**
* #description This is where we would make the API call and get the data
* but we can mock it for now
*/
getHeaderItemData() {
const data = {
name: 'I am loaded dynamically!'
};
this.headerItems.data = data;
this.headerService.configureHeaderItems(this.headerItems);
}
}
main.module.ts
#NgModule({
/* ... */
// Need to add to entryComponents
entryComponents: [MainHeaderDetailsComponent]
})
export class AnswerSetModule { }
main-header-details.component.html
<p>
Header content. {{data.name}}
</p>
main-header-details.component.ts
/* ... */
export class MainHeaderDetailsComponent implements OnInit {
constructor() { }
#Input() data: any;
ngOnInit() {
}
}