Angular how to resolve "Circular dep for" error - circular-dependency

I´m making an angular library with 3 components :
FrameComponent (not public)
ApplicationComponent
SectionComponent
ApplicationComponent extends FrameComponent.
SectionComponent should extend FrameComponent as well BUT ApplicationComponent contains x sectionComponent(s).
So if SectionComponent extends FrameComponent, I got this error:
ERROR Error: "Circular dep for SectionComponent"
Angular 10
SectionComponent_Factory section.component.ts:14
Angular 5
AppComponent_Template app.component.html:2
Angular 20
Angular 17
The exaclty reason, it´s because in SectionComponent, I have a Provider to get parent attributes.
#Component({
selector: 'ui-section',
templateUrl: './section.component.html',
styleUrls: ['./section.component.scss'],
providers: [ provideParent( SectionComponent ) ]
})
export class SectionComponent extends FrameComponent implements OnInit, AfterViewInit {
...
constructor( elRef: ElementRef, #Optional() public parent: Parent){
}
}
if I comment /*#Optional() public parent: Parent*/ The error disappeared.
How can I do SectionComponent extend FrameComponent without circular dep error ?

The solution is #SkipSelf() for SectionComponent
export class SectionComponent extends FrameComponent implements OnInit, AfterViewInit {
...
constructor( elRef: ElementRef, #SkipSelf() #Optional() public parent?: FrameComponent ) {
}
}

Related

I get undefined property when I share boolean data between Angular components using #ViewChild

I have a component named RedirectUserToMobileAppComponent , I want to share a boolean property from it named enableLoginForm with app.component.
When I execute, I get this error :
enableLoginForm is undefined property on ngAfterViewInit in app.component
this is RedirectUserToMobileAppComponent component:
import {
Component,
ComponentFactoryResolver,
ComponentRef,
Inject,
Input,
OnInit,
Output,
ViewChild,
ViewContainerRef,
} from '#angular/core';
import { Observable, Subscription } from 'rxjs';
import { filter, map, pluck, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '#angular/router';
import { MAT_DIALOG_SCROLL_STRATEGY_FACTORY } from '#angular/material/dialog';
#Component({
selector: 'redirect-user-to-mobile-app',
templateUrl: './redirect-user-to-mobile-app.component.html',
styleUrls: ['./redirect-user-to-mobile-app.component.sass'],
})
export class RedirectUserToMobileAppComponent implements OnInit {
constructor(
) {}
enableLoginForm = false;
ngOnInit(): void {}
OnLogin(): void {
this.enableLoginForm = true;
this.router.navigate(['../login']);
}
}
and this is app.component:
import {
Component,
HostListener,
OnDestroy,
OnInit,
ViewChild,
AfterViewInit,
} from '#angular/core';
import { MatIconRegistry } from '#angular/material/icon';
import { DomSanitizer } from '#angular/platform-browser';
import { FirebaseService } from './services/firebase/firebase.service';
import {
SnakeMessage,
SnakeMessageService,
} from './services/snakeMessage/snakeMessage.service';
import { MatSnackBar } from '#angular/material/snack-bar';
import { Subscription } from 'rxjs';
import { StorageService } from './services/storage/storage.service';
import { AuthService } from './services/auth/auth.service';
import { RedirectUserToMobileAppComponent } from './redirect-user-to-mobile-app/redirect-user-to-mobile-app.component';
#Component({
selector: 'app-component',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
favIcon: HTMLLinkElement = document.querySelector('#appIcon');
private snakeMessageSub: Subscription;
isLoading = true;
isLogged: boolean;
#ViewChild(RedirectUserToMobileAppComponent)
redirectComponent!: RedirectUserToMobileAppComponent;
constructor(
private matIconRegistry: MatIconRegistry,
private firebaseService: FirebaseService,
private snakeMessageService: SnakeMessageService,
private _snackBar: MatSnackBar,
private storageService: StorageService,
private domSanitizer: DomSanitizer,
private authService: AuthService
) {
this.registerCustomIcons();
this.storageService.initDB();
this.storageService.onLoaded$.subscribe((loaded) => {
if (loaded) {
this.isLoading = false;
}
});
this.isLogged = this.authService.isLoggedIn;
}
ngAfterViewInit() {
if (this.redirectComponent.enableLoginForm) {
this._is = this.redirectComponent.enableLoginForm;
}
}
ngOnInit(): void {
this.snakeMessageSub = this.snakeMessageService.messageSub.subscribe(
(snakeMessage: SnakeMessage) => {
this._snackBar.open(snakeMessage.message, snakeMessage.action, {
duration: 3000,
horizontalPosition: 'center',
verticalPosition: 'top',
});
}
);
}
this is my app.component.html
<ng-container *ngIf="!isLoading">
<ng-container *ngIf="isMobileDevice() && !isLogged">
<redirect-user-to-mobile-app> </redirect-user-to-mobile-app>
<router-outlet
*ngIf="enableLoginForm"
></router-outlet>
</ng-container>
<router-outlet *ngIf="!isMobileDevice()"></router-outlet>
This is how you use ViewChild:
#ViewChild('templateId', { static: false }) redirectComponent: RedirectUserToMobileAppComponent;
You should have the templateId set in the template part :
<redirect-user-to-mobile-app #templateId> ... </redirect-user-to-mobile-app>
EDIT: Though I agree with skyBlue, you should use a service to shared data between components
ViewChild returns a reference to the HTML element.
I will quote from angular.io:
Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.
So you cant access it's controller variables with ViewChild.
My suggestion for you is to use a service for passing data.
I have changed the method, I have used #Output() component and it works fine:
this is RedirectUserToMobileAppComponent component after changing the method :
import {
Component,
ComponentFactoryResolver,
ComponentRef,
Inject,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
ViewContainerRef,
EventEmitter,
} from '#angular/core';
import { Observable, Subscription } from 'rxjs';
import { filter, map, pluck, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '#angular/router';
import { MAT_DIALOG_SCROLL_STRATEGY_FACTORY } from '#angular/material/dialog';
#Component({
selector: 'yobi-redirect-user-to-mobile-app',
templateUrl: './redirect-user-to-mobile-app.component.html',
styleUrls: ['./redirect-user-to-mobile-app.component.sass'],
})
export class RedirectUserToMobileAppComponent implements OnInit {
constructor(
private router: Router,
private componentFactoryResolver: ComponentFactoryResolver
) {}
#Output() _enableLoginForm: EventEmitter<boolean> = new EventEmitter();
variable: any;
login: boolean;
enableLoginForm = false;
enableSignupForm = false;
ngOnInit(): void {}
sendDataToParent() {
this.enableLoginForm = true;
this._enableLoginForm.emit(this.enableLoginForm);
console.log(this.enableLoginForm + ' From redirect ');
}
I added this to RedirectUserToMobileAppComponent.html:
<a class="login-text" (click)="sendDataToParent()">
Login
</a>
I added this code to app.component :
receiveChildData($event) {
this.enableLoginForm = $event;
}
I added this code to the app.component.html :
<redirect-user-to-mobile-app
(_enableLoginForm)="receiveChildData($event)"
>
</redirect-user-to-mobile-app>

Subscribe a local variable Value to an Observable of variable in another Component in Angular

I want to change an HTML view via *ngIf, based on a local variable, which should change based on a variable delivered through an observable from a shared service.
HTML
<div class="login-container" *ngIf="!isAuthenticated">
TypeScript of same component:
export class LoginComponent implements OnInit {
authenticationsSubscription;
isAuthenticated: boolean;
constructor(
private authService: AuthServiceService,
private router: Router,
private route: ActivatedRoute){}
getAuth(): Observable<boolean>{
return this.authService.validation();
}
ngOnInit() {
this.authenticationsSubscription = this.authService.validation().subscribe(auth => this.isAuthenticated = auth);
}
}
TypeScript of shared service AuthService:
export class AuthServiceService {
isAuthenticated: boolean;
validation(): Observable<boolean>{
return of(this.isAuthenticated);
}
}
While debugging I found out, the variable isAuthenticated in the LoginComponent does not change, on changes of the variable isAuthenticated of the AuthService. I also tried using pipe() and tap(), which did not change anything.
What am I doing wrong?
Convert your AuthServiceService to have the authentication state as a BehaviorSubject and return it as Observable as described below.
import { Observable, BehaviorSubject } from "rxjs";
export class AuthServiceService {
private isAuthenticatedSub: BehaviorSubject<boolean> = new BehaviorSubject(false);
set isAuthenticated(isAuthenticated: boolean) {
this.isAuthenticatedSub.next(isAuthenticated);
}
get isAuthenticated(): boolean {
return this.isAuthenticatedSub.value;
}
validation(): Observable<boolean> {
return this.isAuthenticatedSub.asObservable();
}
}
The actual subscription of your observable will only happens once, when the OnInit lifecycle hook is triggered when the component is initialized.
You can subscribe to a BehaviorSubject in order to catch value changes.
Stackblitz example
AuthService
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class AuthService {
isAuthenticated: BehaviorSubject<boolean>;
constructor() {
this.isAuthenticated = new BehaviorSubject<boolean>(false);
}
}
Component
import { Component, OnInit } from '#angular/core';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
isAuthenticated: Observable<boolean>;
constructor(private authService: AuthService) {}
ngOnInit() {
this.isAuthenticated = this.authService.isAuthenticated;
}
login() {
this.authService.isAuthenticated.next(true);
}
logout() {
this.authService.isAuthenticated.next(false);
}
}
Template
<div *ngIf="isAuthenticated | async; else notAuthenticated">
User is authenticated
</div>
<ng-template #notAuthenticated>
<div>User isn't authenticated</div>
</ng-template>
<button (click)="login()">Login</button>
<button (click)="logout()">Logout</button>

null parameters from angular

I am trying to send parameters from my angular app using httpRequest.
I am getting back Null to my backend server.
I have checked with Postman and Fiddler both work with a json Object.
I have tried changing from Post to Get.
I am using Java RestAPI for the backend with apache Tomcat as the server.
This is my Service for login:
#Injectable({
providedIn: 'root'
})
export class LoginService {
private loginURL='http://localhost:8080/CouponSystemWeb/rest/loginpage/login'
constructor(private http:HttpClient) { }
public login(loginDetailes:LoginDetailes):Observable<LoginDetailes>{
return this.http.post<LoginDetailes>(this.loginURL,loginDetailes,{withCredentials:true})
}
}
This is my Login Component:
import { Component, OnInit } from '#angular/core';
import { LoginDetailes } from 'src/app/Entities/LoginDetailes';
import { LoginService } from 'src/app/services/login.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
public loggedin:boolean;
public loggedClient:string;
public errormessage:string;
public loginDetailes = new LoginDetailes();
constructor(private loginservice:LoginService,private router:Router) { }
ngOnInit() {
this.loggedin=false;
}
public onLogin():void{
const observable=this.loginservice.login(this.loginDetailes);
observable.subscribe((returnedLoginDetailes:LoginDetailes)=>{
alert("Login Aquired");
this.loggedin=true;
if(this.loginDetailes.clientType=="ADMIN"){
this.router.navigate(['/crtComp']);
}
else if(this.loginDetailes.clientType=="COMPANY"){
this.router.navigate(['/login']);
}
else if(this.loginDetailes.clientType=="CUSTOMER"){
this.router.navigate(['/login']);
}else{
alert("Wrong Login Detailes");
}
}, err => {
this.errormessage=err.console.error("Wrong Detailes please Check Again!");
alert(this.errormessage);
}
)}}
This is the login Entity :
export class LoginDetailes{
public name:string
public password:string
public clientType:string
constructor(){
}
}
I have tried ngModel but that didn't fix the problem.
I have tried changing my backend from Post to Get.
The problem happends only in the angular App. I can send parameters with fiddler and Postman without problem.
Ok the answer was not in the component or the service.
the problem was in the HTML i was missing the ngModel two way data binding so my App was sending null's.

Angular 6 AOT issue - can't pass function as argument

I'm unable to pass a function as an argument to a base class from a child class when trying to build using ng build --prod. The build works fine without the --prod flag, which looks to indicate an issue with AOT. The error I get is:
ERROR in : Can't resolve all parameters for AppGridComponent in
/src/app/components/core/shared/app-grid.component.ts: (?, [object
Object])
I found this SO thread which has several different answers for solutions, but I haven't been able to get any to work. It appears that AOT wants to inject this argument as a service and can't resolve (which I don't need since I am passing the function as a value from the child).
Base Class - app-grid.component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { GlobalsService } from '../../../services/globals.service';
#Component({})
export class AppGridComponent implements OnInit, OnDestroy {
constructor(protected loadDataCallBack: any, protected globalsService: GlobalsService) {
}
ngOnInit() {
this.init();
}
// get api data
public init() {
this.loadDataCallBack()
.subscribe(result => result);
}
Child class - rules.component.ts
const loadApiData = function() {
return this.productRuleService.get();
};
#Component({
selector: 'app-rules',
template: `<div class="grid-wrapper">Data here...</div>`
})
export class RulesComponent extends AppGridComponent implements OnInit, OnDestroy {
constructor(protected globalsService: GlobalsService, protected productRuleService: ProductRelationshipRuleService) {
super(loadApiData, globalsService);
}
Any suggestions on how to get this to build would be appreciated.
I was able to get this to work by creating a class that extends Function and then using this class as the provider in the component.
// Base Class - app-grid.component.ts
export class LoadDataCallBack extends Function {
}
#Component({
template: '',
providers: [{provide: LoadDataCallBack, useValue: () => {}}]
})
This ultimately satisfied the compiler and it is able to identify the type to inject into the first argument: loadDataCallBack.

Angular 2 Dialog won`t open

Dear Stackoverflow community,
I have a problem with the angular2 Dialog, befor i give you the error here is a snipet of my Code(i have exluced a lot, but i think they might not be important here)
signup.component.ts file:
import {Component, OnInit, NgZone, SimpleChanges} from '#angular/core';
import {MdDialog} from '#angular/material';
import template from './signup.component.html';
#Component({
selector: 'signup',
template
})
export class SignupComponent implements OnInit {
constructor(private router: Router, private zone: NgZone, private formBuilder: FormBuilder, public dialog: MdDialog) {
}
openDialog() {
this.dialog.open(DialogOverviewExampleDialog);
}
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: './dialog.html',
})
export class DialogOverviewExampleDialog {}
signup.component.html file:
<h3 flex>Just<a (click)="openDialog()">Click here</a></h3>
and the dialog.html:
<p>pls work</p>
i get this error on the console in Chrome:
No component factory found for DialogOverviewExampleDialog
The DialogOverviewExampleDialog component must be declared in the entryComponents field of your NgModule. Otherwise, the angular compiler doesn't know that this component is displayed somewhere (since its selector isn't used in any template), and it's thus not compiled.