Angular State Management Between Components (Direct dependency) returning NullInjectorError - R3InjectorError

26 Views Asked by At

I'm encountering a NullInjectorError in my Angular application and need help resolving it. Here's the structure of my components:

I have three main components in my Angular app: ProductRegistrationAndListingScreen, ProductList, and ProductRegistration.

ProductRegistrationScreen is a parent component that contains the other two components. ProductList is responsible for displaying a list of products. ProductRegistration is used for adding new products. I'm trying to establish communication between ProductRegistration and ProductList without injecting additional services or dependencies directly into these components. I have a boolean variable in ProductRegistration that I want to use to trigger a refresh in ProductList.

enter image description here

Here's the relevant code:

ProductRegistrationAndListingScreen Component:

 export class ProductRegistrationAndListingScreen{

  isRegistrationMode: boolean = true;
  isEditMode: boolean = false; 

  constructor(private productList: ProductList, private productRegistration: ProductRegistration) {}

  ngOnInit() {
    this.initiateContinuousUpdate();
  }  

  toggleScreenMode() {
    this.isRegistrationMode = !this.isRegistrationMode;
    this.isEditMode = !this.isEditMode;
  }

  initiateContinuousUpdate() {
    setInterval(() => {
      const registrationFormActive = this.productRegistration.isFormActive();      

      if (registrationFormActive) {
        this.productList.loadProducts();
        this.productRegistration.setFormActive(false);
      }

    }, 1000); 
  }
}

ProductList Component:

export class ProductList implements OnInit {

  constructor(private productService: ProductService) {}

  ngOnInit() {
    this.loadProducts();  
  }
  
  productList: Product[] = [];
  shouldReloadForm: boolean = false;  

  setFormActive(active: boolean) {
    this.shouldReloadForm = active;
  }

  isFormActive(){
    return this.shouldReloadForm;
  }

  loadProducts() {
    const path = 'products';
    this.productService.get<Product[]>(path).subscribe(
      (products) => {
        this.productList = products;
      },
      (error) => {
        console.error('Error loading products:', error);
      }
    );
  }
}

ProductRegistration Component:

export class ProductRegistration {

  constructor(private productService: ProductService) {}

  productList: Product[] = [];
  isFormActive: boolean = false;

  product: Product = {
    id: 0,
    code: '',
    name: '',
    purchasePrice: 0,
    brand: '',
    manufacturer: ''
  };  

  isFormActive()
  {
    return this.isFormActive;
  }

  setFormActive(value : boolean)
  {
    this.isFormActive = value;
  }

  toggleForm() {
    this.isFormActive = !this.isFormActive;
  }
}

It returns me this error:

core.mjs:10920 ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(AppModule)[ListaProdutoComponent -> ListaProdutoComponent]: 
  NullInjectorError: No provider for ListaProdutoComponent!
NullInjectorError: R3InjectorError(AppModule)[ListaProdutoComponent 

I don't wanna implement service in ProductRegistration or ProductList, like these approaches, cause both classes should be reused:

export class ListaProdutoComponent implements OnInit {
  constructor(private sharedService: SharedService) {}

ngOnInit() {
    this.sharedService.refreshEmitter.subscribe(() => {
....

export class SharedService {
  refreshEmitter: EventEmitter<void> = new EventEmitter<void>();

  triggerRefresh() {
    this.refreshEmitter.emit();
  }
2

There are 2 best solutions below

0
AudioBubble On

You need @Injectable annotation - check to see if its there

@Injectable({
  providedIn: 'root',
})
export class XXXService {

 }
0
Randy On

Normally you don't inject components like you seem to want to. You inject services via the constructor. If you want to say have a Abc component and a AbcList component that has a list of Abc's... you would ensure they are declared or imported into the current module file in Angular. Then you would use the Abc component's tag like <app-abc ... > in the template (html file) of AbcList, thus composing the aggregate (usually with a looping directive like *ngFor). Further, the AbcList, could get the list, then in the template pass in the data on Abc as an @Input() parameter.

You may want to checkout the Heros tutorial: https://angular.io/tutorial/tour-of-heroes

If you really want to connect to components content, see this: https://blog.angular-university.io/angular-viewchild/