How can I access ngOffline directive in a component instead of html - npm

I'm using this npm library https://www.npmjs.com/package/ng-offline to alert end user when offline.
<div class="alert alert-danger" ngOffline>You're offline. Check your connection!</div>
stackblitz here: https://stackblitz.com/edit/angular-ngoffline-npm?file=src%2Fapp%2Fapp.component.html
Works great - BUT I want to open a modal with this ngOffline directive, so I'm trying to access the directive from my angular 11 component but not sure how to approach this, any help on this would be greatly appreciated.
Is there away for me to open a ngx-bootstrap modal from the html with this directive?

Because the ng-offline module isn't exporting things as you might expect (i.e. you can't inject a standalone NgOfflineDirective for you to use without having it in your html file), you could add a block like this (where you've used #trigger to identify your ngOnline element):
import { AfterContentChecked, Component, ElementRef, OnDestroy, ViewChild } from '#angular/core';
import { BehaviorSubject, Subscription } from 'rxjs';
import { distinctUntilChanged, filter } from 'rxjs/operators';
#Component({ ... })
export class YourClass implements AfterContentChecked, OnDestroy {
offline$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>();
subscription: Subscription;
#ViewChild('trigger') trigger: ElementRef;
constructor() {
this.subscription = this.offline$.pipe(
distinctUntilChanged(),
filter((offline: boolean) => offline),
).subscribe(() => this.showModal());
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
ngAfterContentChecked() {
if (
this.trigger &&
this.trigger.nativeElement
) {
this.offline$.next(this.trigger.nativeElement.style.display === "none");
}
}
showModal() {
console.log('Show your modal here.');
}
}

Related

How to create directive for disable input in reactive form with Angular 8^?

The old directive from angular 6 doesn't working more.
(I need directive and not sample code in the component because of the need for dynamism that changes during the run.
this is the old directive code:
import { Directive, Input } from "#angular/core";
import { NgControl } from "#angular/forms";
#Directive({
selector: "([formControlName], [formControl])[disabledControl]"
})
export class DisabledControlDirective {
#Input() set disabledControl(state: boolean) {
const action = state ? "disable" : "enable";
this.ngControl.control[action]();
}
constructor(private readonly ngControl: NgControl) {}
}

How to access shadowDom when testing Lit element with open-wc

Lit docs refer to Web Test Runner as testing. It navigates to this example page.
I tried testing MyElement, which has only one <p>.
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
#customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`<p>Hello, World.</p>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"my-element": MyElement;
}
}
When testing by open-wc, the element's shadowDom did not have <p> in descendant.
import { expect, fixture, html } from "#open-wc/testing";
import { MyElement } from "../src/MyElement";
it("get shadowDom", async () => {
const el: MyElement = await fixture(html`<my-element></my-element>`);
expect(el).shadowDom.to.be.not.null; // passed
expect(el).shadowDom.to.have.descendant("p"); // failed
});
Does it need more setup to test Lit elements with open-wc?
web-test-runner.config.js is:
import { esbuildPlugin } from '#web/dev-server-esbuild';
export default {
files: ['test/*.test.ts'],
plugins: [esbuildPlugin({ ts: true })],
};
Try shadowRoot instead of shadowDom:
it("get shadowDom", async () => {
const el = await fixture(
html` <my-element></my-element>`
);
const descendant = el.shadowRoot!.querySelector("p")!;
expect(descendant).to.be.not.null;
});
I had similar issue. In my case shadowRoot was "null". To have shadowRoot content I had to import my web component like that:
import './myWebcomponent';

Vue.js + Bootstrap - question - invoking $bvToast from store.js

I have probably very profoundly simple question: how to invoke methods located in components from store.js, for instance I want a toast popup from store mutation. Any ideas?
The $bvToast can be found in BToast export. You can import it to use it.
import { BToast } from 'bootstrap-vue'
Example Class
import { BToast } from 'bootstrap-vue'
class uiUtils
{
showToast(message : string,title : string){
let bootStrapToaster = new BToast();
bootStrapToaster.$bvToast.toast(message, {
title: title,
toaster: "b-toaster-top-right",
solid: true,
appendToast: false
})
}
}
export default new uiUtils();
The documentation does show the individual component exports at the bottom of the page and can be found here Bootstrap Vue Documentation.

Ionic 4 Not support Events?

Im using Event fucntion to publish some data in app. But its not working in ionic 4. I need to know ionic 4 support Events or not?
import { Events } from '#ionic-angular';
// Module not found: Error: Can't resolve '#ionic-angular'
You can use #angular/Events
//MyEvents Service Page
import { Injectable } from '#angular/core';
import { Subject, Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class EventsService {
constructor() { }
private subject = new Subject<any>();
sendMessage(text){
this.subject.next(text);
}
getMessage():Observable<any>{
return this.subject.asObservable();
}
}
//Page for sendMessage
constructor(private events: EventsService) {
this.events.sendMessage({'created':1}); //send message key-value format
}
//Page for getMessage
subscription: Subscription;
constructor(private events: EventsService) {
this.subscription = this.events.getMessage().subscribe(text => {
console.log(text.created);
})
}
Below solution is working in ionic v4
import { Events } from '#ionic/angular';
constructor(private events: Events) {
events.subscribe('notificationLength', notilen => {
//TO DO`enter code here`
})
}
// Publish the events where ever you want
this.events.publish('notificationLength', this.NotificationList.length)
The problem was in the version. When I updated to the latest patch of version 4, it was working.
npm i #ionic/angular#4.11.10

boostrap-multiselect plugin in Aurelia

I am trying to get bootstrap-multiselect to work with Aurelia. have got it working more or less but not sure it is the best solution or if I might run into trouble.
Bootstrap-multiselect is a jquery plugin that turns a normal select (multi) into a drop down with checkboxes (http://davidstutz.github.io/bootstrap-multiselect/)
My first problem is to get it working with dynamically created options. I solved that by using the plugins "rebuild" feature when my array of options (created as a bindable property) changes. However the options of the original select hhas then not yet been created so I use setTimeout to delay rebuilding so Aurelia have rebuilt the select. Feels like a "dirty" solution and I know to little about the Aurelia lifecyle to be sure it will always work.
Second problem is that value for component will not be updated, however the change method will fire. I solved this by firing off a change event (found an example for some other plugin that do the same). Works fine, value wiill be updated but the change method will fire twice. Not a big problem but might be a problem if a change does some time consuming work (like getting data from a database etc).
Any suggestions to improve code ?
<template>
<select value.bind="value" multiple="multiple">
<option repeat.for="option of options"Value.bind="option.value">${option.label}</option>
</select>
</template>
import {customElement, bindable, inject} from 'aurelia-framework';
import 'jquery';
import 'bootstrap';
import 'davidstutz/bootstrap-multiselect';
#inject(Element)
export class MultiSelect {
#bindable value: any;
#bindable options: {};
#bindable config: {};
constructor(private element) {
this.element = element;
}
optionsChanged(newVal: any, oldVal: any) {
setTimeout(this.rebuild, 0);
}
attached() {
var selElement = $(this.element).find('select');
selElement.multiselect(
{
includeSelectAllOption: true,
selectAllText: "(All)",
selectAllNumber: false,
numberDisplayed: 1,
buttonWidth: "100%"
})
.on('change', (event) => {
if (event.originalEvent) { return; }
var notice = new Event('change', { bubbles: true });
selElement[0].dispatchEvent(notice);
});
}
detached() {
$(this.element).find('select').multiselect('destroy');
}
rebuild = () => {
$(this.element).find('select').multiselect('rebuild');
}
}
Your first problem could be solved by pushing the $(this.element).find('select').multiselect('rebuild'); onto the microTaskQueue, inside the optionsChanged() handler. In this way, Aurelia will fire this event after rendering the new options.
Your second problem is not actually a problem. What is happening is that #bindable properties are one-way by default. You should declare the value property as two-way. Then, you should update the value inside the multiselect.change event.
Finally, your custom element should be something like this:
import {inject, bindable, bindingMode, TaskQueue} from 'aurelia-framework';
#inject(TaskQueue)
export class MultiselectCustomElement {
#bindable options;
#bindable({ defaultBindingMode: bindingMode.twoWay }) value = [];
constructor(taskQueue) {
this.taskQueue = taskQueue;
}
attached() {
$(this.select).multiselect({
onChange: (option, checked) => {
if (checked) {
this.value.push(option[0].value);
} else {
let index = this.value.indexOf(option[0].value);
this.value.splice(index, 1);
}
}
});
}
optionsChanged(newValue, oldValue) {
if (oldValue) {
this.taskQueue.queueTask(() => {
this.value = [];
$(this.select).multiselect('rebuild');
});
}
}
}
Running example: https://gist.run/?id=60d7435dc1aa66809e4dce68329f4dab
Hope this helps!