ORIGINAL QUESTION:
<div *ngIf="viewOrdersClientId || (selectedPatient | async as patient)">
<wss-patient-detail-component
[patient]="patient"
[patientId]="viewOrdersClientId">
</wss-patient-detail-component>
</div>
When I do that I get this error :
Property 'patient' does not exist on type 'PatientComponent'
Is there some way I can achieve having only one wss-patient-detail-component instead of my current approach ?
ANSWER:
You are calling an object or string named patient - from the parent which is called PatientComponent. The component is unaware of this variable. Either you did not declare it on PatientComponent or the ngIf statement is not allowing the data to pass through if its false because it discards the component instruction - however the component is still rendered without an answer from the parent, as it was ignored.
POSSIBLE FIXES:
NG TEMPLATE WAY
ng-template for this to give an alternative template to render the answer if there is no component instance, which could be a simple open and close div with no content.
<ng-container *ngIf="viewOrdersClientId || (selectedPatient | async as patient); else falseTemplate">
<wss-patient-detail-component
[patient]="patient"
[patientId]="viewOrdersClientId">
</wss-patient-detail-component>
</ng-container>
<ng-template #falseTemplate>
<div></div>
</ng-template>
NG CONTAINER WAY
ng container waits for the instruction before rendering the component as the html div element does not - it process the html in first order and javascript in second order.
<ng-container *ngIf="viewOrdersClientId || (selectedPatient | async as patient)">
<wss-patient-detail-component
[patient]="patient"
[patientId]="viewOrdersClientId">
</wss-patient-detail-component>
</ng-container>
NG CLASS WAY
The other way to accomplish your goal is with [ngClass] div is to just show or hide based upon your if - this is the cleanest way out of all of them and it fixes your variable issue. *ngIf statements run in order of the rendered viewport and without an alternative template you get stuck with few choices to fix the problem.
<div [ngClass]="viewOrdersClientId || (selectedPatient | async as patient) ? 'show':'hide'">
<wss-patient-detail-component
[patient]="patient"
[patientId]="viewOrdersClientId">
</wss-patient-detail-component>
</div>
on your patient.component.scss or .css the fix is to just use simple css to hide it or show it, set the height to 0px on hide and 100% on show.
.show{
visibility: visible;
height: 100%;
}
.hide{
visibility: hidden;
height: 0px;
}
DIRECTIVE WAY
from cli
ng g d patientdetail
on the module where you import the wss-patient-detail-component add this after exports:[]
exports:[wss-patient-detail-component]
entryComponents: [wss-patient-detail-component]
import {
Directive, ViewContainerRef, ChangeDetectorRef,
ComponentFactoryResolver,
Input
} from '@angular/core';
import { from, Observable } from 'rxjs';
import { WssPatientDetailComponent } from './YOUR PATH/wsspatientdetailcomponent.component';
@Directive({
selector: '[elPatientDetail]'
})
export class PatientDetailDirective{
@Input() patient?: any;
@Input() patientId: any;
elRef: any;
alias: string;
readonly templateMapper: any = {
patientdetail: WssPatientDetailComponent
}
constructor( private cdr: ChangeDetectorRef,
private factory: ComponentFactoryResolver,
public viewContainerRef: ViewContainerRef) {
this.elRef = viewContainerRef;
}
async ngOnInit(): Promise<void> {
console.log(await this.viewportLayout)
// console.log('template directive started');
this.buildViewPort();
}
async buildViewPort(): Promise<void> {
this.elRef.clear();
const componentFactory = this.factory.resolveComponentFactory(
this.getComponentByAlias(this.viewportLayout as unknown as string)
);
/** send data through the dom to the newly created component this in theory makes the component "reactive" */
const instance = this.elRef.createComponent(componentFactory, 0).instance;
// tslint:disable-next-line: max-line-length
instance.patient = this.patient;
instance.patientId = this.patientId
// console.log(newcomponent);
this.cdr.detectChanges();
}
// }
// tslint:disable-next-line: typedef
private getComponentByAlias(alias: string) {
// connects directly to the templateMapper and allows us to import the components we want at this time
console.log('alias', alias);
return this.templateMapper[alias];
}
}
}
on the parent component after you import the directive to that same module. the data is passed through to the component via an instance which allows it to stay as an async.
on the parent component
<div *ngIf="viewOrdersClientId ||
(selectedPatient | async as patient)"
[patient]="patient"
[patientId]="viewOrdersClientId"
elPatientDetail></div>