Use ng-multiselect-dropdown in a component instead of app.module.ts - node.js

I am trying to use the ng-multiselect-dropdown component in another component/page (viz. registered through RouterModule.forRoot in app.mudule.ts).
I have imported it in my component as suggested in this link-
import { Component, OnInit, NgModule, NO_ERRORS_SCHEMA } from
'#angular/core';
import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown';
import { FormsModule } from '#angular/forms';
#Component({
selector: "ng-multiselect-dropdown",
templateUrl: "./multiselect.component.html"
})
#NgModule({
imports: [
NgMultiSelectDropDownModule.forRoot(),
FormsModule
],
schemas: [NO_ERRORS_SCHEMA]
})
export class MultiSelectComponent implements OnInit {
//constructor(private activatedRoute: ActivatedRoute,
// private router: Router,
// private http: HttpClient,
// #Inject('BASE_URL') private baseUrl: string) {}
dropdownList = [];
selectedItems = [];
dropdownSettings = {};
ngOnInit() {
this.dropdownList = [
{ item_id: 1, item_text: 'Mumbai' },
{ item_id: 2, item_text: 'Bangaluru' },
{ item_id: 3, item_text: 'Pune' },
{ item_id: 4, item_text: 'Navsari' },
{ item_id: 5, item_text: 'New Delhi' }
];
this.selectedItems = [
{ item_id: 3, item_text: 'Pune' },
{ item_id: 4, item_text: 'Navsari' }
];
this.dropdownSettings = {
singleSelection: false,
idField: 'item_id',
textField: 'item_text',
selectAllText: 'Select All',
unSelectAllText: 'UnSelect All',
itemsShowLimit: 3,
allowSearchFilter: true
};
}
onItemSelect(item: any) {
console.log(item);
}
onSelectAll(items: any) {
console.log(items);
}
}
But I get the following errors, when I look in the browser developer tools.
emplate parse errors:
Can't bind to 'placeholder' since it isn't a known property of 'ng-multiselect-dropdown'.
1. If 'placeholder' is an Angular directive, then add 'CommonModule' to the '#NgModule.imports' of this component.
2. To allow any property add 'NO_ERRORS_SCHEMA' to the '#NgModule.schemas' of this component.
Can't bind to 'data' since it isn't a known property of 'ng-multiselect-dropdown'.
Can't bind to 'settings' since it isn't a known property of 'ng-multiselect-dropdown'.
The html of the page is as below -
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<ng-multiselect-dropdown [placeholder]="'custom placeholder'"
[data]="dropdownList"
[(ngModel)]="selectedItems"
[settings]="dropdownSettings"
(onSelect)="onItemSelect($event)"
(onSelectAll)="onSelectAll($event)">
</ng-multiselect-dropdown>
</body>
</html>

This error means that the component's module doesn't have the required imports.
Try importing the module in this components' module file.

Instead of
[placeholder]="'custom placeholder'"
use
[value]="custom placeholder"
And of course check that the import in the module It's done the correct way
However I checked the official documentation and you can bind the placeholder property

Finally got it to work. The NgMultiSelectDropDownModule module itself should be imorted to app.module.ts. The ngOnInit() method should be used inside the component.ts. As correctly pointed out by others, the component's own selector should be different from the ng-multiselect-dropdown selector used in the component html.

Related

validating an array of uuids in nestjs swagger body

It sounds like a quite simple question but I've been searching for a solution for a very long time now. I want to validate an array of UUIDs in an endpoint.
Like this:
["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]
I have already successfully implemented it as a JSON object { "id": ["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]} with the following code:
public getIds(
#Body(ValidationPipe)
uuids: uuidDto
) {
console.log(uuids);
}
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: [String],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string;
}
But unfortunately I can't customize the function that calls that endpoint. So I need a solution to only validate a array of uuids.
instead of type string , write string[]. like below:
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: string[],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string[];
}
You can build a custom validation pipe for it:
#Injectable()
export class CustomClassValidatorArrayPipe implements PipeTransform {
constructor(private classValidatorFunction: (any)) {}
transform(value: any[], metadata: ArgumentMetadata) {
const errors = value.reduce((result, value, index) => {
if (!this.classValidatorFunction(value))
result.push(`${value} at index ${index} failed validation`)
return result
}, [])
if (errors.length > 0) {
throw new BadRequestException({
status: HttpStatus.BAD_REQUEST,
message: 'Validation failed',
errors
});
}
return value;
}
}
In your controller:
#Post()
createExample(#Body(new CustomClassValidatorArrayPipe(isUUID)) body: string[]) {
...
}
Ensure to use the lowercase functions from class-validator. It has to be isUUID instead of IsUUID. (This is used for the manual validation with class-validator.)
CustomClassValidatorArrayPipe is build modular. You can validate any other type with it. For example a MongoId: #Body(new CustomClassValidatorArrayPipe(isMongoId)) body: ObjectId[]
Result
If you send this:
POST http://localhost:3000/example
Content-Type: application/json
[
"986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e",
"123",
"test"
]
Server will reply:
{
"status": 400,
"message": "Validation failed",
"errors": [
"123 at index 1 failed validation",
"test at index 2 failed validation"
]
}

(this.internalValue || []).findIndex is not a function when enabling multiple selection on v-select

I am using Vue.js with Vuetify.
Following is my minimal reproducible example:
<template>
<v-app>
<v-select v-model="site" :items="sites" item-value="_id" item-text="name"></v-select>
<v-btn #click="showSelections">Show Selections</v-btn>
</v-app>
</template>
<script>
export default {
name: 'App',
data: () => ({
site: [],
sites: [
{
name: 'Vancouver',
_id: '5d9c276784e00100699281e2',
},
{
name: 'LA',
_id: '5d9c276784e00100699281e5',
},
{
name: 'Montreal',
_id: '5d9c276784e00100699281e3',
},
],
}),
methods: {
showSelections: function() {
console.log(this.site);
}
}
};
</script>
This example works perfect until you want to enable multiple selection on the v-select component.
<v-select v-model="site" :items="sites" multiple item-value="_id" item-text="name"></v-select>
As soon as you click the combobox, you'd get this:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in v-on handler: "TypeError: (this.internalValue || []).findIndex is not a function"
found in
---> <VSelectList>
<VThemeProvider>
<VMenu>
<VSelect>
<VMain>
<VApp>
<App> at src/App.vue
<Root>
TypeError: (this.internalValue || []).findIndex is not a function
at VueComponent.findExistingIndex (VSelect.ts?1576:338)
at VueComponent.selectItem (VSelect.ts?1576:816)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.invoker (vue.runtime.esm.js?2b0e:2179)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.Vue.$emit (vue.runtime.esm.js?2b0e:3888)
at click (VSelectList.ts?7bd1:169)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.invoker (vue.runtime.esm.js?2b0e:2179)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
This seems to be an issue caused by Vue CLI 4.5.11 transpiling Vuetify. If you remove vuetify from transpileDependencies, your example works properly:
// vue.config.js
module.exports = {
// transpileDependencies: [
// 'vuetify'
// ]
}
Interestingly, this isn't a problem at all (no config changes needed) with Vue CLI 5.0.0-alpha.4, so consider upgrading.
I had the same problem. I leave you here as I have it in case it works for you:
<!-- VueJS Template -->
<v-select :items="arrayItems" v-model="arrayItemsSelected" label="Items" item-text="text" outlined multiple chips attach dark></v-select>
// VueJS Data
export default {
data: () => ({
arrayItemsSelected: [],
arrayItems: [
{ value: "Item1", text: "Item1" },
{ value: "Item2", text: "Item2" },
{ value: "Item3", text: "Item3" },
{ value: "Item4", text: "Item4" },
{ value: "Item5", text: "Item5" },
],
}),
}
I had the same issue when I was toggling the multiple property of the v-select. See the reproduction link: https://codepen.io/kkojot/pen/MWOpYqZ
To avoid this error you have to clear the property bound to v-model and change it empty object {} or empty array [] accordingly.
computed: {
isMultiple() {
//comment the if statment below to see the 'TypeError: (this.internalValue || []).findIndex is not a function'
if (this.multiple) this.site = [];
else this.site = {};
return this.multiple;
},
},

Angular Get Data From JSON to Model And List It

I'm trying to get data from a JSON using Angular and map it to a model, then show it on a webpage.
I did it buy I'm not getting any results, like the data from JSON cannot be taken or something.
Here's my try:
The JSON:
{
"location": [
{
"_id": "5f3567a8d8e66b41d4bdfe5f",
"lat": "44.4363228",
"lng": "25.9912305",
"token": "edb153fb9d8d5628",
"__v": 0
}
]
The model:
export class Post {
public _id: string;
public token: string;
public lat: string;
public lng: string;
}
Service class:
#Injectable({ providedIn: 'root' })
export class PostsService {
public posts: Post[] = [];
private postsUpdated = new Subject<Post[]>();
Post: Promise<any>;
constructor(private http: HttpClient) {}
private url: string = 'http://localhost:8000/location';
getPosts() {
this.http
.get<{ posts: any[] }>(this.url)
.pipe(
map((postData) => {
return postData.posts.map((post) => {
console.log(this.posts);
return {
_id: post._id,
token: post.token,
lat: post.lat,
lng: post.lng,
};
});
})
)
.subscribe((transformedPosts) => {
this.posts = transformedPosts;
this.postsUpdated.next([...this.posts]);
});
}
getPostUpdateListener() {
return this.postsUpdated.asObservable();
}
}
post-list.component.ts:
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css'],
})
export class PostListComponent implements OnInit, OnDestroy {
posts: Post[] = [];
private postsSub: Subscription;
result: any;
constructor(public postsService: PostsService) {
//dependency-injection
}
ngOnInit() {
this.postsService.getPosts();
this.postsSub = this.postsService
.getPostUpdateListener()
.subscribe((posts: Post[]) => {
this.posts = posts;
});
}
onShow() {
console.log('TODO');
}
ngOnDestroy() {
this.postsSub.unsubscribe();
}
}
post-list.component.html:
<mat-accordion multi="true" *ngIf="posts.length > 0">
<mat-expansion-panel *ngFor="let post of posts">
<mat-expansion-panel-header>
{{ post.token }}
</mat-expansion-panel-header>
<p>{{ post.lat }}</p>
<p>{{ post.lng }}</p>
<mat-action-row>
<button mat-raised-button color="accent" (click)="onShow(post._id)">
SHOW
</button>
</mat-action-row>
</mat-expansion-panel>
</mat-accordion>
<p class="info-text mat-body-1" *ngIf="posts.length <= 0">No posts added yet</p>
app.component.html:
<app-header></app-header> <br /><br />
<agm-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
<agm-marker [latitude]="lat" [longitude]="lng"></agm-marker>
</agm-map>
<br /><br />
<app-post-list></app-post-list>
And here's my result (photo):
I also tried to do it in different ways, always getting no result.
Any help or ideas would be much appreciated!
The error in the image says posts attribute does not exists on postData object which is gotten by get request. Absence of posts attribute is also clear in the JSON you provided.
{
"location": [
{
"_id": "5f3567a8d8e66b41d4bdfe5f",
"lat": "44.4363228",
"lng": "25.9912305",
"token": "edb153fb9d8d5628",
"__v": 0
}
]
You should completely remove the pipe and it should be fine.

How to Pass data from api into pie chart with angular

I would like to pass the output of an API in a piechart canvas with angular but I could not achieve any result, the API is consumed but I have a problem associating it with the piechart (datapoints)
the code just below.
app.component.ts
import { Component, OnInit } from '#angular/core';
import { EmailValidator } from '#angular/forms';
import { Router, NavigationEnd } from '#angular/router';
import { ApiService } from './api.service';
import { ApistatService } from './apistat.service';
import * as CanvasJS from './canvasjs.min';
//var CanvasJS = require('./canvasjs.min');
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
elements: any[];
constructor(
private apistatService: ApistatService
) {
this.elements = [];
}
ngOnInit() {
let chart = new CanvasJS.Chart("chartContainer", {
theme: "light2",
animationEnabled: true,
exportEnabled: true,
title:{
text: "Monthly Expense"
},
data: [{
type: "pie",
showInLegend: true,
toolTipContent: "<b>{elements.total}</b>: ${y} (#status)",
indexLabel: "{name} - #percent%",
dataPoints: [
{ y: 120, name: "Insurance" },
{ y: 300, name: "Traveling" },
{ y: 800, name: "Housing" },
{ y: 150, name: "Education" },
{ y: 150, name: "Shopping"},
{ y: 250, name: "Others" }
]
}]
});
this.apistatService.getData().subscribe((data:any) => {
if(data.status === 200) {
console.log(data.response);
this.elements = data.response;
}
})
chart.render();
}
}
app.component.html
<div id="chartContainer" style="height: 370px; width: 100%; margin-left:auto;margin-right:auto;">
</div>
To consume API data in pie chart you should just set data from API to dataPoints array just make sure your API data has same formatting as currently available inside dataPoints array which you are passing in data array while rendering chart, and it will work.

How to access ReactQuill Ref when using dynamic import in NextJS?

I'm running into a funny problem. I'm using NextJS for its server-side rendering capabilities and am using ReactQuill as my rich-text editor. To get around ReactQuill's tie to the DOM, I'm dynamically importing it. However, that presents another problem which is that when I try to attach a ref to the ReactQuill component, it's treated as a loadable component instead of the ReactQuill component. I need the ref in order to customize how images are handled when uploaded into the rich-text editor. Right now, the ref returns current:null instead of the function I can use .getEditor() on to customize image handling.
Anybody have any thoughts on how I can address this? I tried ref-forwarding, but it's still applying refs to a loadable component, instead of the React-Quill one. Here's a snapshot of my code.
const ReactQuill = dynamic(import('react-quill'), { ssr: false, loading: () => <p>Loading ...</p> }
);
const ForwardedRefComponent = React.forwardRef((props, ref) => {return (
<ReactQuill {...props} forwardedRef={(el) => {ref = el;}} />
)})
class Create extends Component {
constructor() {
super();
this.reactQuillRef = React.createRef();
}
imageHandler = () => {
console.log(this.reactQuillRef); //this returns current:null, can't use getEditor() on it.
}
render() {
const modules = {
toolbar: {
container: [[{ 'header': [ 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
['link', 'image'],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'align': [] }],
['blockquote', 'code-block'],],
handlers: {
'image': this.imageHandler
}
}
};
return(
<ForwardedRefComponent
value={this.state.text}
onChange={this.handleChange}
modules={modules}
ref={this.reactQuillRef}/> //this.reactQuillRef is returning current:null instead of the ReactQuill function for me to use .getEditor() on
)
}
}
const mapStateToProps = state => ({
tutorial: state.tutorial,
});
export default connect(
mapStateToProps, {createTutorial}
)(Create);
I share my solution with hope that it helps you too.
Helped from https://github.com/zenoamaro/react-quill/issues/642#issuecomment-717661518
const ReactQuill = dynamic(
async () => {
const { default: RQ } = await import("react-quill");
return ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;
},
{
ssr: false
}
);
export default function QuillWrapper() {
const quillRef = React.useRef(false)
return <>
<ReactQuill forwardedRef={quillRef} />
</>
}
for example you can use the ref to upload image with custom hanlder
import React, { useMemo } from "react";
import dynamic from "next/dynamic";
const ReactQuill = dynamic(
async () => {
const { default: RQ } = await import("react-quill");
return ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;
},
{
ssr: false,
}
);
export default function QuillWrapper({ value, onChange, ...props }) {
const quillRef = React.useRef(false);
// Custom image upload handler
function imgHandler() {
// from https://github.com/quilljs/quill/issues/1089#issuecomment-318066471
const quill = quillRef.current.getEditor();
let fileInput = quill.root.querySelector("input.ql-image[type=file]");
// to prevent duplicate initialization I guess
if (fileInput === null) {
fileInput = document.createElement("input");
fileInput.setAttribute("type", "file");
fileInput.setAttribute(
"accept",
"image/png, image/gif, image/jpeg, image/bmp, image/x-icon"
);
fileInput.classList.add("ql-image");
fileInput.addEventListener("change", () => {
const files = fileInput.files;
const range = quill.getSelection(true);
if (!files || !files.length) {
console.log("No files selected");
return;
}
const formData = new FormData();
formData.append("file", files[0]);
formData.append("uid", uid);
formData.append("img_type", "detail");
quill.enable(false);
console.log(files[0]);
axios
.post("the/url/for/handle/uploading", formData)
.then((response) => {
// after uploading succeed add img tag in the editor.
// for detail visit https://quilljs.com/docs/api/#editor
quill.enable(true);
quill.insertEmbed(range.index, "image", response.data.url);
quill.setSelection(range.index + 1);
fileInput.value = "";
})
.catch((error) => {
console.log("quill image upload failed");
console.log(error);
quill.enable(true);
});
});
quill.root.appendChild(fileInput);
}
fileInput.click();
}
I don't know much about useMemo
but if i don't use the hook,
the editor keeps rerendered resulting in losing focus and I guess perfomance trouble too.
const modules = useMemo(
() => ({
toolbar: {
container: [
[{ font: [] }],
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
["bold", "italic", "underline", "strike"], // toggled buttons
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ script: "sub" }, { script: "super" }], // superscript/subscript
[{ header: 1 }, { header: 2 }], // custom button values
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent
[{ direction: "rtl" }], // text direction
[{ align: [] }],
["link", "image"],
["clean"], // remove formatting button
],
handlers: { image: imgHandler }, // Custom image handler
},
}),
[]
);
return (
<ReactQuill
forwardedRef={quillRef}
modules={modules}
value={value}
onChange={onChange}
{...props}
/>
);
}
In NextJS, React.useRef or React.createRef do not work with dynamic import.
You should Replace
const ReactQuill = dynamic(import('react-quill'), { ssr: false, loading: () => <p>Loading ...</p> }
);
with
import ReactQuill from 'react-quill';
and render after when window is loaded.
import ReactQuill from 'react-quill';
class Create extends Component {
constructor() {
super();
this.reactQuillRef = React.createRef();
this.state = {isWindowLoaded: false};
}
componentDidMount() {
this.setState({...this.state, isWindowLoaded: true});
}
.........
.........
render(){
return (
<div>
{this.isWindowLoaded && <ReactQuil {...this.props}/>}
</div>
)
}
}
Use onChange and pass all the arguments, here one example to use the editor.getHTML()
import React, { Component } from 'react'
import dynamic from 'next/dynamic'
import { render } from 'react-dom'
const QuillNoSSRWrapper = dynamic(import('react-quill'), {
ssr: false,
loading: () => <p>Loading ...</p>,
})
const modules = {
toolbar: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ size: [] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[
{ list: 'ordered' },
{ list: 'bullet' },
{ indent: '-1' },
{ indent: '+1' },
],
['link', 'image', 'video'],
['clean'],
],
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
}
/*
* Quill editor formats
* See https://quilljs.com/docs/formats/
*/
const formats = [
'header',
'font',
'size',
'bold',
'italic',
'underline',
'strike',
'blockquote',
'list',
'bullet',
'indent',
'link',
'image',
'video',
]
class BlogEditor extends Component {
constructor(props) {
super(props)
this.state = { value: null } // You can also pass a Quill Delta here
this.handleChange = this.handleChange.bind(this)
this.editor = React.createRef()
}
handleChange = (content, delta, source, editor) => {
this.setState({ value: editor.getHTML() })
}
render() {
return (
<>
<div dangerouslySetInnerHTML={{ __html: this.state.value }} />
<QuillNoSSRWrapper ref={this.editor} onChange={this.handleChange} modules={modules} formats={formats} theme="snow" />
<QuillNoSSRWrapper value={this.state.value} modules={modules} formats={formats} theme="snow" />
</>
)
}
}
export default BlogEditor
If you want to use ref in Next.js with dynamic import
you can use React.forwardRef API
more info

Resources