pass value from element inside ngFor to Service - html

I have a component which iterates through an array of areas which all have unique id's. When you click the button it opens a dialog which contains a iframe which listens to an eventlistener and retrieves data as JSON and sends it via post to a INSERT method in my ASP.NET CORE API.
I'm now also trying to send the id of the area and right now I only pass it as a paramter on the openDialog function.
planner.component.html
<div *ngFor="let area of areas">
<div class="area-container">
{{area.areaId}}
<button (click)="openDialog(area.areaId)" type="button" class="btn btn-primary btn-rounded">+</button>
</div>
</div>
planner.component.ts
The dialog opens with the iframe and right now only logs the id of the area.
openDialog(id: number) {
let dialogref = this.dialog.open(IframeComponent, {
width: '120vh'
});
console.log({areaId:id}) // like this {areaId: 20}
this.plannerService.getData(this.id);
dialogref.afterClosed().subscribe(result => {
console.log(`Dialog closed: ${result}`);
this.dialogresult = result;
})
}
in the service i have a eventListener which stores data from the iframe and sends it to my ASP.NET Core API as JSON
You can see here that I'm trying to send in an id but i don't know how to get the value from the planner.component which makes it undefined.
.planner.service.ts
getData(id: number) {
window.addEventListener("message", (e) => {
let data = JSON.parse(e.data)
console.log(data);
console.log({areaId: id})
this.createResource(data, id).subscribe(data => this.resources.push(data));
}, false);
}
data from iframe as JSON
{id: 282970, title: "page 1", description: "", thumbnail: "https://site/image/resourc…umbnail?guid=a0b67c13-ead4-49e0-a576-b4c8be1491b2", url: "https://site/l/show.html#PZWQz", …}
description: ""
guid: "a0b67c13-ead4-49e0-a576-b4c8be1491b2"
id: 282970
thumbnail: "https://site/image/resourcethumbnail?guid=a0b67c13-ead4-49e0-a576-b4c8be1491b2"
title: "page 1"
url: "https://site/l/show.html#PZWQz"
{areaId: undefined}
the question is how can a pass the value of the areaId inside the planner.component (which now only gets logged) to my service method so that i can pass it to my API?

In your component, you can inject your planner service and call its method
Assuming your service name is PlannerService
You component contructor would be like this:
constructor(private plannerService: PlannerService) {
}
And then in your method you can do this:
openDialog(id: number) {
let dialogref = this.dialog.open(IframeComponent, {
width: '120vh'
});
console.log({areaId:id}) // like this {areaId: 20}
this.plannerService.getData(id) <-- here id is the parameter you want to send
dialogref.afterClosed().subscribe(result => {
console.log(`Dialog closed: ${result}`);
this.dialogresult = result;
})
}

Related

Angular: Re-render asynchronous data in image tag

I am using Angular to fetch user profile picture from backend(Node.js/Express). Everything is working perfectly except for one thing. The Angular does not re-render the HTML that displays the profile picture incase, the user has updated his picture or if no picture is present and user uploads his first image. As expected, the Angular is rendering the HTML only once and isn't re-rendering again. I don't know how can I wait for asynchronous data in HTML as I am directly targeting an endpoint in HTML instead of TS.
Here's my code:
userProfile.component.html
<div class = "imgClass">
<img class = "img-thumbnail rounded-circle imgclass"
src="http://localhost:3000/api/getProfilePhoto?id={{cookieData}}">
//angular is sending request to the above endpoint to fetch the image only once at the time
application starts or user logs in. How can I send a request again?
<div class="middle">
<div class="text"><button type="button" name="button" (click) = "selectImage()" class = "btn
btn-outline-primary"> <i class="bi bi-plus"></i> </button></div>
<input type="file" id="imgUpload" (change) = "handleImageInput($event.target.files)">
</div>
</div>
userProfile.component.ts
selectImage()
{
document.getElementById('imgUpload').click();
}
handleImageInput(files: FileList)
{
this.imageUpload = files.item(0);
this.uploadImage();
}
uploadImage()
{
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {params, responseType: "text"})
.subscribe(responseData => {
this.imageChanged = true; //I have tried using this as *ngIf in HTML but it is not working either
}
,error => {
console.log("Image uploading failed" + error.message);
})
}
Does anybody know how can I send the request to an endpoint in HTML once user changes/uploads his first picture?
You need to trigger the image fetch request for each update/upload requests. Or you could adjust the backend to return the image data from the update/upload requests.
Option 1: manually fetch image for each update/upload requests
Use RxJS switchMap operator to switch to image fetch request after the uploading has completed. It'll not be fetched if the uploading failed.
profileImage: any;
selectImage() {
document.getElementById('imgUpload').click();
}
handleImageInput(files: FileList) {
this.imageUpload = files.item(0);
this.uploadImage();
}
uploadImage() {
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {
params,
responseType: "text"
}).pipe(
tap(null, error => console.log("Image uploading failed" + error.message)),
switchMap(_ => this.http.get(`http://localhost:3000/api/getProfilePhoto?id${this.cookieData}`))
).subscribe(
image => {
this.profileImage = image;
},
error => {
console.log("Image fetching failed" + error.message);
}
);
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">
Option 2: Return the image from upload/update request
Adjust the backend to return the image data from the Upload POST request.
profileImage: any;
uploadImage() {
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {
params,
responseType: "text"
}).subscribe(
image => {
this.profileImage = image;
},
error => {
console.log("Image uploading failed" + error.message);
}
);
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">
As a sidenote, using document.getElementById() in Angular will search the whole DOM, not just the individual component. In relatively complex apps, it might lead to performance issues. Instead try to use an event handler or if it's not possible, use Angular ViewChild with a template reference parameter to get an element from the current component's DOM.
if the webservice resolving the image url returns an Observable, you can make the call from typescript like below
imageData$: Observable<number>;
getImage(id): Observable<string> {
this.imageData$=http.get(url?id=<some_id>);
return this.imageData$
}
and the adding async pipe on it
<img class = "img-thumbnail rounded-circle imgclass" [src]="imageData$ | async">
Basically The async pipe subscribes to an Observable or Promise and
returns the latest value it has emitted. When a new value is emitted,
the async pipe marks the component to be checked for changes. When the
component gets destroyed, the async pipe unsubscribes automatically to
avoid potential memory leaks.

How to pass a thunk or callback function into a redux action. Serializing functions in a redux store for modals and toast confirm notifications

When using a generic modal or toast with a confirm button, it becomes useful to be able to pass an action into this component so it can be dispatched when you click confirm.
The action may look something like this:
export function showConfirm({modalConfirm}) {
return {
type: 'MODALS/SHOW_MODAL',
payload: {
modalId: getUuid(),
modalType: 'CONFIRM',
modalConfirm : modalConfirm,
},
};
}
Where modalConfirm is another action object such as:
const modalConfirm = {
type: 'MAKE_SOME_CHANGES_AFTER_CONFIRM',
payload: {}
}
The modalConfirm action is dispatched inside the modal component using dispatch(modalConfirm) or even dispatch(Object.assign({}, modalConfirm, someResultFromTheModal)
Unfortunatley this solution only works if modalConfirm is a simple redux action object. This system is clearly very limited. Is there anyway you can pass a function (such as a thunk) in instead of a simple object?
Ideally, something full featured likes this:
const modalConfirm = (someResultFromTheModal) => {
return (dispatch, getState){
dispatch({
type: 'MAKE_SOME_UPDATES',
payload: someResultFromTheModal
})
dispatch({
type: 'SAVE_SOME_STUFF',
payload: http({
method: 'POST',
url: 'api/v1/save',
data: getState().stuffToSave
})
})
}
}
Funny, putting an action object in the store and passing it as a prop to a generic dialog is exactly the approach I came up with myself. I've actually got a blog post waiting to be published describing that idea.
The answer to your question is "Yes, but....". Per the Redux FAQ at http://redux.js.org/docs/FAQ.html#organizing-state-non-serializable , it's entirely possible to put non-serializable values such as functions into your actions and the store. However, that generally causes time-travel debugging to not work as expected. If that's not a concern for you, then go right ahead.
Another option would be to break your modal confirmation into two parts. Have the initial modal confirmation still be a plain action object, but use a middleware to watch for that being dispatched, and do the additional work from there. This is a good use case for Redux-Saga.
I ended up using string aliases to an actions library that centrally registers the actions.
Modal emmiter action contains an object with functionAlias and functionInputs
export function confirmDeleteProject({projectId}) {
return ModalActions.showConfirm({
message: 'Deleting a project it permanent. You will not be able to undo this.',
modalConfirm: {
functionAlias: 'ProjectActions.deleteProject',
functionInputs: { projectId }
}
})
}
Where 'ProjectActions.deleteProject' is the alias for any type of complicated action such as:
export function deleteProject({projectId}) {
return (dispatch)=>{
dispatch({
type: 'PROJECTS/DELETE_PROJECT',
payload: http({
method: 'DELETE',
url: `http://localhost:3000/api/v1/projects/${projectId}`,
}).then((response)=>{
dispatch(push(`/`))
}),
meta: {
projectId
}
});
}
}
The functions are registered in a library module as follows:
import * as ProjectActions from '../../actions/projects.js';
const library = {
ProjectActions: ProjectActions,
}
export const addModule = (moduleName, functions) => {
library[moduleName] = functions
}
export const getFunction = (path) => {
const [moduleName, functionName] = path.split('.');
// We are getting the module only
if(!functionName){
if(library[moduleName]){
return library[moduleName]
}
else{
console.error(`Module: ${moduleName} could not be found.`);
}
}
// We are getting a function
else{
if(library[moduleName] && library[moduleName][functionName]){
return library[moduleName][functionName]
}
else{
console.error(`Function: ${moduleName}.${functionName} could not be found.`);
}
}
}
The modalConfirm object is passed in to the modal by props. The modal component requires the getFunction function in the module above. The modalConfirm object is transformed into a function as follows:
const modalConfirmFunction = (extendObject, modalConfirm) => {
const functionFromAlias = getFunction(modalConfirm.functionAlias);
if(functionFromAlias){
dispatch(functionFromAlias(Object.assign({}, modalConfirm.functionInputs, extendObject)));
}
}
As you can see, this function can take in inputs from the modal. It can execute any type of complicated action or thunk. This system does not break time-travel but the centralized library is a bit of a drawback.

Backbone.js Service Call Event Binding

I'm new to Backbone, and I am trying to do a get request (getDivisions) and store the response JSON into 'divisions', defined in my defaults. I logged 'divisions' inside the service call, and outside the service call, as seen below.
define(['underscore', 'backbone', 'service-manager', 'backbone-nested'],
function(_, Backbone, svgmgr) {
return Backbone.NestedModel.extend({
defaults: {
message: "",
divisions: []
},
initialize: function () {
this.getDivisions();
},
getDivisions: function() {
var that = this;
svgmgr.Interface.call('getDivisions').done(function(data) {
that.set('divisions', data);
console.log("Inside the service call: " + that.get('divisions'));
});
console.log("Outside service call" + this.get('divisions'));
}
});
});,
In Dev Tools, the 'Outside the service call' log was called first, returning a blank array (it's default), while the 'Inside the service call' log was called after that, returning the correct response data. This is obviously not what I want.
How do I get this model to run this service call on initialize, so that when I reference 'divisions' I get back the response data?
First you don't need a Model but a Collection, and second Backbone can handle the ajax call for you.
So you have to do like this :
var Division = Backbone.Model.extend({
urlRoot: /* url to get a single division */,
defaults: {...}
});
var Divisions = Backbone.Collection.extend({
url: /* url to get all your divisions */,
model: Division,
initialize: function () {
this.fetch({
success: function(response) {
// you get the result here
}
});
}
});

check the json data for comparing in extjs

I created a simple login page using extjs MVC to understand MVC architecture of extjs. As you can see below, I am trying to get the json data into the store and then I will check each username and password in that data with the entered login credentials. The thing in which I am confused right now is that, how to check the username and password from the retrieved json data present in store folder into the view folder? (Below code is only the related code with the problem)
I aware that this could invoke security threats, as I am checking on client side.
'view' folder --> Code.js
function checkJson(username, password){
//if matched, return true.
//else, return false.
}
'model' folder --> Code.js
Ext.define('AM.model.User', {
extend: 'Ext.data.Model',
fields: ['name', 'email']
});
'store' folder --> Code.js
Ext.define('LoginPage.store.Code', {
extend: 'Ext.data.Store',
model: 'LoginPage.model.Code',
autoLoad: true,
proxy: {
type: 'ajax',
api: {
read: 'data/loginResponse.json',
update: 'data/checkCredentials.json' //Contains: {"success": true}
},
reader: {
type: 'json',
root: 'loginResponse',
successProperty: 'success'
}
}
});
loginResponse.json
{
"form": {
"login": [
{
"username": "venkat",
"password": "123"
},
{
"username": "admin",
"password": "345"
}
]
}
You should put your checking part of the code to the Controller (views are for presentation). In view define some form with login and password fields. In Controller catch click event on form OK (Login) button, get form values (login + password), then use Ext.data.Store.query() method to find wether credentials fits or not like:
Look here for examples how to use controllers in MVC to catch events;
In your Controller put:
init: function() {
this.control({
'#form_ok_button': { // this is the `id` property of your form's Login button
click: function(button) {
var fValues = button.up('form').getValues(); // Assume your button is bound to the form
// Or you can use `Controller.refs` property (http://docs.sencha.com/ext-js/4-1/#!/api/Ext.app.Controller-cfg-refs) to get form
var matched = store.query('username', fValues.username);
if(matched.length && matched[0].get('password') === fValues.password) {
// login OK!
}
}
}
});
},
How to use refs (in Controller):
refs: [
{ ref: 'usernameField', selector: '#username_field' }, // username field id is "username_field"
{ ref: 'passwordField', selector: '#password_field' }, // password field id is "password_field"
],
init: function() {
this.control({
'#form_ok_button': {
click: function() {
// with `refs` autogetters are created for every `ref`:
var username_field = this.getUsernameField();
var password_field = this.getPasswordField();
}
}
})
}
You can read about referencing here.
For every Store in Ext.app.Controller.stores array autogetters are created too (in your case for Code store use this.getCodeStore() inside controller).
Here is the flow:
You get username and password field values with this.getUsernameField() and this.getPasswordField();
You query() store for username
If username exist in store, you check if password fits.

create dijit accordian container from foreach loop with data returned from json

how do i use a for each loop to create accordian containers for each data returned from json file using ajax ?
i have tried this ! is it the way to it ?
dojo.xhrGet({
url:"json/"file_Name".json",
handleAs: "json",
timeout: 10000,
load: function(response,details){
container(response)},
error: function(error_msg,details){
container(error_msg, details);
}
});
//how do i use the json file to add data to the array arrFruit and then create dijit accordian container for every data in the array//
container = function(array, domConstruct) {
var arrFruit = ["apples", "kiwis", "pineapples"];
array.forEach(arrFruit, function(item, i){
domConstruct.create("li", {innerHTML: i+1+". "+item}, "properties");
});
};
//the response data from my json file is:-
[
{
"itemId": 1234,
"Name": "Name",
}]
I would suggest you re-write it into leveraging the ItemFileReadStore. The Store is a data container in which you can pull out items by their id. This means that your json needs to be changed slightly with description of what is identifier and - if any - which is the children attribute keys.
JSON:
{
identifier: 'itemId',
// you dont seem to have child references any but these are defaults
childrenAttrs: ['items', 'children'],
items: [
{
itemId: 1234,
name: 'Name'
}, {
...
}
]
}
then in your code, instead of using .xhr use .fetch in a store like so:
// 1.7+ syntax, pulling in dependencies.
// We want accordion and some other display widget like contentpane.
// Also we await domReady before calling our function
require(["dojo/data/ItemFileReadStore", "dijit/layout/AccordionContainer", "dijit/layout/ContentPane", "dojo/domReady!"], function(itemStore, accordion, contenpane) {
// this section is called when loading of itemstore dependencies are done
// create store
var store = new itemStore({
url:"json/"file_Name".json"
});
// create container
var acc = new accordion({style:"height: 300px"}, 'someDomNodeId');
// call XHR via .fetch and assign onComplete (opposed to 'load') callback
store.fetch({ onComplete: function(items) {
// items is an array of all the objects kept in jsonObject.items array
items.forEach(function(item) {
acc.addChild(new contentpane({
title: "Name for id: " + store.getValue(item, 'itemId'),
content: store.getValue(item, 'name')
}));
});
console.log(acc.getChildren()); // << would print out an array of contentpane widgets
});
});
This is howto :)
At any given time you could use the store and fetch some items, lets say you want to filter out some specific ones, call .query like so: store.fetch({query: { name: '*foo'/*all item.name ending with foo*/ }, onComplete: function(items) { /*cb func*/});
See
http://livedocs.dojotoolkit.org/dijit/layout/AccordionContainer#programmatic-example
and
http://livedocs.dojotoolkit.org/dojo/data/ItemFileReadStore