primefaces / primeng

The Most Complete Angular UI Component Library
https://primeng.org
Other
10.46k stars 4.6k forks source link

Notifications p-toast do not work with Angular 16 standalone (no ngModule) app #13529

Open morsagmon opened 1 year ago

morsagmon commented 1 year ago

Describe the bug

Implementing a notification.component in Angular to serve notifications does not pop up the notifications. I suspect that the notifications implementation requires a single, app-wide, import of the MessageService in the app.module, however, as in a standalone Angular app there is no ngModule - it fails to serve the messages.

notification.component.html:

<p-toast key="success" position="top-right" [style]="{width: '30rem'}" [preventOpenDuplicates]="true">
    <ng-template let-message pTemplate="message">
        <div class="flex align-items-start flex-1">
            <i class="pi pi-check-circle text-green-900 text-2xl mr-3"></i>
            <div>
                <span class="text-xl font-medium text-green-900">{{message.summary}}</span>
                <p class="text-green-800 mt-3">{{message.detail}}</p>
            </div>
        </div>
    </ng-template>
</p-toast>

notification.component.ts:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { ButtonModule } from 'primeng/button';
import { RippleModule } from 'primeng/ripple';

@Component({
  selector: 'app-notification',
  standalone: true,
  imports: [CommonModule, ToastModule, ButtonModule, RippleModule],
  providers: [MessageService],
  templateUrl: './notification.component.html',
  styleUrls: ['./notification.component.scss']
})
export class NotificationComponent {

  constructor(private messageService: MessageService) {}

  success(title: string, description: string) {
    this.messageService.clear();
    this.messageService.add({
        key: 'success',
        severity: 'success',
        summary: title,
        detail: description,
        styleClass: 'bg-green-500',
        contentStyleClass: 'p-3',
        closable: false
    });
}
}

The caller component:

import { ChangeDetectorRef, Component, ElementRef, OnInit, Renderer2, ViewContainerRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet, RouterLinkWithHref } from '@angular/router';
import { ReactiveFormsModule, FormControl, Validators, NonNullableFormBuilder, FormGroup } from '@angular/forms';
import { RecaptchaFormsModule, RecaptchaModule  } from 'ng-recaptcha';
import { environment } from 'src/environments/environment';
import { PasswordValidator, PhoneValidator } from 'src/app/validators';
import { InstructionsVisible } from 'src/app/interfaces';
import { InstructionDirective } from 'src/app/directives';
import { StyleClassModule } from 'primeng/styleclass';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from 'primeng/button';
import { RippleModule } from 'primeng/ripple';
import { CalendarModule } from 'primeng/calendar';
import { DropdownModule } from 'primeng/dropdown';
import { InputNumberModule } from 'primeng/inputnumber';
import { FileUploadModule } from 'primeng/fileupload';
import { CheckboxModule } from 'primeng/checkbox';
import { InputSwitchModule } from 'primeng/inputswitch';
import { DividerModule } from 'primeng/divider';
import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
import { CountriesService, EmailService } from 'src/app/services';
import { userEmailValidator } from 'src/app/validators/email.validator';
import { VerificationCodeComponent } from 'src/app/components/dialogs/verification-code/verification-code.component';
import { NotificationComponent } from 'src/app/components/notification/notification.component';
import { MessageService } from 'primeng/api';

@Component({
  selector: 'app-signup',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, StyleClassModule, RouterOutlet, InstructionDirective,  RecaptchaModule,
    RecaptchaFormsModule, 
    RouterLinkWithHref, InputTextModule,
    ButtonModule,
    RippleModule,
    CalendarModule,
    DropdownModule,
    InputNumberModule,
    FileUploadModule,
    CheckboxModule,
    InputSwitchModule,    
    DividerModule,
    ],
  providers: [DialogService, DynamicDialogRef, MessageService, NotificationComponent],
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.scss'],    
})
export class SignupComponent implements OnInit {

  constructor(
    private countriesService: CountriesService, 
    private emailService: EmailService, 
    private fb: NonNullableFormBuilder, 
    public dialogService: DialogService, 
    public notification: NotificationComponent
    ) {  }

  ngOnInit() {    
    console.log('In sign-up page onInit');
    this.notification.success('Email Sent','Test notification here!');
  }
}

NOTE: Removing the MessageService from the providers array in this caller component throws a runtime error in the console:

main.ts:20 ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(Standalone[SignupComponent])[MessageService -> MessageService -> MessageService -> MessageService]: 
  NullInjectorError: No provider for MessageService!
NullInjectorError: R3InjectorError(Standalone[SignupComponent])[MessageService -> MessageService -> MessageService -> MessageService]: 
  NullInjectorError: No provider for MessageService!

Additional notes

I tried to add the MessageService to the maint.ts file - to no avail:

import 'zone.js';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from 'src/environments/environment';
import { enableProdMode, importProvidersFrom  } from '@angular/core';
import { 
  PreloadAllModules, 
  provideRouter, 
  withDebugTracing, 
  withPreloading, 
  withRouterConfig 
} 
from '@angular/router';
import { appConfig } from './app/app.config';

if (environment.production) {
  enableProdMode();
}

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

appConfig:

import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { 
  PreloadAllModules, 
  provideRouter, 
  withDebugTracing, 
  withPreloading, 
  withRouterConfig 
} 
from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { MessageService } from 'primeng/api';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes), provideHttpClient(), MessageService ]
};

Environment

Local dev env, vscode, Windows 10 machine.

Reproducer

https://stackblitz.com/edit/stackblitz-starters-sc2bnr?file=src%2Fapp%2Fpages%2Fsignup%2Fsignup.component.ts

https://stackblitz-starters-sc2bnr.stackblitz.io

Angular version

16.1.4

PrimeNG version

16.0.2

Build / Runtime

Angular CLI App

Language

TypeScript

Node version (for AoT issues node --version)

18.16.1

Browser(s)

No response

Steps to reproduce the behavior

  1. Create a standalone Angular app - no ngModules at all
  2. Create a notification component - with the code above in it
  3. Create a caller component (can use app.component, for example) - with the code as above
  4. ng serve

Expected behavior

The notification popup to be served in the browser to the user. The popup does not show and no errors are given.

morsagmon commented 1 year ago

I found a workaround to make this work! Instead of having the dedicated NotificationComponent, I have moved the presentation of the toast task to the AppComponent. If you ask me - it should either be fixed to be supported from any component, or be EMPHASIZED in the documentation that it will only work if implemented in the app-root component. This is true to any use of the p-toast of PrimeNG that uses the MessagesApi service.

ProfEibe commented 11 months ago

does also not work with p-messages. callings from a child-service or similar works, but in the standalone-component which provides the messageService, it can not be used.

ProfEibe commented 11 months ago

okay, short after complaining I found the problem.

You cannot use the message-service in ngOnInit() because the view is not build yet and the messageservice cannot write to it. Use ngAfterViewInit for example to call the messageservice.add() method.

Using the variable binding function is no problem in the OnInit Method because the message is stored in the variable and read there after the view is initialised.

ngAfterViewInit() {
    setTimeout(() => {
      this.messageService.add({
        key: 'success',
        severity: 'success',
        summary: title,
        detail: description,
        styleClass: 'bg-green-500',
        contentStyleClass: 'p-3',
        closable: false
      });
    });
  }

This will work.