Send Component Dropdown Values two levels up in Angular with Event Emitter - html

I am trying to send data between two unrelated components. I am trying to utilize Event Emitter Output and Address Service.
How to get the first Address Dropdown Event Emitter, to Pass Data to the Service? The service can then send data to the Receiver.
export class AddressDropdownComponent implements OnInit {
addresses: any[] = [];
#Input() addressDefaultItem: AddressDto;
#Input() selectedAddress: any;
#Input() TxtField: string = 'addressDescription';
#Output() selectedItemOutput = new EventEmitter();
constructor(private addressService:AddressServiceProxy ) { }
ngOnInit() {
}
statusSelectedItemChanged(e) {
this.selectedAddress = e;
}
Still Working on This Address Service
export class AddressService {
private messageSource = new BehaviorSubject("default message");
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Resource: following is only for parent-child, looking for unrelated 'grandfather' or 'sibling' cases
What is the best way to use nested components in Angular 4 and calling a method of a parent to child and vice versa?

So you have a source let's say ComponentA and some component on top of it several levels let's say ComponentV.
To connect them you need first to connect them via a service. If there is only one instance of each of them you can use a singletone service (has providedIn: 'root' in #Injectable decorator). However, if you can have multiple ComponentV which contain ComponentA you need to provide this service at top-level of hierarchy. In this case ComponentV must have providers: [SomeService] to create a new service instance when ComponentV is created.
Then you define some prop in SomeService to share data.
For example you end up with
// SomeService contents
...
// create a source
private dataSource$ = new Subject();
// Expose as observable so it's value can be changed
// only via exposed method (dataChanged)
data$ = this.dataSource$.asObservable();
// public API to pass new data
dataChanged(data) {
this.dataSource$.next(data);
}
...
Then you inject this service in ComponentA and define a function to emit data change.
// ComponentA contents
...
constructor(private readonly someService: SomeService) {}
onDataChange(data) {
// here we notify about data change
this.someService.dataChanged(data);
}
...
Then you need to subscribe to the observable in top level component
// CompoentV contents
...
constructor(private readonly someService: SomeService) {
// here we subsribe to changes and use whatever handler we need
this.someService.data$.subscribe(data => {
// some logic goes here or pass this data to a method
});
}
...
This allows one to share some state or events between unrelated or loosely related components.

Related

Generating unattached dynamic components in Angular

I went through this issue while working on the ScheduleJS framework. At some point I am provided with a HTMLCanvasElement which I want to replace with a dynamically generated component programatically. To do so, and to keep the code as clean as possible, I'd like to create my own Angular components at runtime and use the HTMLCanvasElement.replaceWith(component) method from the provided HTMLCanvasElement replacing the canvas with the dynamically created component.
Here is the Angular service I came up with, which does the job the way I expected:
import {ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable, Injector, Type} from "#angular/core";
import {ReplacementComponent} from "xxx"; // This is a higher order type of Component
#Injectable({providedIn: "root"})
export class DynamicComponentGenerator {
// Attributes
private _components: Map<string, ComponentRef<ReplacementComponent>> = new Map();
private _currentKey: number = 0;
// Constructor
constructor(private _appRef: ApplicationRef,
private _resolver: ComponentFactoryResolver,
private _injector: Injector) { }
// Methods
create(componentType: Type<ReplacementComponent>): ComponentRef<ReplacementComponent> {
const componentRef = componentType instanceof ComponentRef
? componentType
: this._resolver.resolveComponentFactory(componentType).create(this._injector);
this._appRef.attachView(componentRef.hostView);
this._components.set(`${this._currentKey}`, componentRef);
componentRef.instance.key = `${this._currentKey}`;
this._currentKey += 1;
return componentRef;
}
remove(componentKey: string): void {
const componentRef = this._components.get(componentKey);
if (componentRef) {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(componentKey);
}
}
clear(): void {
this._components.forEach((componentRef, key) => {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(key);
});
this._currentKey = 0;
}
}
So basically this service lets me create a component with .create(ComponentClass) remove it by providing the component key .remove(key) and clear() to remove all the components.
My issues are the following:
The ComponentFactoryResolver class is deprecated, should I use it anyways?
Could not manage to use the newer API to create unattached components (not able to have access to an Angular hostView)
Is there a better way to do this?
Thank you for reading me.
You could try using new createComponent function:
import { createComponent, ... } from "#angular/core";
const componentRef =
createComponent(componentType, { environmentInjector: this._appRef.injector});
this._appRef.attachView(componentRef.hostView);

objects parsing in html component in angular

I want to use an object in #input parameter in the html.
for example:
<app-home [param]="user.salary"></app-home>
but the type of my user is something like that:
user:Person=new Employee();
my classes are:
export class Person {
constructor(public name:string){}
}
export class Employee extends Person {
constructor(public name:string,
public salary:number){
super(name);
}
}
how do I parse the user in the #input parameter to Employee?
I tried to do so:
<app-home [param]="(user as Employee).salary"></app-home>
but I get an error. so how can I do it?
If you want to pass the complete object, and as I may assume you're already defining it, change the param class to handle to object
class HomeComponent{
#Input() param: Employee;
}
then pass the object instead of a simgle property
<home-component [param]="user"></home-component>
This way you're getting the full component and you can now access and manipulate all it's properties.
If you have an object in home-component that you want to define by passing the user to it, try using a setter, like this
class homeComponent{
private _user:Employee;
#Input()
set param(data) {
this._user = data;
}
}
Or you can destructure it to handle easily each property and assign individually
class homeComponent{
private _user:Employee;
#Input()
set param({name, salary}) {
this._user = new Employee(name, salary)
}
}
If your User object is missing salary and you want to assign it after passing to the HomeComponent, you can try this
class homeComponent{
private _employee:Employee;
private _salary:number = 2000;
#Input()
set param(data) {
this._employee = new Employee({...data, salary: this._salary})
}
}
This way, you're getting your entire object, and trigger the setter to complete it's definition by adding the salary property;

How to pass a function from a parent to a deep nested child and use an #input value into the passed function in Angular 8?

I have 3 components in this situation:
-OuterComponent
--MiddleComponent
---InnerComponent
I need to pass a function from OuterComponent to InnerComponent through MiddleComponent.
It is important to mention that the function I need to pass does take an input: DoSomething(node)
I don't know if it is relevant but I am already passing a NodeTree from the OuterComponent to the MiddleComponent and then I am unpacking the NodeTree into a Node and passing it InnerComponent. This Node is what I need to use as an input for the function being passed.
So, I need to be able to use an #Input as the input for the function being passed to the InnerCompoenent, which I assume will need to be an #output.
Method 1:
You can call the parent component function(OuterComponent) from the child component(InnerComponent) using #Output.
OuterComponent HTML:
<MiddleComponent (updateOuterComponent)="parentFunction($event)" />
OuterComponent TS:
export class OuterComponent implements OnInit {
constructor() {}
ngOnInit() {}
parentFunction(para) {
console.log(para);
// operations you want to do in parent component
}
}
MiddleWare HTML:
<InnerComponent (updateMiddleComponent)="middleFunction($event)" />
MiddleComponent TS:
export class MiddleComponent implements OnInit {
#Output() updateOuterComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
middleFunction(para) {
this.updateOuterComponent.emit(para);
}
}
InnerComponent HTML:
It can be whatever you want to write
InnerComponent TS:
export class InnerComponent implements OnInit {
#Output() updateMiddleComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
updateMiddleAndParent(para) {
this.updateMiddleComponent.emit(para);
}
}
Once you call updateMiddleAndParent function form Inner component using emitter, that will trigger middleFunction in the MiddleComponent. After triggering middleFunction, Similarly middleFunction will trigger parentFunction with the help of emitter.
Method 2:
You need to create a service and use it to call the parent function:
DataService:
import {BehaviorSubject} from "rxjs/BehaviorSubject"
export class DataService {
private state$ = new BehaviorSubject<any>('initialState');
changeState(myChange) {
this.state$.next(myChange);
}
getState() {
return this.state$.asObservable();
}
}
call DataService in both InnerComponent and OuterComponent:
In the OuterComponent call DataService and call getState(), this will return an observable whenever the value changes you can any function using data passed in observable response.
In the InnerComponent call DataService and use the changeState() to change the value.
once the value is changed in DataService, then in parent Component the value will be change as you are subscribed to the observable, You will get the updated data from there you can call any function in parent.

Property getter calling function can't be binded?

I have a model class with a property that returns a value by calling a method, but when i try to bind that property, there is result on the page, but also no error occuring.
export class TestClass {
testProperty: string = this.getString();
getString() {
return 'hello';
}
}
in html:
{{model.testProperty}}
Does Typescript / Angular not support this? What is the common way to do it?
This is a simple enough class. What you could do is initialize testProperty as null or by a default value and in ngOnInit() assign the returned value from the function.
import { Component, OnInit } from '#angular/core';
export class TestClass implements OnInit {
testProperty: string = null;
ngOnInut() {
this.testProperty = this.getString();
}
getString() {
return 'hello';
}
}
The ngOnInit() is a lifecycle hooks and runs when the component is initialized.
public get testProperty(): string {
return 'hello'
}
If you use the 'get' after the public, it allow the function to be called as it would be a normal variable. I think you shouldnt even need the 'model.'

Angular 2 - Update view when variable changes

I have a button on my nav bar (app.component.html) that I want to only show when the user is logged in.
This is my current approach that does not work for obvious reasons explained later. I want to find out how I can modify it to work.
Inside my app.component.html, I have the following button
<button *ngIf="isCurrentUserExist">MyButton</button>
Inside my app.component.ts, I am trying to bound the variable isCurrentUserExist to a function that returns true if the user exists.
I believe this is the problem because this code is only executed once at OnInit as oppose to somehow keeping the view updated
ngOnInit() {
this.isCurrentUserExist = this.userService.isCurrentUserExist();
}
For reference, inside my UserService.ts
export class UserService {
private currentUser: User
constructor(private http: Http,private angularFire: AngularFire) { }
getCurrentUser(): User {
return this.currentUser
}
setCurrentUser(user: User) {
this.currentUser = user;
}
isCurrentUserExist(): boolean {
if (this.currentUser) {
return true
}
return false
}
}
A bit more information about my app...
Upon start up when the user does not exist, I have a login screen (login component).
When the user logs in, it goes to firebase and grab the user information (async) and store it to my user service via
setCurrentUser(user: User)
So at this point, I like to update the button in my nav bar (which exists in app.component.html) and show the button.
What can I do to achieve this?
let's try this:
using BehaviorSubject
UserService.ts
import { Subject, BehaviorSubject} from 'rxjs';
export class UserService {
private currentUser: User;
public loggedIn: Subject = new BehaviorSubject<boolean>(false);
constructor(private http: Http,private angularFire: AngularFire) { }
getCurrentUser(): User {
return this.currentUser
}
setCurrentUser(user: User) { // this method must call when async process - grab firebase info - finished
this.currentUser = user;
this.loggedIn.next(true);
}
isCurrentUserExist(): boolean {
if (this.currentUser) {
return true
}
return false
}
}
app.component.ts
ngOnInit() {
this.userService.loggedIn.subscribe(response => this.isCurrentUserExist = response);
}
in app.component.ts you are assigned value from function once. So it will never change. To resolve this problem and to real time update use assign function instance of boolean variable this.isCurrentUserExist = this.userService.isCurrentUserExist;. And in view change change *ngIf expression as function isCurrentUserExist().