2

I work with Angular 11 Universal - server side rendering. I'm trying to implement Bootstrap 5 toasts (css works well), but it doesn't understand class new bootstrap: enter image description here

angular.json - it's imported properly

            "styles": [
              "src/styles.scss",
              "node_modules/bootstrap/dist/css/bootstrap.min.css"
            ],
            "scripts": [
              "node_modules/@popperjs/core/dist/umd/popper.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.js"
            ]

package.json

 "@angular/platform-browser": "~11.2.7",
    "@angular/platform-browser-dynamic": "~11.2.7",
    "@angular/platform-server": "~11.2.7",
    "@angular/router": "~11.2.7",
    "@nguniversal/express-engine": "^11.2.1",
    "@popperjs/core": "^2.9.2",
    "bootstrap": "^5.0.0-beta3",
    "express": "^4.15.2",
    "popper.js": "^1.16.1",

I was trying to implement toasts with initial JS code:

import {
  AfterViewInit,
  Component,
  ElementRef,
  EventEmitter,
  Inject,
  Input,
  OnInit,
  Output,
  PLATFORM_ID,
  ViewChild
} from '@angular/core';
import { Toast } from '../../../../../node_modules/bootstrap/dist/js/bootstrap.min.js'
import {isPlatformBrowser} from "@angular/common";

@Component({
  selector: 'app-toast',
  templateUrl: './toast.component.html',
  styleUrls: ['./toast.component.scss']
})
export class ToastComponent implements OnInit, AfterViewInit {
  @Output() closeHit: EventEmitter<boolean> = new EventEmitter<boolean>();
  // @Input() title: string = "Toast";
  @Input() message: string = 'Enter message here';



  @ViewChild('toast') toast: ElementRef<HTMLDivElement>

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {

    if (isPlatformBrowser(this.platformId)) {

      // var toastElList = [].slice.call(document.querySelectorAll('.toast'))
      // var toastList = toastElList.map(function (toastEl) {
        return new bootstrap.Toast(this.toast, {})
      // })


      new Toast(this.toast);

      // Array.from(document.querySelectorAll('.toast'))
      //   .forEach(toastNode => new Toast(toastNode))
    }
  }

  ngOnInit(): void {

  }

  ngAfterViewInit() {
  }

}

But it don't understand class bootstrap - in new bootstrap TS2304: Cannot find name 'bootstrap'.

2 variant with importing toast directly from bootstrap.js is breaking the app new Toast(this.toast);

ReferenceError: document is not defined A server error has occurred. node exited with 1 code. connect ECONNREFUSED 127.0.0.1:62043 npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! client@0.0.0 dev:ssr: ng run client:serve-ssr npm ERR! Exit status 1

Please, help! Is there any way to use Bootstrap 5 functionality for toasts, modals in Angular Universal?

Tatyana Molchanova
  • 1,305
  • 5
  • 12
  • 23

5 Answers5

3

Add the bootstrap/Types

npm i @types/bootstrap

Then in your component import Toast

import {Toast} from 'bootstrap'

Use a template reference for your toast

<div #myToast role="alert" aria-live="assertive" aria-atomic="true" class="toast fade" data-bs-autohide="false">
  <div class="toast-header">
    <img src="..." class="rounded me-2" alt="...">
    <strong class="me-auto">Bootstrap</strong>
    <small>11 mins ago</small>
    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
  </div>
  <div class="toast-body">
    Hello, world! This is a toast message.
  </div>
</div>

And use ViewChild with static true to create the Toast element in ngOnInit

  @ViewChild('myToast',{static:true}) toastEl: any
  isClosed(){
    return !this.toastEl.nativeElement.classList.contains('show')
  }
  toast:any
  ngOnInit()
  {
    this.toast=new Toast(this.toastEl.nativeElement,{})
  }

Then use toast.show() or toast.hide()

see the stackblitz

NOTE: You can also use librarys that has toast component, e.g. ng-bootstrap (I put this because is closer to bootstrap and is "pure" Angular

Update We can improve the code using a directive

If we create a directive like

import {Directive,ElementRef} from '@angular/core'
import {Toast} from 'bootstrap'
@Directive({
  selector: '.toast', //<--(1)
})
export class ToastDirective {
  toast:any;
  isClosed(){
    return !this.el.nativeElement.classList.contains('show')
  }
  constructor(private el:ElementRef){
    this.toast=new Toast(this.el.nativeElement)
  }
  toogle()
  {
    if (this.isClosed())
      this.toast.show();
    else
      this.toast.hide();
  }
  hide(){
    this.toast.hide();
  }
  show(){
    this.toast.show();
  }
}

(1) make that the directive is applied to all the div with class="toast".

We has all "encapsulated", now if we has a toast

<div #myToast role="alert" aria-live="assertive" aria-atomic="true" class="toast fade" data-bs-autohide="false">
...
</div>

ViewChild is now

 @ViewChild('myToast',{static:true,read:ToastDirective}) toast: ToastDirective

And we can do, e.g.

<button class="btn btn-primary" (click)="toast.toogle()">toogle</button>

I create another stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
2

Angular 12 & Bootstrap 5 .TS

@ViewChild('myToast',{static:true}) toastEl!: ElementRef<HTMLDivElement>;
toast: Toast | null = null;

ngOnInit()
{
     this.toast = new Toast(this.toastEl.nativeElement,{});
}

show(){
  this.toast!.show();
}

HTML

<button type="button" class="btn btn-primary" id="liveToastBtn" 
(click)="show()">Show live toast</button>

 <div #myToast role="alert" aria-live="assertive" aria-atomic="true" 
 class="toast fade" data-bs-autohide="false">
 <div class="toast-header">
 <img src="..." class="rounded me-2" alt="...">
 <strong class="me-auto">Bootstrap</strong>
 <small>11 mins ago</small>
 <button type="button" class="btn-close" data-bs-dismiss="toast" aria- 
 label="Close"></button>
  </div>
  <div class="toast-body">
  Hello, world! This is a toast message.
 </div>
 </div>
May Alvarado
  • 111
  • 1
  • 4
2
npm install bootstrap
npm install @types/bootstrap


code...
const Bootstrap = await import('bootstrap');
const Modal = new Bootstrap.Modal(...element..., ...options...);

angular.json
"build": {
    ...,
    "options": {
        ...,    
        "allowedCommonJsDependencies": [
            "bootstrap"
        ],
        ...
    },
    ...
}

Toast try like this

0

Error npm ERR! Failed at the client@0.0.0 dev:ssr script - it's not rendering on server as I understand, bootstrap needs DOM, but this is just server side.

The app is crashed:
E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:79069
  EventHandler.on(document, EVENT_CLICK_DATA_API$7, SELECTOR_DISMISS, Alert.handleDismiss(new Alert()));
                  ^

ReferenceError: document is not defined
    at E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:79069:19
    at E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:78335:28
    at Object.SYky (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:78337:2)
    at __webpack_require__ (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:26:30)
    at Module.Mvz9 (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:71515:67)
    at __webpack_require__ (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:26:30)
    at Module.PCNd (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:74232:91)
    at __webpack_require__ (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:26:30)
    at Module.ZAI4 (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:90421:79)
    at __webpack_require__ (E:\PRACTICE\MYPETPROJECTS\tanechka\client\dist\client\server\main.js:26:30)

A server error has occurred.
node exited with 1 code.
connect ECONNREFUSED 127.0.0.1:60499
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! client@0.0.0 dev:ssr: `ng run client:serve-ssr`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the client@0.0.0 dev:ssr script

enter image description here

enter image description here

enter image description here

enter image description here

Tatyana Molchanova
  • 1,305
  • 5
  • 12
  • 23
0

Single right decision for me was to refuse from using Bootstrap in Angular Universal and replacing it with Angular Material SnackBar and utilities.

https://material.angular.io/components/snack-bar/examples

This line of code

this.toast = new Toast(this.toastEl.nativeElement,{});
  • calling new Toast - it didn't work in Universal Angular for how I was trying to implement it in any ways.
Tatyana Molchanova
  • 1,305
  • 5
  • 12
  • 23