Cannot see parent variable after changing to use custom dropdown? - javascript

I have a component with 2 direct children, both of which use the event variable that is held within the parent. However, upon changing one of the components which uses a dropdown list from using a <select> to a custom-animated dropdown... I can no longer see the event within this child, despite using the near-identical code.
Parent.ts
event: IEvent;
constructor(private eventService: EventService) {
}
ngOnInit() {
this.subToEventService();
}
subToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.event = res;
}
}
Child 1 (can see event)
export class ChildOne extends ParentComponent implements OnInit {
constructor(eventService: EventService) {
super(eventService);
}
ngOnInit() {
console.log(this.event);
}
}
Child 2 (cannot see event)
export class ChildTwo extends ParentComponent implements OnInit {
#ViewChild('dropdown') dropdown: ElementRef;
expanded = false;
constructor(eventService: EventService) {
super(eventService);
}
ngOnInit() {
console.log(this.event);
}
toggleDropdown() {
const dropdown = this.dropdown.nativeElement;
if (this.expanded) {
TweenMax.to(dropdown, 0.5, {...});
} else {
TweenMax.to(dropdown, 0.5, {...});
}
this.expanded = !this.expanded;
}
determineStyle() {
const style = this.dropdown.nativeElement.style;
style.height = this.expanded ? 376 : 34;
}
}

In both your child components, you are overriding the ngOnInit method of the ParentComponent class.
You need to call super.ngOnInit(); on both child ngOnInit to subscribe to your subject inside those components.

Related

Angular: Check when Output Variable in Component Changes?

How do I check if Output in Component changes? Then run another method
Here is Parent component,
After it gets data from Child, want to immediately run another event.
Parent HTML:
<div>
Address Type:*
<app-address-type-dropdown (selectedItemOutput) = "test"></app-address-type-dropdown>
</div>
Parent Typescript:
Goal: When value is outputted, detect changes in this parent, and write console command.
export class AddressFormatheaderFormComponent implements OnInit {
constructor() { }
public test: any;
public sayHi(){
console.log(this.test);
}
ngOnInit() {
}
}
You can do so by creating another function, an event handler essentially. So when your child component <app-address-type-dropdown> emits a value, this event handler will take care of what to do next.
E.g. onNewItemSelect($event) is the event handler.
<div>
Address Type:*
<app-address-type-dropdown (selectedItemOutput)="onNewItemSelect($event)"></app-address-type-dropdown>
</div>
export class AddressFormatheaderFormComponent implements OnInit {
constructor() { }
public test: any;
ngOnInit() {
}
onNewItemSelect(itemSelected){
this.test = itemSelected;
console.log(this.test);
//do something else
}
}
Do have read on this section of of Angular official docs on component interaction for more information.
I think you will need EventEmitter so in child component, when value changes, it will emit event and in parent component, it will detect changes and call parent function.
Something like let's say your child component.
import { Component, EventEmitter, Output } from '#angular/core';
#Component({
selector: 'app-address-type-dropdown,
template: `<button class='btn btn-primary' (click)="valueChanged()">Click me</button> `
})
export class AppAddressTypeDropdown{
#Output() selectedItemOutput= new EventEmitter();
Counter = 0;
valueChanged() { // You can give any function name
this.counter = this.counter + 1;
this.selectedItemOutput.emit(this.counter);
}
}
And in parent html, just try update call slightly. Please call any function to know when it changes.
<app-address-type-dropdown (selectedItemOutput) = "changeDetect($event)"></app-address-type-dropdown>
export class AddressFormatheaderFormComponent implements OnInit {
constructor() { }
public test: any;
public sayHi(){
console.log(this.test);
}
ngOnInit() {
}
changeDetect(counter){
console.log(counter);
//do something here
}

#Input property is not being updated second time

I'm creating a reusable component which can be shown from any external component, but can be hidden using a function in same component, but somehow the property change in parent component is not updating child.
Here is the stackblitz for the same.
https://stackblitz.com/edit/angular-hfjkmu
I need "Show" button should show the component all the time and I can hide the component using "hide" button any time.
you need sync value from child to parent using Output
#Input()
show = false;
#Output()
showChange = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
hide(){
this.show = false;
this.showChange.emit(this.show);
}
<app-show-hide [(show)]="show"></app-show-hide>
The show property from child do not pointing to same prop in the parent comp, because it's primitive value.
I don't recommend to modify data that not belong to child component (reference type, eg: object, array), it can lead to unexpected behavior.
Online demo with reference type (be careful when modify ref type): https://stackblitz.com/edit/angular-vhxgpo?file=src%2Fapp%2Fshow-hide-obj%2Fshow-hide-obj.component.tsenter link description here
You have the problem because your child component modify Input value within your child component scope so no way parent component know the data is change
Your child component
export class ShowHideComponent implements OnInit {
#Input('show') show: boolean;
#Output() updateShowValue: EventEmitter<any> = new EventEmitter<
any
>();
constructor() { }
ngOnInit() {
console.log(this.show);
}
hide() {
this.updateShowValue.emit(!this.show);
}
}
In the app.component.html
<app-show-hide [show]="show" (updateShowValue)="update($event)"></app-show-hide>
And app.component.ts
export class AppComponent implements OnInit {
show:boolean = false;
ngOnInit() {
this.show = false;
console.log(this.show)
}
showComp(){
this.show = !this.show;
}
update(event) {
this.show = event;
}
}
You need to add an #Output in your child component, when you click the hide button (in the child component) you need to notify your parent component and change the value of show variable to false, this is done with the EventEmitter.
Changes to made are :
ShowHideComponent.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-show-hide',
templateUrl: './show-hide.component.html'
})
export class ShowHideComponent {
#Input('show') show : boolean;
#Output('') hideEE = new EventEmitter();
constructor() { }
hide(){
this.hideEE.emit(false);
}
}
AppComponent.ts
import { Component,OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
show:boolean = false;
}
appComponent.html
<button type="button" (click)="show = true">Show</button>
<app-show-hide [show]="show" (hideEE)="show = $event"></app-show-hide>
stackblitz Link

Creating nested dynamic components in angular

I Want to know how to create nested dynamic components and maintains its parent child relationship.
For example, I have data like this,
- A
--A.1
--A.2
-B
--B.1
-C
I wanted to create the component like this,
<A>
<A1></A1>
<A2></A2>
</A>
<B>
<B1></B1>
</B>
<C></C>
But with my code I could only create parent component or child component. But not both.
Below is my code,
setRootViewContainerRef(view: ViewContainerRef): void {
this.rootViewContainer = view;
}
createComponent(content: any, type: any) {
console.log(content);
if (content.child && content.child.length > 0) {
content.child.forEach(type => {
const typeP = this.contentMappings[type.type];
this.createComponent(type, typeP);
});
} else {
this.renderComp(content,type)
}
}
renderComp(content,type) {
if (!type) {
return
}
this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
this.componentReference = this.rootViewContainer.createComponent(this.componentFactory);
if (this.componentReference.instance.contentOnCreate) {
this.componentReference.instance.contentOnCreate(content);
}
}
With this code, I get this output.
Link to working example, StackBlitz
Please help me to resolve this issue.
Updated.
Even after adding the viewChild, It still throws the viewchild not defined.
Refer this image, In the component.instance I'm not seeing the view child element.
Updated stackblitz link https://stackblitz.com/edit/angular-dynamic-new-mepwch?file=src/app/content/a/a.component.ts
You should create ViewContainer on each level that is going to render child components:
a.component.html
<p>
a works!
</p>
<ng-container #container></ng-container>
a.component.ts
export class AComponent implements OnInit {
#ViewChild('container', { read: ViewContainerRef, static: true }) embeddedContainer: ViewContainerRef;
And then render component to dedicated container:
create-dynamic-component.service.ts
#Injectable()
export class CreateDynamicComponentService {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
#Inject(CONTENT_MAPPINGS) private contentMappings: any,
private inlineService: InlineService
) { }
createComponent(content: any, type: any, vcRef) {
const componentRef = this.renderComp(content, type, vcRef)
if (content.child && content.child.length) {
if (!componentRef.instance.embeddedContainer) {
const cmpName = componentRef.instance.constructor.name;
throw new TypeError(`Trying to render embedded content. ${cmpName} must have #ViewChild() embeddedContainer defined`);
}
content.child.forEach(type => {
const typeP = this.contentMappings[type.type];
this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
});
}
}
renderComp(content,type, vcRef: ViewContainerRef) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
const componentRef = vcRef.createComponent<any>(componentFactory);
if (componentRef.instance.contentOnCreate) {
componentRef.instance.contentOnCreate(content);
}
return componentRef;
}
}
Note how renderComp method takes ViewContainerRef from the component with children:
this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
Forked Stackblitz

subscription to behaviour subject don't work on all components

I my global service I instiante a behaviourSubject variable
dataWorkFlowService:
export class CallWorkflowService {
url = 'http://localhost:3000/';
selectedNode : BehaviorSubject<Node> = new BehaviorSubject(new Node(''))
dataflow : BehaviorSubject<any> = new BehaviorSubject<any>({});
constructor(private http: HttpClient) {}
getDataflow() {
return this.http.get(this.url);
}
updateNode(node :Node) {
this.selectedNode.next(node);
}
}
In my component ReteComponent I set behaviourSubject value using
this.dataFlowService.selectedNode.next(node);
Im my second component I subscribe to the BehaviourSubject
export class ComponentsMenuComponent implements OnInit {
constructor(private callWorkflowService:CallWorkflowService) { }
selectedNode:Node = new Node('');
dataFlow:any;
nxtElements:String[]=[]
ngOnInit() {
this.callWorkflowService.dataflow.subscribe(data=> {
this.dataFlow=data
})
this.callWorkflowService.selectedNode.subscribe( (node) => {
this.selectedNode=node; <=== ###### Subscription is not triggered
if(this.dataFlow) {
this.nxtElements=this.dataFlow[node.name].next;
}
})
}
When I trigger new value to selectedNode my subscription does not work
But in another component it's working well
export class AppComponent {
opened:boolean=false;
events: string[] = [];
constructor(private callWorkflowService:CallWorkflowService) { }
ngOnInit() {
this.callWorkflowService.selectedNode.pipe(
skip(1)
)
.subscribe( (node) => {
this.opened=true; <== subscription is working
})
}
}
I have noticed in that in ComponentsMenuComponent when I change it to
export class ComponentsMenuComponent implements OnInit {
constructor(private callWorkflowService:CallWorkflowService) { }
selectedNode:Node = new Node('');
dataFlow:any;
nxtElements:String[]=[]
ngOnInit() {
this.callWorkflowService.getDataflow().subscribe(data=> {
this.dataFlow=data;
}) ####CHANGE HERE ### <== using `getDataFlow` method which is not observable
this.callWorkflowService.selectedNode.subscribe( (node) => {
this.selectedNode=node; ### <=== subscription is triggered
if(this.dataFlow) {
this.nxtElements=this.dataFlow[node.name].next;
}
})
}
the selectNode subscription is working.
Update
I have tried to change how I proceed
In my service I added a method that return last value
updateDataFlow() {
return this.dataflow.getValue();
}
In ComponentsMenuComponent
this.callWorkflowService.node.subscribe( (node) => {
this.dataFlow = this.callWorkflowService.updateDataFlow();
this.selectedNode=node;
if(this.dataFlow) {
this.nxtElements=this.dataFlow[node.name].next;
}
})
Here again subscription is not working..
I have tried to comment the line
this.dataFlow = this.callWorkflowService.updateDataFlow();
And here surprise.. subscription works.
I don't know why it don't subscribe when I uncomment the line that I have mentioned
You must be providing your CallWorkflowService incorrectly and getting a different instance of the service in different components. If one component is working and another is not then I would guess that they are not both subscribed to the same behavior subject.
How are you providing the service? Is it provided in a module, component or are you using provided in?

How to refresh one component (nav-bar) when button is clicked from another component?

I want to reload component after a button click from another component Angular 6.
As #MariyamMohammedJalil said you can use an EventEmitter to trigger the update of your first component.
See following sample:
first.component.ts
#Component({
selector: 'first-component',
template: '<div>{{label}}</label>
})
export class FirstComponent {
#Input() update: EventEmitter<string>;
label = 'First Component';
constructor() {}
ngOnInit() {
if (this.update) {
// Subscribe to the event emitter to receive an update event
this.update.subscribe((value: string) => {
this.refresh(value);
})
}
}
refresh(value: string) {
// Do your stuff here
this.label = value;
}
}
second.component.ts
#Component({
selector: 'second-component',
template: '<button (click)="updateFirstCmp()">Update First Component</button>'
})
export class SecondComponent {
#Input() update: EventEmitter<string>;
constructor(){}
updateFirstCmp() {
// Emit an event to update your first component
this.update.emit('Updated First Component');
}
}
And for example you should add following to your app.component.ts:
updateEventEmitter: EventEmitter<string>;
constructor() {
...
this.updateEventEmitter = new EventEmitter();
}
And in your app.component.html:
<first-component [update]="updateEventEmitter"></first-component>
<second-component [update]="updateEventEmitter"
Another way do solve your problem can be to enter the first.component as input parameter to the second.component to call the refresh function directly without EventEmitter. See following sample:
app.component.html
<first-component #firstComponent></first-component>
<second-component [firstCmp]="firstComponent"></second-component>
second.component.ts
#Component({
selector: 'second-component',
template: '<button (click)="updateFirstCmp()">Update First Component</button>'
})
export class SecondComponent {
#Input() firstCmp: FirstComponent;
constructor(){}
updateFirstCmp() {
// Update the first component directly
this.firstCmp.refresh('Updated First Component');
}
}
With this sample you don't need to subscribe to an update event, because you're not using an EventEmitter.

Categories