manipulate data fetched from remote api angular 2 - loops

Im receiving data from the Marvel api and use ngFor to display the data.
Before displaying the data I need to make some changes to it.
If the name doesn't exist or the description is empty I want to show a message.
Where is the best place to manipulate data and how can I do this?
I tried something like this in my comic view after the subscribe but it didnt work.
If (stuff.data.results['0'].title == ''){
stuff.data.results['0'].title == 'try gain'}
my service.ts
getComics(searchterm): Observable<any> {
const search: URLSearchParams = new URLSearchParams();
search.set('titleStartsWith',searchterm);
search.set('ts', '1'); // time stamp
search.set('apikey', 'key');
search.set('hash', 'key');
let obs: Observable<any> = this.http
.get(ComicService.BASE_URL, new RequestOptions({search}))
.map( (res) => res.json());
console.log("obj " + obs)
return obs;
}
my comic-view.ts
searchComics(event): boolean {
this.error = 'fout'
this._comicService.getComics(this.searchterm)
.subscribe(
stuff => {
this.stuff = stuff; console.log('title: ' + stuff.data.results['0'].title + ' description:' +
stuff.data.results['0'].description);
},
error => { this.error = error; }
);
return false;
}
comis.html
<ul>
<li *ngFor=" let x of stuff?.data?.results ">
<label> Title:</label> {{x.title}}
<br>
<label> description: </label> {{x.description}}
<br>
</li>
</ul>

Related

Trouble loading array safely in my angular html template

I have an array that is populated after a .subscribe to my API. Console shows it populated as expected. Accessing an element of the array results to an error thrown because of it being undefined
<div *ngIf="!invoices || invoices.length === 0">
No invoices
</div>
<div *ngIf="invoices || async ">
{{ invoices[0]?.invoice_id || async}}
</div>
If I remove the elvis operator my content will load fine however the console will throw errors InvoicesComponent.html:10 ERROR TypeError: Cannot read property 'invoice_id' of undefined until the array gets populated from the subscribe function.
The invoices array is initialised in my service
invoices: Array<Invoice> = [];
And I populate the array
getInvoices(){
var _invoices = this.invoices;
if(this.afAuth.user){
// users/uid/invoices/invoice_id/
var userRef = this.afs.doc(`users/${this.afAuth.auth.currentUser.uid}`)
userRef.collection('invoices').get().subscribe(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
_invoices.push({
'invoice_id': doc.id,
'customer_company': doc.data().customer_company,
'year_id':doc.data().year_id,
'date_created': doc.data().date_created,
'date_modified': doc.data().date_modified})
});
console.log(_invoices)
});
return _invoices
}
Based on the suggestion of trichetriche, an `Invoice class was created
import { QueryDocumentSnapshot } from "#angular/fire/firestore";
import { of } from 'rxjs'
export class Invoice {
invoice_id: string;
customer_company: string;
date_created: string;
date_modified: string;
year_id: string;
constructor(invoiceDoc: QueryDocumentSnapshot<any>){
this.invoice_id = invoiceDoc.id
this.customer_company = invoiceDoc.data().customer_company
this.date_created = invoiceDoc.data().date_created
this.date_modified = invoiceDoc.data().date_modified
this.year_id = invoiceDoc.data().year_id
}
toObservable(){
return of(this)
}
}
Original
<div *ngIf="!invoices || invoices.length === 0">
No invoices
</div>
<div *ngIf="invoices || async ">
{{ invoices[0]?.invoice_id || async}}
</div>
Edited
<ng-container *ngIf="invoices | async as invoicesSync; else noInvoices">
<p>{{ invoicesSync[0]?.invoice_id || 'No ID for invoice' }}</p>
</ng-container>
<ng-template #noInvoices>
<p>No invoices</p>
</ng-template>
1 - It's | async, not || async : | is a pipe, || is a fallback to a falsy statement.
2 - There should be a single async in your code, which create a template variable through as XXX.
3 - You don't need several conditions. Use a single one with a then statement.
i think you are using the Async pipe in wrong way .
you can passe Observable directly to template and the code will like this :
<div *ngIf="invoices|async as invoicesList; else noInvoices">
{{ invoicesList[0]?.invoice_id}}
</div>
<ng-template #noInvoices>
<div >
No invoices
</div>
</ng-template>
Right so after some research it seems that I was better off subscribing to an observable and dealing with the data as it arrives from my API with the async pipe.
So my final functions look kind of like this:
ngOnInit() {
this.observableInvoices = this.auth.getObservableInvoices().pipe(map(
(data) => data));
console.log(this.observableInvoices)
}
<li *ngFor="let invoice of observableInvoices | async; index as i">
getObservableInvoices(): Observable<any> {
this.observable_invoices = this.afs
.collection(`users/${this.afAuth.auth.currentUser.uid}/invoices`)
.valueChanges() as Observable<any[]>;
return this.observable_invoices;
}

How to get distinct value using lodash in ReactJS?

How to get distinct value using lodash in ReactJS? Now I'm getting all the data. But how to avoid printing duplicate data? Actually it is a filter box. So data repetition I've to avoid. Can anyone help me out?
Function:
comboClick () {
var apiBaseUrl = "http://api.eventsacross-stage.railsfactory.com/api/";
var input = this.state.search_event;
let self = this;
axios.get(apiBaseUrl+'v1/events/?on_dashboard=false'+input)
.then(function (response) {
let events = response.data.events;
self.setState({events: events});
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
Jsx Part:
<div className="dropdown text-center">
<button
className="btn btn-default dropdown-toggle"
type="button"
data-toggle="dropdown"
style={{width: "50%"}}
onClick={this.comboClick.bind(this)}>
Place
<span className="caret"></span>
</button>
<ul className="dropdown-menu">
{this.state.events.map(function (event, i) {
return (
<div key={i}>
{event.locations.map(function (locations, j) {
return (
<li key={j}><p>{locations.city}</p></li>
)
})}
</div>
)
})}
</ul>
</div>
You can use _.uniqBy, as per documentation:
This method is like _.uniq except that it accepts iteratee which is
invoked for each element in array to generate the criterion by which
uniqueness is computed. The order of result values is determined by
the order they occur in the array. The iteratee is invoked with one
argument: (value).
I've added an example:
var locations =
[
{city:"city1"},
{city:"city2"},
{city:"city3"},
{city:"city1"},
];
var locationsUnique = _.uniq(locations, 'city');
console.log(locationsUnique);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
UPDATE:
From the method shared in the comments I'm guessing that what you want to do is something like:
comboClick() {
var apiBaseUrl = "api.eventsacross-stage.railsfactory.com/api/";
var input = this.state.search_event;
let self = this;
axios.get(apiBaseUrl + 'v1/events/?on_dashboard=false' + input).then(function(response) {
let events = response.data.events;
for (var i = 0; i < response.data.events_count; i++) {
var obj = response.data.events[i];
console.log(obj.locations);
//Assuming that obj.locations has the locations that you want to display
var locationsUnique = _.uniq(obj, 'city'); //Added this line
console.log(locationsUnique); //Added this line
}
for (var j = 0; j <= obj; j++) {}
self.setState({events: events});
}).catch(function(error) {
console.log(error);
});
}

Vue.js: Manipulate Array and post form with new data

In my Vue.js application I want to post form data to my Node.js/MongoDB Backend.
This is my source code: https://github.com/markusdanek/t2w-vue/blob/master/src/components/backend/JobEdit.vue
JSON for my job entry: http://t2w-api.herokuapp.com/jobs/591c09a55ba85d0400e5eb61
Relevant code for my question:
HTML:
<div class="row">
<input type='text'
:name="'qual'+index"
v-model="qualifications[index]">
<button #click.prevent="removeQualifiaction(index)">X</button>
</div>
Methods:
onChange(value, $event){
if (!this.job.xmlOnline)
this.job.xmlOnline = []
const index = this.job.xmlOnline.findIndex(v => v == value)
const checked = $event.target.checked
if (checked && index < 0)
this.job.xmlOnline.push(value)
if (!checked && index >= 0)
this.job.xmlOnline.splice(index, 1)
}
removeQualifiaction() {
this.qualifications.splice(this.qualifications.index, 1);
}
Sending the form data with submit button on form end:
editJob() {
let job = Object.assign({}, this.job);
job.qualifications = this.qualifications;
job.responsibility = this.responsibility;
this.$http.post('https://t2w-api.herokuapp.com/jobs/' + this.$route.params.id, job).then(response => {
console.log(response);
}, response => {
console.log(response);
});
}
My problems now:
When I edit a "Job", I have a list of "qualification items", that are input fields in my form.
Clicking the "delete" button results that the first input gets deleted, not the one I am clicking. Done with #thanksd answer.
How do I add a button and method to add a new input field and to append it to my job.qualifications?
In my JobAdd.vue implemented, to add a new entry to job.qualifications, like this:
<a #click.prevent="addQualification">+</a>
addQualification() {
this.qualification.push({ text: '' });
}
addJob() {
let job = Object.assign({}, this.job);
job.qualifications = this.qualification.map(q => q.text);
this.$http.post('https://t2w-api.herokuapp.com/jobs/', job).then(response => {....
Full source for my JobAdd.vue: https://github.com/markusdanek/t2w-vue/blob/master/src/components/backend/JobAdd.vue
this.qualification.push({ text: '' }); doesnt work obviously not in my JobEdit.vue when there are already strings in my job.qualifications.
Change your removeQualifiaction method to use the index being passed in:
removeQualifiaction(index) {
this.qualifications.splice(index, 1);
}

Angular - post a comment to database

I am trying post a comment from an input form to my database. The comment I am passing in currently logs as undefined. The service doesn't log and I get a 500 server error: user_id violates not null constraint. What am I doing incorrectly?
HTML(updated)
<div ng-controller="CommentsCtrl as commentsCtrl">
<div ng-show="makeComment" class="collapsible-header">
<input ng-model="comment" type="text" name="comment" id="comment_content" class="col s11 m11 l11" placeholder="Make a comment.">
<i class="material-icons">check_circle</i>
</div>
Controller - CommentsCtrl(Updated)
this.comment = '';
createComment(comment, userId, postId) {
return this.commentsService.createComment(comment, userId, postId);
}
}
Service - CommentsService(Updated)
this.createcomment = $http;
this.commentContent = '';
this.postId;
this.userId;
createComment(comment, userId, postId) {
this.createcomment.post('/comments', { comment: this.commentContent, userId: this.userId, postId: this.postId })
.then((res) => {
this.comment = res.data;
})
.catch((err) => {
return err;
});
}
You are not passing any values for userId or postId:
this.commentsService.createComment(comment);
They are null.

How to insert an image into sql server database?

As said, i'm trying to insert an image in a table, where the type of the field is Varbinary.
What i've done so far :
I've a form with many fields:
#using (Html.BeginForm("InsertProduct", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PRODUCT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.PRODUCT_ID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PRODUCT_ID)
#Html.ValidationMessageFor(model => model.PRODUCT_ID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PRODUCT_NAME)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PRODUCT_NAME)
#Html.ValidationMessageFor(model => model.PRODUCT_NAME)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PRODUCT_IMAGE)
</div>
<div class="editor-field">
<input type="file" name="PRODUCT_IMAGE" id="PRODUCT_IMAGE" style="width: 100%;" />
</div>
<p>
<input type="submit" value="Create" class="btn btn-primary"/>
</p>
</fieldset>
}
And all these fields allow me to construct a PRODUCT object in my controller :
public ActionResult InsertProduct(PRODUCT ord)
{
MigrationEntities1 sent = new MigrationEntities1();
sent.PRODUCT.Add(ord);
sent.SaveChanges();
List<PRODUCT> Products = sent.PRODUCT.ToList();
return View("Products", Products);
}
But when i'm trying to upload the image (by clicking on Create button), i've the following :
entry is not a valid base64 string because it contains a character
that is not base 64
So first of all : is it the right way to deal with images and second, I think I need to do a pre-treatemant on my image to insert it : how to o that ?
Thanks !
Edit :
Thanks to answers received, seems to be good for insertion. But for displaying, I still have issues (only the "not found image" piture is displayed). I've try to do it two ways :
1.
<img src="LoadImage?id=#Model.product.PRODUCT_ID"/>
and in the controller
public Image LoadImage(int id)
{
String serviceAddress = ConfigurationManager.AppSettings["WCFADDRESS"];
DataServiceContext context = new DataServiceContext(new Uri(serviceAddress));
PRODUCT product = context.Execute<PRODUCT>(new Uri(serviceAddress + "prod_id?prod_id=" + id)).ToList().FirstOrDefault();
MemoryStream ms = new MemoryStream(product.PRODUCT_IMAGE);
Image img = Image.FromStream(ms);
return img;
}
And 2. :
#{
if (Model.product.PRODUCT_IMAGE != null)
{
WebImage wi = new WebImage(Model.product.PRODUCT_IMAGE);
wi.Resize(700, 700,true, true);
wi.Write();
}
}
But none of them are working. What am I doing wrong ?
1) Change your database table to have these columns:
1: ProductImage - varbinary(MAX)
2: ImageMimeType - varchar(50)
2) Change your action method like this:
public ActionResult InsertProduct(PRODUCT ord,
HttpPostedFileBase PRODUCT_IMAGE)
{
if (ModelState.IsValid)
{
MigrationEntities1 sent = new MigrationEntities1();
if (image != null)
{
ord.ProductImage= new byte[PRODUCT_IMAGE.ContentLength];
ord.ImageMimeType = PRODUCT_IMAGE.ContentType;
PRODUCT_IMAGE.InputStream.Read(ord.ProductImage, 0,
PRODUCT_IMAGE.ContentLength);
}
else
{
// Set the default image:
Image img = Image.FromFile(
Server.MapPath(Url.Content("~/Images/Icons/nopic.png")));
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Png); // change to other format
ms.Seek(0, SeekOrigin.Begin);
ord.ProductImage= new byte[ms.Length];
ord.ImageMimeType= "image/png";
ms.Read(ord.Pic, 0, (int)ms.Length);
}
try
{
sent.PRODUCT.Add(ord);
sent.SaveChanges();
ViewBag.HasError = "0";
ViewBag.DialogTitle = "Insert successful";
ViewBag.DialogText = "...";
}
catch
{
ViewBag.HasError = "1";
ViewBag.DialogTitle = "Server Error!";
ViewBag.DialogText = "...";
}
List<PRODUCT> Products = sent.PRODUCT.ToList();
return View("Products", Products);
}
return View(ord);
}
This action method is just for create. you need some works for edit and index too. If you have problem to doing them, tell me to add codes of them to the answer.
Update: How to show images:
One way to show stored images is as the following:
1) Add this action method to your controller:
[AllowAnonymous]
public FileContentResult GetProductPic(int id)
{
PRODUCT p = db.PRODUCTS.FirstOrDefault(n => n.ID == id);
if (p != null)
{
return File(p.ProductImage, p.ImageMimeType);
}
else
{
return null;
}
}
2) Add a <img> tag in the #foreach(...) structure of your view (or wherever you want) like this:
<img width="100" height="100" src="#Url.Action("GetProductPic", "Products", routeValues: new { id = item.ID })" />
Change the Image type on the sql sever to Byte[] and use something like this. This is how I have stored images in the past.
http://www.codeproject.com/Articles/15460/C-Image-to-Byte-Array-and-Byte-Array-to-Image-Conv
If not, you can always just store the image locally and pass the image location through a string into the SQL data base, this method works well and is quick to set up.
So, here are the modifications to do :
To insert data in the database :
[HttpPost]
public ActionResult InsertProduct(PRODUCT ord, HttpPostedFileBase image)
{
MigrationEntities1 sent = new MigrationEntities1();
if (image != null)
{
ord.PRODUCT_IMAGE = new byte[image.ContentLength];
image.InputStream.Read(ord.PRODUCT_IMAGE, 0, image.ContentLength);
}
sent.PRODUCT.Add(ord);
sent.SaveChanges();
List Products = sent.PRODUCT.ToList();
return View("Products", Products);
}
Note: this is the "light" way, for something that is more complete, have a look to Amin answer.
For displaying :
In the view
<img src="LoadImage?id=#Model.product.PRODUCT_ID"/>
And in the controller :
public FileContentResult LoadImage(int id)
{
String serviceAddress = ConfigurationManager.AppSettings["WCFADDRESS"];
DataServiceContext context = new DataServiceContext(new Uri(serviceAddress));
PRODUCT product = context.Execute<PRODUCT>(new Uri(serviceAddress + "prod_id?prod_id=" + id)).ToList().FirstOrDefault();
return new FileContentResult(product.PRODUCT_IMAGE, "image/jpeg");
}
And everything is ok now, thanks !

Resources