I created a Angular2 app that allows the user to upload images. I want to implement a preview option. However, when i try to imperilment it the image doesn't show up. How do i achieve this feature?
UploadComponent.ts
import * as ng from '#angular/core';
//import { UPLOAD_DIRECTIVES } from 'ng2-uploader';
import {UploadService} from '../services/upload.service';
#ng.Component({
selector: 'my-upload',
providers:[UploadService],
template: require('./upload.html')
})
export class UploadComponent {
progress:any;
logo:any;
filesToUpload: Array<File>;
constructor(public us:UploadService){
this.filesToUpload = [];
}
upload() {
this.us.makeFileRequest("http://localhost:5000/api/SampleData/Upload", this.filesToUpload)
.then((result) => {
console.log(result);
}, (error) => {
console.error(error);
});
}
onFileChange(fileInput: any){
this.logo = fileInput.target.files[0];
}
}
Upload.html
<h2>Upload</h2>
<input type="file" (change)="onFileChange($event)" placeholder="Upload image..." />
<button type="button" (click)="upload()">Upload</button>
<img [src]="logo" alt="Preivew">
The way you try it, you don't get the image URL with fileInput.target.files[0], but an object.
To get an image URL, you can use FileReader (documentation here)
onFileChange(fileInput: any){
this.logo = fileInput.target.files[0];
let reader = new FileReader();
reader.onload = (e: any) => {
this.logo = e.target.result;
}
reader.readAsDataURL(fileInput.target.files[0]);
}
filesToUpload: Array<File> = [];
url: any;
image: any;
//file change event
filechange(fileInput: any) {
this.filesToUpload = <Array<File>>fileInput.target.files;
this.image = fileInput.target.files[0]['name'];
this.readurl_file(event);
}
//read url of the file
readurl_file(event) {
if (event.target.files && event.target.files[0]) {
const reader = new FileReader();
reader.onload = (eve: any) => {
this.url = eve.target.result;
};
reader.readAsDataURL(event.target.files[0]);
}
}
<div class="form-group">
<label for="image">Image</label>
<input type="file" class="form-control" (change)="filechange($event)" placeholder="Upload file..." >
</div>
<div class="container">
<img [src]="url">
</div>
Using FileReader is not a good practice. If an image is too large it can crash your browser because onload function load whole image in RAM.
Better approach is to use:
url = URL.createObjectURL($event.target.files[0]);
Then, show it out with DomSanitizer:
this.sanitizer.bypassSecurityTrustUrl(url)
So in ts:
constructor(private sanitizer: DomSanitizer) {}
onFileChange(fileInput: any){
this.url = URL.createObjectURL($event.target.files[0]);
}
get previewUrl(): SafeUrl {
return this.sanitizer.bypassSecurityTrustUrl(this.url);
}
And in html:
<img [src]="previewUrl"/>
Related
I am using a mutation query to take the input from user and update it in database but while updating it is throwing this error invariant violation which I am not able to understand, In mutation I have four objects with min and max value that is coming from user:
export const ADD_INSIGHT_META_DATA = gql`
mutation MyMutation ($data : [projectInsightsMetadata_insert_input!]!) {
insert_projectInsightsMetadata(objects: $data) {
returning {
id
createdAt
}
}
}
`
This is how I am using the above query :
export const updateScheduleInsightMetaData = async(InsertData:any )=>{
const response:ApolloQueryResult<any> = await client.query({
query:ADD_INSIGHT_META_DATA,
context:{
role:"updateMasterPlan",
token:getProjectExchangeToken()
},
fetchPolicy: 'network-only',
variables:{
data:InsertData
}
})
return response.data;
}
Now I am using updateScheduleInsightMetaData function in one of code :
import React, { useContext, useEffect, useState } from "react";
import { IconButton, makeStyles, Paper, TextField, Typography } from "#material-ui/core";
import { useParams } from "react-router-dom";
import { Button } from "#material-ui/core";
import { Box } from "#mui/system";
import {getScheduleInsightMetaData,updateScheduleInsightMetaData} from "./InsightsSettingsActions";
import { setIsLoading } from "src/modules/root/context/authentication/action";
import { stateContext } from "src/modules/root/context/authentication/authContext";
import Notification, { AlertTypes } from "src/modules/shared/components/Toaster/Toaster";
import { projectDetailsContext } from "src/modules/baseService/projects/Context/ProjectDetailsContext";
import {
decodeExchangeToken,
getProjectExchangeToken
} from "../../../../../services/authservice";
import "./InsightsSettings.scss";
import NoDataMessage from "src/modules/shared/components/NoDataMessage/NoDataMessage";
interface Params {
projectId:any
}
interface InputType{
min:number,
max:number
}
export const noPermissionMessage = "You don't have permission to view project insight settings";
const InsightsSettings: React.FC =()=>{
const { projectDetailsState }: any = useContext(projectDetailsContext);
const { dispatch, stateCont }: any = useContext(stateContext);
const {projectId} = useParams<Params>();
const [hasCreateAccess, setCreateAccess] = useState<boolean>(false);
const [informationalConstraints, setInformationalConstraints] = useState<any>({min:Number(""),max:Number("")});
const [rfiResponse, setRfiResponse] = useState<any>({min:Number(""),max:Number("")});
const [physicalConstraints, setPhysicalConstraints] = useState<any>({min:Number(""),max:Number("")});
const [managementConstraints, setManagementConstraints] = useState<any>({min:Number(""),max:Number("")});
const tenantId = decodeExchangeToken().tenantId;
console.log("informationalConstraints",informationalConstraints);
console.log("rfiResponse",rfiResponse);
console.log("physicalConstraints",physicalConstraints);
console.log("managementConstraints",managementConstraints);
const projectToken = getProjectExchangeToken();
useEffect(()=>{
setCreateAccess(decodeExchangeToken(projectToken).allowedRoles.includes("viewMasterPlan"))
},[projectToken])
const navigateback = () => {
// props.navBack();
}
useEffect(()=>{
const token = projectDetailsState.projectToken;
fetchInsightsData(projectId,tenantId,token)
},[projectId,tenantId])
const fetchInsightsData = async(projectId:any,tenantId:any,token:any)=>{
try{
// const tenantId = decodeExchangeToken().tenantId;
// const token = projectDetailsState.projectToken;
dispatch(setIsLoading(true));
const res = await getScheduleInsightMetaData(projectId, tenantId ,token)
dispatch(setIsLoading(false));
}catch(err){
console.log("error in fetching insights metadata",err)
Notification.sendNotification('An error occured while fetching insights metadata', AlertTypes.warn);
dispatch(setIsLoading(false));
}
}
const handleInformationalInputChange = (e:any)=>{
setInformationalConstraints((prevState:any)=>({...prevState,[e.target.name]:Number(e.target.value)}))
}
const handleRfiInputChange = (e:any)=>{
setRfiResponse((prevState:any)=>({...prevState,[e.target.name]:Number(e.target.value)}))
}
const handlePhysicalInputChange = (e:any)=>{
setPhysicalConstraints((prevState:any)=>({...prevState,[e.target.name]:Number(e.target.value)}))
}
const handleManagementInputChange = (e:any)=>{
setManagementConstraints((prevState:any)=>({...prevState,[e.target.name]:Number(e.target.value)}))
}
const handleUpdate = async(e:any,informationalConstraints:any,rfiResponse:any,physicalConstraints:any,managementConstraints:any)=>{
try{
dispatch(setIsLoading(true));
const data = {
LeadtimeMgmntConstraints:managementConstraints,
LeadtimePhysicalConstraints:physicalConstraints,
ChangeOrderIssueReview:rfiResponse,
RFIReviewResponse:informationalConstraints
}
const response = await updateScheduleInsightMetaData(data)
// const response = await updateScheduleInsightMetaData(managementConstraints,physicalConstraints,rfiResponse,informationalConstraints)
Notification.sendNotification("Successfully updated insights", AlertTypes.success);
dispatch(setIsLoading(false));
}catch(err){
dispatch(setIsLoading(false));
Notification.sendNotification(err, AlertTypes.warn);
console.log(err)
}
}
return(
<>
{hasCreateAccess?
<div className="InsightsSettings">
<>
<div className="InsightsSettings__header">
<Typography component="p">Insight Settings</Typography>
</div>
<div className="InsightsSettings__input_area">
<div className="InsightsSettings__individual_box">
<Typography component="p">How far ahead of an activity start does the team review and resolve management or informational constraints?</Typography>
<div className="InsightsSettings__constraints">
<input name="min" className="InsightsSettings__constraints__style" value={informationalConstraints.min} placeholder="min" onChange={handleInformationalInputChange}/>
<input name="max" className="InsightsSettings__constraints__style" value={informationalConstraints.max} placeholder="max" onChange={handleInformationalInputChange}/>
</div>
</div>
<div className="InsightsSettings__individual_box">
<Typography component="p">What is the average (or typical) RFI design response period for this project?</Typography>
<div className="InsightsSettings__constraints">
<input name="min" className="InsightsSettings__constraints__style" value={rfiResponse.min} placeholder="min" onChange={handleRfiInputChange}/>
<input name="max" className="InsightsSettings__constraints__style" value={rfiResponse.max} placeholder="max" onChange={handleRfiInputChange}/>
</div>
</div>
<div className="InsightsSettings__individual_box">
<Typography component="p">How far ahead of an activity start does the team review and resolve physical or site constraints ?</Typography>
<div className="InsightsSettings__constraints">
<input name="min" className="InsightsSettings__constraints__style" value={physicalConstraints.min} placeholder="min" onChange={handlePhysicalInputChange}/>
<input name="max" className="InsightsSettings__constraints__style" value={physicalConstraints.max} placeholder="max" onChange={handlePhysicalInputChange}/>
</div>
</div>
<div className="InsightsSettings__individual_box">
<Typography component="p">How far ahead of an activity start does the team review and resolve management or informational constraints?</Typography>
<div className="InsightsSettings__constraints">
<input name="min" className="InsightsSettings__constraints__style" value={managementConstraints.min} placeholder="min" onChange={handleManagementInputChange}/>
<input name="max" className="InsightsSettings__constraints__style" value={managementConstraints.max} placeholder="max" onChange={handleManagementInputChange}/>
</div>
</div>
</div>
</>
<div className="InsightsSettings__action-button">
<Button data-testid={'cancel-update'} variant="outlined" onClick={navigateback} className="cancel-button">
Cancel
</Button>
<Button
variant="outlined"
className="update-button"
onClick={(e:any)=>{handleUpdate(e,informationalConstraints,rfiResponse,physicalConstraints,managementConstraints)}}
>
Update
</Button>
</div>
</div>: (
<div className="noCreatePermission-insight">
<div className="no-permission-insight">
<NoDataMessage message={noPermissionMessage}/>
</div>
</div>
) }
</>
)
}
export default InsightsSettings
When I am clicking on update button to make the insertion in database I am getting invariant violation: 47 error ,I am pasting the error here
Invariant Violation: Invariant Violation: 47 (see https://github.com/apollographql/invariant-packages)
at new InvariantError (http://localhost:3000/static/js/vendors~main.chunk.js:327507:24)
at invariant (http://localhost:3000/static/js/vendors~main.chunk.js:327521:11)
at getQueryDefinition (http://localhost:3000/static/js/vendors~main.chunk.js:9716:230)
at StoreReader.diffQueryAgainstStore (http://localhost:3000/static/js/vendors~main.chunk.js:2449:286)
at InMemoryCache.diff (http://localhost:3000/static/js/vendors~main.chunk.js:1132:29)
at http://localhost:3000/static/js/vendors~main.chunk.js:4864:28
at perform (http://localhost:3000/static/js/vendors~main.chunk.js:1264:31)
at InMemoryCache.batch (http://localhost:3000/static/js/vendors~main.chunk.js:1288:7)
at InMemoryCache.performTransaction (http://localhost:3000/static/js/vendors~main.chunk.js:1321:17)
at QueryInfo.markResult (http://localhost:3000/static/js/vendors~main.chunk.js:4842:20)
at http://localhost:3000/static/js/vendors~main.chunk.js:5713:19
at both (http://localhost:3000/static/js/vendors~main.chunk.js:10788:20)
at http://localhost:3000/static/js/vendors~main.chunk.js:10777:26
at new Promise (<anonymous>)
at Object.then (http://localhost:3000/static/js/vendors~main.chunk.js:10776:16)
at Object.next (http://localhost:3000/static/js/vendors~main.chunk.js:10791:39)
at notifySubscription (http://localhost:3000/static/js/vendors~main.chunk.js:332559:18)
at onNotify (http://localhost:3000/static/js/vendors~main.chunk.js:332603:3)
at SubscriptionObserver.next (http://localhost:3000/static/js/vendors~main.chunk.js:332652:5)
at http://localhost:3000/static/js/vendors~main.chunk.js:10848:23
at Array.forEach (<anonymous>)
at iterateObserversSafely (http://localhost:3000/static/js/vendors~main.chunk.js:10847:23)
at Object.next (http://localhost:3000/static/js/vendors~main.chunk.js:10586:87)
at notifySubscription (http://localhost:3000/static/js/vendors~main.chunk.js:332559:18)
at onNotify (http://localhost:3000/static/js/vendors~main.chunk.js:332603:3)
at SubscriptionObserver.next (http://localhost:3000/static/js/vendors~main.chunk.js:332652:5)
at Object.next (http://localhost:3000/static/js/vendors~main.chunk.js:6773:22)
at notifySubscription (http://localhost:3000/static/js/vendors~main.chunk.js:332559:18)
at onNotify (http://localhost:3000/static/js/vendors~main.chunk.js:332603:3)
at SubscriptionObserver.next (http://localhost:3000/static/js/vendors~main.chunk.js:332652:5)
at notifySubscription (http://localhost:3000/static/js/vendors~main.chunk.js:332559:18)
at onNotify (http://localhost:3000/static/js/vendors~main.chunk.js:332603:3)
at SubscriptionObserver.next (http://localhost:3000/static/js/vendors~main.chunk.js:332652:5)
at http://localhost:3000/static/js/vendors~main.chunk.js:7061:18
After going to the github url provided in error i.e https://github.com/apollographql/invariant-packages it tells to go into particular file in your node-modules to get the more understanding of invariant number error
node_modules/#apollo/client/invariantErrorCodes.js
after going into above file they have provided some extra information related to error code so for me I was getting invariant violation: 47
47: {
file: "#apollo/client/utilities/graphql/getFromAST.js",
node: invariant(queryDef && queryDef.operation === 'query', 'Must contain a query definition.')
},
In my case I am having mutation query and trying to query it instead of doing mutation that's why I was getting error so the changes that I did in my code to make it working is :
Instead of doing this:
export const updateScheduleInsightMetaData = async(InsertData:any )=>{
const response:ApolloQueryResult<any> = await client.query({
query:ADD_INSIGHT_META_DATA,
context:{
role:"updateMasterPlan",
token:getProjectExchangeToken()
},
fetchPolicy: 'network-only',
variables:{
data:InsertData
}
})
return response.data;
}
I needed to do this:
export const updateScheduleInsightMetaData = async(InsightsData:any )=>{
const response = await client.mutate({
mutation:ADD_INSIGHT_META_DATA,
context:{
role:"updateMasterPlan",
token:getProjectExchangeToken()
},
fetchPolicy: 'network-only',
variables:{
data:InsightsData
}
})
return response.data;
}
I'm newbie in Vuejs. I am try to learning to code vuejs for couple of hours until I get this error.I guess the problem is come from props to to Blade. Here is my code.
// Blade View
<div id="app">
<div :posts="{{ $posts }}"></div>
</div>
// Vue Template
<table striped hover :items="imageList">
<template slot="image" slot-scope="data">
<img :src="'storage/images' + data.item.image" alt="">
</template>
</table>
// Vue JS
<script>
export default {
props:['posts'],
data: function() {
return {
imageList: []
};
},
mounted() {
this.fetch_image_list();
},
methods: {
fetch_image_list() {
let items = [];
if (Array.isArray(posts.data) && posts.data.length) {
posts.data.forEach((image,key) => {
let currentImage = {
'id':post.id,
'name':post.name,
'image':post.img,
}
items.push(currentImage)
});
this.imageList = items;
}
}
}
}
</script>
You should use this when accessing your data (if you don't have another scope defined inside). And you're trying to access the properties of undefined object (post) in forEach loop.
methods: {
fetch_image_list() {
let items = [];
if (Array.isArray(this.posts.data) && this.posts.data.length) {
this.posts.data.forEach((post, key) => {
let currentImage = {
'id':post.id,
'name':post.name,
'image':post.img,
}
items.push(currentImage)
});
this.imageList = items
}
}
}
I'm trying to test my component injected into a mock class I created. Although the component works when I try to test its existence it returns null.
Injectable Component:
import { Injectable, ElementRef, Renderer2, RendererFactory2 } from '#angular/core';
#Injectable()
export class NgBackdropComponent {
private renderer: Renderer2;
private appElementRef: ElementRef;
message: string = 'Carregando...';
constructor(rendererFactory: RendererFactory2) {
this.renderer = rendererFactory.createRenderer(null, null);
this.appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0));
}
show() {
const divSpinnerItem1 = this.renderer.createElement('i');
const divSpinnerItem2 = this.renderer.createElement('i');
const divSpinnerItem3 = this.renderer.createElement('i');
const divSpinner = this.renderer.createElement('div');
this.renderer.addClass(divSpinner, 'spinner');
this.renderer.appendChild(divSpinner, divSpinnerItem1);
this.renderer.appendChild(divSpinner, divSpinnerItem2);
this.renderer.appendChild(divSpinner, divSpinnerItem3);
const spanMensagem = this.renderer.createElement('span');
spanMensagem.innerHTML = this.message;
const div = this.renderer.createElement('div');
this.renderer.addClass(div, 'lock-content');
this.renderer.appendChild(div, divSpinner);
this.renderer.appendChild(div, spanMensagem);
this.renderer.appendChild(this.appElementRef.nativeElement, div);
}
hide() {
const elemento = this.appElementRef.nativeElement.querySelector('.lock-content');
if (elemento) {
elemento.remove();
}
}
}
my testing environment:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { NgBackdropComponent } from './ng-backdrop.component';
import { Component } from '#angular/core';
import { By } from '#angular/platform-browser';
#Component({
template: `
<button (click)="clickButton()"></button>
`
})
class MockNgBackdropComponent {
constructor(private backdrop: NgBackdropComponent) { }
clickButton() {
this.backdrop.message = 'Teste BackDrop aesdas';
this.backdrop.show();
console.log('iniciei backdrop');
}
closeBackdrop() {
this.backdrop.hide();
}
}
describe('NgBackdropComponent', () => {
let component: MockNgBackdropComponent;
let fixture: ComponentFixture<MockNgBackdropComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MockNgBackdropComponent],
providers: [NgBackdropComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MockNgBackdropComponent);
component = fixture.componentInstance;
});
describe('Deve injetar', async () => {
it('Deve ter uma div principal', function () {
const btnClick = fixture.nativeElement.querySelector('button');
btnClick.click();
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('.lock-content');
console.log(el);
expect(el).toBeTruthy();
});
});
});
In testing I create a Mock class where I inject my component.
I do not understand why it can not find the class because it exists.
The reason you can't find it in the component is because you did not create it in the component. If you look at this line in your constructor:
this.appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0))
You are creating it on the document directly in the <body> element. If you search for that in your spec, you will find it there. I created a STACKBLITZ to show you what I mean. Here is the spec from that stackblitz:
it('Deve ter uma div principal', () => {
const btnClick = fixture.nativeElement.querySelector('button');
console.log(btnClick);
btnClick.click();
fixture.detectChanges();
const appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0));
const el = appElementRef.nativeElement.querySelector('.lock-content');
expect(el).toBeTruthy();
});
Adding a little more clarification:
If you console.log(appElementRef) you'll notice that its tagName is body, and note the contents of its nativeElement.innerHTML Here is what that would look like "prettyfied":
<body>
<div class="jasmine_html-reporter">
<div class="jasmine-banner"><a class="jasmine-title" href="http://jasmine.github.io/" target="_blank"></a><span
class="jasmine-version">3.3.0</span></div>
<ul class="jasmine-symbol-summary"></ul>
<div class="jasmine-alert"></div>
<div class="jasmine-results">
<div class="jasmine-failures"></div>
</div>
</div>
<div id="nprogress" style="transition: none 0s ease 0s; opacity: 1;">
<div class="bar" role="bar" style="transform: translate3d(0%, 0px, 0px); transition: all 200ms ease 0s;">
<div class="peg"></div>
</div>
</div>
<div id="root0" ng-version="7.0.1">
<button></button>
</div>
<div class="lock-content">
<div class="spinner">
<i></i>
<i></i>
<i></i>
</div>
<span>Teste BackDrop aesdas</span>
</div>
</body>
Note how the button was created within the div with id="root0"? However, the div with class="lock-content" was created right off the root <body> element, and therefore is not within the div of the component.
In fact, you can see this very clearly when you console.log(fixture.nativeElement) and see that the tagName is "div", its innerHTML is <button></button>, and it has two attributes: id: "root0" and ng-version: "7.0.1". Put that all together and it looks like this:
<div id="root0" ng-version="7.0.1">
<button></button>
</div>
So you can clearly see that you cannot find the div you created in the component because you created it outside the component.
I hope this helps.
I think you should use DebugElement, for example:
it('Deve ter uma div principal', function () {
const btnClick = fixture.debugElement.query(By.css('button'));
btnClick.click();
fixture.detectChanges();
const el = fixture.debugElement.query(By.css('.lock-content'));
console.log(el);
expect(el).toBeTruthy();
});
Follow this link for more information.
I have a service which is publishing objects on an observable, but when I update an array using a subscription, the new object does not display on the list (html):
export class PublicationService {
// ... more code ...
getAllPapersOnState(state: string): Observable<Paper> {
return Observable.create(observer => {
this.WF_SC.deployed().then(instance => {
// State changed watcher to update on-live the observable
const event = instance.AssetStateChanged({});
event.on('data', (data) => {
console.log('StateChanged catched!');
this.getPaper((data['args']['assetAddress'])).then((paper) => observer.next(paper));
});
// TODO Filter by asset type
return instance.findAssetsByState.call(state);
}).then(addresses => {
addresses.forEach((address) => this.getPaper(address).then((paper) => observer.next(paper)));
});
});
}
}
export class HomeComponent implements OnInit, OnDestroy {
papersSubmitted$:Observable<DataCard>;
papersOnReview$:Observable<DataCard>;
papersPublished$:Observable<DataCard>;
constructor(private publicationService: PublicationService) { }
ngOnInit() {
this.papersSubmitted$ = this.publicationService.getAllPapersOnState("Submitted").pipe(
map(paper => HomeComponent.paperToCard(paper,'Read', 'Review'))
);
this.papersOnReview$ = this.publicationService.getAllPapersOnState("OnReview").pipe(
map(paper => HomeComponent.paperToCard(paper,'Read', 'Accept'))
);
this.papersPublished$ = this.publicationService.getAllPapersOnState("Published").pipe(
map(paper => HomeComponent.paperToCard(paper,'Read', ''))
);
}
// ... more code ...
}
<app-cardlist
[title]="'Published'"
[description]="'Last science papers published. Yay!'"
[collection$]="papersPublished$"
(clickActionCard)="clickActionCardHandlePublished($event)"></app-cardlist>
<app-cardlist
[title]="'On Review'"
[description]="'Last papers on review. On publish way!'"
[collection$]="papersOnReview$"
(clickActionCard)="clickActionCardHandleOnReview($event)"></app-cardlist>
<app-cardlist
[title]="'Submitted'"
[description]="'Last papers submitted for reviewing. Be the first one to check them!'"
[collection$]="papersSubmitted$"
(clickActionCard)="clickActionCardHandleSubmitted($event)"></app-cardlist>
export class CardlistComponent implements OnInit {
#Input() title;
#Input() description;
#Input() collection$: Observable<DataCard>;
items:DataCard[] = [];
subscription:Subscription;
#Output() clickActionCard: EventEmitter<any> = new EventEmitter();
constructor() { }
ngOnInit() {
this.subscription = this.collection$.subscribe((data_card:DataCard) => {
console.log(data_card);
this.items.push(data_card);
console.log(this.items);
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
<div *ngIf="(items)" class="card-deck">
<div *ngFor="let item of items" class="card" style="width: 18rem;">
<div class="card-body">
<div class="card-body">
<h5 class="card-title">{{item['title']}}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{item['subtitle']}}</h6>
<p class="card-text">{{item['description']}}</p>
{{item['action_1_name']}}
{{item['action_2_name']}}
</div>
</div>
</div>
</div>
The new object published on observable $collection should be displayed on the CardList but nothing is refreshed although console.log(data_card) show the new object.
Repository code: HomeComponent, CardListComponent
Im very new to typescript.
I'm trying to understand the Observables but I'm kinda lost here.
The function below searches for videos on Youtube API v3.
Is it a good approach?
Is subscribing inside a function which will be called many times a good idea?
This function is called whenever user types something.
Should I have an unsubscribe somewhere?
searchVideos(searchbar: any): void{
const typedValue: string = searchbar.srcElement.value;
if(typedValue.length > 2){
this.videosList = this.youtubeProvider.searchVideos(typedValue);
this.videosList.subscribe(data => {
if( data.length == 0 ){
this.notFoundAnyVideo = true;
}else{
this.notFoundAnyVideo = false;
}
})
}
}
It's a good question!
They are some ways to answer your question:
1/ you can debounce the action which call your function
Imagine, your action is triggered by a keyup in input field:
HTML
<input type="text" (keyup)="onSearchKeyup(this.value, $event)">
Component
export class MyComponent implements OnInt {
onSearch$: Subject<string>
ngOnInt(): void {
this.onSearch$
.debounceTime(500) //-> put your time here
.subscribe(search => searchVideos(search)
}
onSearchKeyup(search: string, e: any) {
this.onSearch$.next(search)
e.preventDefault()
}
}
2/ you can cancel the observable with takeUntil
Component
export class MyComponent implements OnInt {
onStopSearch$: Subject<void> = new Subject<void>();
onSearchKeyup(search: string, e: any) {
this.onStopSearch$.next()
this.searchVideos(string)
e.preventDefault()
}
private searchVideos(search: string): void{
if(typedValue.length > 2){
this.videosList = this.youtubeProvider.searchVideos(typedValue);
this.videosList
.takeUntil(this.onSearchStop$)
.subscribe(data => {
if( data.length == 0 ){
this.notFoundAnyVideo = true;
}else{ this.notFoundAnyVideo = false; }
})
}
}
}
Of course you can combine 1 and 2
Why I use takeUntil to cancel my requests: https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87
I suppose you could use RxJS all the way through, cause its reactive paradigm lends itself very well for search components. Take a look at the code below, I implemented variations of it in few applications.
import {Component, ViewChild, ElementRef} from "#angular/core";
#Component({
selector: 'search',
styleUrls: ['./search.component.scss'],
template: `
<form #searchBoxEl action="" class="search-form" [formGroup]="form">
<fieldset>
<input #searchBoxEl type="text" placeholder='Search for Youtube videos'
autocomplete="off" />
<nwum-list (itemSelected)="onItemSelect($event)"></nwum-list>
</fieldset>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchComponent implements OnInit {
#ViewChild('searchBoxEl') searchBoxEl: ElementRef;
componentDestroyed$: Subject<void> = new Subject<void>();
videosList: Video[];
constructor(public videoService: VideoService){}
ngOnInit(){
subscribeToSearchQueryChanges();
}
subscribeToSearchQueryChanges(){
const minNumOfChars = 2;
Observable.fromEvent(this.searchBoxEl.nativeElement, 'keyup')
.debounceTime(300)
.pluck('target', 'value')
.map(value => value.trim())
// .map(() => this.searchBoxEl.nativeElement.value.trim())
.filter(value => value.length >= minNumOfChars)
.takeUntil(this.componentDestroyed$)
.switchMap(value => this.videoService.fetchVideos(value))
.subscribe((videos: Video[]) => {
//show videos, etc
this.videosList = this.videoService.groupSuggestions(suggestions);
}, err => {
console.error('failed fetching videos', err);
this.removeAllSubscriptions();
this.subscribeToSearchQueryChanges();
});
this.addSubscription(sub);
}
ngOnDestroy() {
this.removeAllSubscriptions();
}
removeAllSubscriptions(){
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
}
}