How to assign global variable from subscribe() function and update the view - javascript

I am doing the task and I have a problem I can't assign value to global to be seen in the view in angular8. I know what is asynchronous but how to assign the value and update the view.
post-details.component.ts
export class PostDetailsComponent implements OnInit {
indexOfPost;
titles=[];
subscription: Subscription;
renderPost:boolean=false;
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService,private detectChanges: ChangeDetectorRef) {
this.subscription = this.getSharedTitles.getMessage().subscribe((title)=>{
this.titles=title.titles;
this.renderPost=true;
this.detectChanges.markForCheck();
});
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
post-details.component.html
<p *ngIf="renderPost">works</p>
It does not work. My aim is to show the titles in post-details.component.html. I'll be grateful for the tips.Regards

There is no need to write too much for a simple task. Just explore async pipe and it will automatically subscribe and unsubscribe.
export class PostDetailsComponent implements OnInit {
message$ = this.getSharedTitles.getMessage();
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService) {
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
In template
<p *ngIf="( message$ | async) as message">{{message.title}}</p>

You can use async pipe which unsubscribes auto-magically.
export class PostDetailsComponent implements OnInit {
indexOfPost;
titles: Observable<any> = this.getSharedTitles.getMessage();
constructor(private activatedRoute:ActivatedRoute,private getSharedTitles:ShareInfoTitleService,private detectChanges: ChangeDetectorRef) {
}
ngOnInit() {
this.indexOfPost=this.activatedRoute.snapshot.paramMap.get("id");
}
}
in template
<p *ngIf="(titles | async)">works</p>

Related

How to use RXJS to make a click event emit a value in angular

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?

Share a method between two child components (Angular)

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();
}
}

Confusing while Passing data between the components

I am new in angular 6, I am creating the project using angular 6. I am coming to the problem while sharing the data.
Here is my code:
1) Component Sidebar:
selectedCategory(type:any) {
this.loginService.categoryType = type; // need to pass this data
}
2) List Comp:
export class ListPostsComponent implements OnInit {
ngOnInit() {
// here I need the data
}
}
3) Service:
export class LoginService {
categoryType:any;
}
In your service make categoryType a Subject and call the next() when you need to pass data to another component:
#Injectable({
providedIn: 'root',
})
export class LoginService {
private categoryType: Subject<any> = new Subject<any>();
public categoryType$ = this.categoryType.asObservable();
public sendData(data: any){
this.categoryType.next(data);
}
}
Now in your Component Sidebar, you need to inject the service LoginService and call the sendData method:
constructor(private loginService: LoginService ){ }
selectedCategory(type:any) {
this.loginService.sendData(type);
}
Since a Subject is both an Observer and an Observable you can subscribe to the Subject and listen for changes in the component you wish to receive the data:
export class ListPostsComponent implements OnInit {
constructor(private loginService: LoginService ){ }
ngOnInit() {
this.loginService.categoryType$.subscribe((data) => {
//use your data here
});
}
}
Here is a working example of the above solution in Stackblitz: https://stackblitz.com/edit/angular-2sld4k?file=src%2Fapp%2Floginservice.service.ts

Reference to service from dynamically added component in Angular 6 CLI

I created a directive in Angular 6 named 'DeleteDirective' and reference to a service 'DeleteService' to make sure I can delete an item from my application. After the item is marked as deleted (in PHP back-end), I'll show an Undo element via the 'UndoComponent' that I dynamically added in the DeleteService. No problems so far.
#Directive({
selector: '[appDelete]'
})
export class DeleteDirective {
constructor(
#Inject(ViewContainerRef) viewContainerRef,
renderer: Renderer2
) {
service.renderer = renderer;
service.setRootViewContainerRef(viewContainerRef);
service.addUndoElement();
}
#HostListener('click') onClick() {
// (Some code to execute deletion)
this.deleteService.showUndoElement();
}
#Injectable({
providedIn: 'root'
})
export class DeleteService {
constructor(
rendererFactory: RendererFactory2,
private factoryResolver: ComponentFactoryResolver,
private appRef: ApplicationRef,
) {
this.renderer = rendererFactory.createRenderer(null, null);
this.factoryResolver = factoryResolver;
}
setRootViewContainerRef(viewContainerRef) {
this.rootViewContainer = viewContainerRef;
}
addUndoElement() {
const factory = this.factoryResolver.resolveComponentFactory(UndoComponent);
const component = factory.create(this.rootViewContainer);
// this.rootViewContainer.insert(component.hostView);
this.appRef.attachView(component.hostView);
const domElem = (component.hostView as EmbeddedViewRef<any>)
.rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
}
Now, in the UndoComponent HTML I created a link to undo the action, named restoreItem. I would like to use another service named ListService to get some data again.
#Injectable()
export class UndoComponent implements OnInit {
constructor(private listService: ListService) {
}
restoreItem() {
this.currentList = this.listService.getSelectedList();
console.log(this.currentList); // null
}
}
It seems I cannot reference to the ListService (or any other service) from this dynamically added component to the DOM. It returns null. Any ideas how I can access a service from a dynamically added Component? Thanks so much for any directions!
Edit: added Listservice stub code for clarification
#Injectable({
providedIn: 'root'
})
export class ListService {
lists: List[];
list: List[];
currentList: List;
constructor(private http: HttpClient) { }
setSelectedList(list: List): void {
this.currentList = list;
}
getSelectedList(): List {
return this.currentList;
}
private handleError(error: HttpErrorResponse) {
console.log(error);
return throwError('Error! something went wrong.');
}
}
Are you setting the value of currentList in ListService in anyway.
setSelectedList in ListService is never called which is being used to set value of currentList. So currentList remains null.

Angular4 - let multiple unrelated components notify each other of the problem of updating data, and whether there is a cleaner coding method?

I have encountered a project in progress, let multiple unrelated components notify each other of the update data, is there a cleaner coding method?
There are 3 components (more likely later) and a common-data component. They have no parent-child relationship with each other and only show on the same screen.
The desired effect is to press the button of any component, update the contents of common-data, and notify yourself and other components to fetch new messages from common-data.
At present, my approach is to use Rx's Observable and Subscription, but they must be imported in the component.ts and service.ts files of each component, and a lot of duplicate code appears, it is very messy, I don't know what is better. practice?
Thanks!
My code :
The sample name is test-a-comp (a.b.c and so on, the code is the same)
test-a-comp.html
<p>
{{ownMessage}}
</p>
<button (click)="sendChange()">update</button>
test-a-comp.component
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CommonData } from '../common-data/common-data';
import { TestACompService } from './test-a-comp.service';
import { TestBCompService } from '../test-b-comp/test-b-comp.service';
import { TestCCompService } from '../test-c-comp/test-c-comp.service';
#Component({
selector: 'app-test-a-comp',
templateUrl: './test-a-comp.component.html',
styleUrls: ['./test-a-comp.component.css']
})
export class TestACompComponent implements OnInit {
subscription: Subscription;
ownMessage;
constructor(
private testAService: TestACompService,
private testBService: TestBCompService,
private testCService: TestCCompService,
) {
this.subscription = this.testAService.getMessage()
.subscribe((test) => {
CommonData.message = test;
});
this.subscription = this.testBService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
this.subscription = this.testCService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
}
ngOnInit() {
}
sendChange() {
this.testAService.sendMessage();
}
}
test-a-comp.service:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
#Injectable()
export class TestACompService {
subscription: Subscription;
private subject = new Subject<any>();
constructor() {
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
sendMessage(): void {
this.subject.next('update message from A');
}
}
As far as i understand & you've mentioned in the above, there is a button in one of the component (test-a-component.html). If you update the button, you need to send message to other components which are subscribed.
The Components which have no Parent-Child relationship can communicate via a service:
Create a single service file (In your case: test-a-comp.service)
Create a Subject on what data you need to communicate via this service:
export class testMessageService {
constructor() {}
// Observable string sources
private message = new Subject<string>();
//Observable string streams
testMessage$ = this.message.asObservable();
constructor() {}
// Method to send message when a button is clicked
sendMessage(message: string) {
this.message.next(message);
}
/* You don't need "getMessage()" method as you've already subscribed to
the observables. There subscribed Observable string streams are
injected in your components (As below point 3) to display / do other
operation on the message. */
}
In your other Components, where you want to receive messages, do the following:
export class TestComponent 1 {
myMessage1: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage1 = message;
});
}
export class TestComponent 2 {
myMessage2: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage2 = message;
});
}
export class TestComponent 3 {
myMessage3: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage3 = message;
});
}
For more information/guidance refer Component interaction via a common
service: https://angular.io/guide/component-interaction
Hope this helps!

Categories

Resources