I am an absolute beginner and am self taught using Ionic3
I have a problem that is driving me wild that I hope someone can assist on.
I have a master-detail-detail setup where the following happens:
Master page has a list of reports (taken from a JSON file), click on that report and it takes to a details page, click on that and it opens up another page with even more details, without having to define all the individual parts.
All I want to do is simply pass the whole object from the second page to the third page so that I can use its parameters again
master page (Report),
Second page (Reportmenu),
Third page (GenOverview)
So passing between master and second page is fine and works as it should using navparams (not shown here) but I want to use that same object and pass all the data again to the 3rd page.
I thought it would be just as simple as assigning it to a new variable and then passing that again using navparams but I get undefined
export class ReportmenuPage {
name: any;
overallscore: any;
reportdate: any;
coach: any;
age: any;
TechOverall: any;
TactOverall: any;
PhysOverall: any;
PsychOverall: any;
Logo: any;
data2: any;
constructor(public navCtrl: NavController, public navParams: NavParams,
public postsService: Posts, private toastCtrl: ToastController) {
this.overallscore = this.navParams.get('OverallScore');
this.reportdate = this.navParams.get('ReportDate');
this.name = this.navParams.get('Name');
this.coach = this.navParams.get('Coach');
this.age = this.navParams.get('Age');
this.TechOverall = this.navParams.get('TechOverall');
this.TactOverall = this.navParams.get('TactOverall');
this.PhysOverall = this.navParams.get('PhysOverall');
this.PsychOverall = this.navParams.get('PsychOverall');
this.Logo = this.navParams.get('Logo');
console.log(this.navParams.data);
this.data2 = this.navParams.data;
}
myClickHandlerOverview(data2) {
this.navCtrl.push(GenOverviewPage, data2);
So this is all fine and gives an expected output
So all I want to do now is get this to the GenOverviewPage
Here is the Reportmenu.html
<ion-item (click)="myClickHandlerOverview(data2)" color="primary">
<ion-icon name="arrow-dropright" item-end></ion-icon>
GENERAL OVERVIEW
</ion-item>
And Finally the bit that doesn't work
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { ReportmenuPage } from '../reportmenu/reportmenu';
#IonicPage()
#Component({
selector: 'page-gen-overview',
templateUrl: 'gen-overview.html',
})
export class GenOverviewPage {
constructor(public navCtrl: NavController, public navParams: NavParams) {
let data3 = this.navParams.data;
console.log(data3); //this shows as {}
}
ionViewDidLoad() {
console.log('ionViewDidLoad GenOverviewPage');
}
}
I am pretty certain I am going to get ridiculed as I think this relates to my lack of understanding of objects/ arrays and how they are passed, but I have searched high and low and cannot grasp what I am doing wrong.
and why console.log(data3) shows the second empty array but as you can see I can transfer it fine between page 1 and 2
Many thanks
In your second page, you are saving the data in a local variable that is contained within your constructor.
let data2 = this.navParams.data;
You need to save it in the class variable to be passed to your myClickHandlerOverview. Change it to:
this.data2 = this.navParams.data;
Also may need to change datatype from array to object or any.
data2: any;
A better approach seems to be to store it in a common provider.
Very simple in the end thanks to Suraj
I was missing a this!
myClickHandlerOverview(data2) {
this.navCtrl.push(GenOverviewPage, this.data2);
Related
I have a nestjs project that is mostly in RESTful structure. Everything works fine, but my concern is that some of the routes check for the presence of some query parameters to fetch data.
for instance
#Get('/some-resources')
async getSomeResource(
#Query() query: any
): Promise<HTTPResponseDTO>{
const startDate = query.startDate ? DateTime.fromISO(query.startDate).startOf('day').toISO(): null;
const endDate = query.endDate ? DateTime.fromISO(query.endDate).endOf('day').toISO() : null;
.
.
.
const result = await this.someResourceService.findAll(startDate, endDate,...)
}
Now my question is, is there a cleaner approach to this? Because this can get become a pain to maintain when we have many resources.
As mentioned by Micael Levi, you should be able to do this by creating your own custom pipe. Assuming that what you posted works, you should be able to do something along the lines of:
#Get('/some-resources')
async getSomeResource(
#Query('startDate', ParseDateIsoPipe) startDate?: string,
#Query('endDate', ParseDateIsoPipe) endDate?: string
): Promise<HTTPResponseDTO>{
<code>
}
With your ParseDateIsoPipe as follows (Note that you will still need to import DateTime from the package you are using):
import { PipeTransform, Injectable, ArgumentMetadata } from '#nestjs/common';
#Injectable()
export class ParseDateIsoPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value ? DateTime.fromISO(value).startOf('day').toISO(): null;
}
}
You can use the built-in validation pipe: https://docs.nestjs.com/techniques/validation with the auto validation feature.
The Problem
We're authoring an npm package containing React components that will be used in various (internal) web sites. There is a problematic npm package dependency that we are forced to use in our react .tsx files, that has these problems:
It doesn't expose any useful types despite having .d.ts files in it... they're empty.
It tries to run when required or imported server-side, instead of waiting until called, so we have to avoid a top-level import and instead do if (window) { const module = require('package-name') } and then use it inside that block only.
It is a frequent source of errors so everything in that library needs to be run inside of a try ... catch block.
Well, At Least We Have Types
We have already created our own types file which addressed problem #1:
// problematic-package-types.d.ts
declare module 'problematic-package' {
function doErrorProneButNecessaryThing(
foo: Record<string, unknown>,
bar: string
): void
}
The Needed Solution
The long term solution is to fix this problematic library and we're looking into how to get that done (but it's not in our direct control).
In the short term, though, we need a solution now.
Note that we are configuring dynamic requires in our npm package bundler to import them only at use-time, not treating them like other imports/requires. As our package is consumed inside other applications, we don't have full control over how that application bundling works or when the components are required, so our components may end up being required server-side when they shouldn't, and we have to tolerate that. We're still learning about some aspects of this.
My Wild (But Failed) Stab
My goal is to do something more DRY like this, where we solve all three problems of strong typing, detecting server-side execution & doing nothing, and adding error handling:
// hoping to leverage our module declaration above without importing anything
import type * as ProblematicPackage from 'problematic-package'
import wrapProblematicRequire from '../utils/my-sanity-preserving-module'
const wrappedProblematicPackage = wrapProblematicRequire<ProblematicPackage>()
// then later...
const foo: Record<string, unknown> = { property1: 'yes', property2: false }
const bar = 'yodeling'
wrappedProblematicPackage.invokeIfWindowReady(
'doErrorProneButNecessaryThing',
foo,
bar
)
However, TypeScript doesn't like the import type which unfortunately makes sense:
Cannot use namespace 'ProblematicPackage' as a type.
The Plea
How do I get the type information we've placed into problematic-package-types.d.ts to use as desired?
Or ANYTHING else. Honestly, I'm open to whatever, no matter how crude or hacky, so long as we get some clarity and reliability at call sites, with full type information as described. Suggestions/advice?
Full Details
Here is the full implementation of the wrapProblematicRequire function. I haven't tested it. It's probably awful. I'm sure it could be far better but I don't have time to get this helper module super clean right now. (My attempt to handle function type information isn't quite right.)
type Func = (...args: any[]) => any
type FunctionNames<T, TName extends keyof T> = T[TName] extends Func ? TName : never
type FunctionNamesOf<T> = FunctionNames<T, keyof T>
const wrapProblematicRequire = <T>(packageName: string) => ({
invokeIfWindowReady<TName extends FunctionNamesOf<T>>(
name: T[TName] extends Func ? TName : never,
...args: T[TName] extends Func ? Parameters<T[TName]> : never
): T[TName] extends Func ? ReturnType<T[TName]> : never {
if (!window) {
// #ts-ignore
return undefined
}
try {
// #ts-ignore
return require(packageName)[name] as T[TName](...args)
} catch (error: unknown) {
// ToDo: Log errors
// #ts-ignore
return undefined
}
}
})
export default wrapProblematicRequire
P.S. await import('problematic-package') didn't seem to work. Yes, problems abound.
Cannot use namespace 'ProblematicPackage' as a type.
Well, you can get the typeof that namespace, which seems to be what you want.
To test this, I setup the following:
// problem.js
export function doErrorProneButNecessaryThing(n) {
return n;
}
export function doErrorProneButNecessaryThing2(s) {
return s;
}
console.log('did side effect');
// problem.d.ts
export function doErrorProneButNecessaryThing(n: number): number;
export function doErrorProneButNecessaryThing2(s: string): string;
And now you can do:
import type * as ProblemNs from './problem';
type Problem = typeof ProblemNs;
// works
type A = Problem['doErrorProneButNecessaryThing'] // type A = (n: number) => number
Then the wrapProblematicRequire function just takes the name of the function as a generic, pulls the args for it, and pulls the return type.
const wrapProblematicRequire = <TName extends FunctionNamesOf<Problem>>(
name: TName,
...args: Parameters<Problem[TName]>
): ReturnType<Problem[TName]> | undefined => {
if (!window) return;
const problem = require('./problem'); // type is any, but types are enforced above
try {
return problem[name](...args);
} catch (err) {
console.log('error!');
}
};
Here require('./problem') returns the any type, but the generics keep everything key safe as long as typeof ProblemNs can be trusted.
Now to test that:
console.log('start');
const result: number = wrapProblematicRequire(
'doErrorProneButNecessaryThing',
123
);
console.log('end');
Which logs:
start
did side effect
end
Which seems to work!
Codesandbox
Is there a way to update a string variable when another variable changes? I have a string that is built by using various variables. I display that string in the component's html file using interpolation. However, if a variable changes that the string was using to build itself, the string will never change because they not mutable. Only way would be to reset the string again when one of the other variables change.
Sample ts code:
import { Component} from '#angular/core';
#Component({
selector: 'app-test-component',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent {
myString = '';
amount = 0;
constructor() {
this.myString = `Hello, you have ${this.amount} dollars.`;
setTimeout(() => {
this.amount = 40;
}, 5000);
}
}
Sample html code:
<p>{{ myString }}</p>
Of course, after five seconds, the string stays the same because I never reinitialized it. Is there a way to automatically detect if any of the variables being used inside myString change, then update myString to use the values of the changed variables?
Thank you, any information is appreciated!
You can do that using BehaviorSubject, which has a notion of "the current value". It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately receive the "current value" from the BehaviorSubject.
Create separate service for amount data.
service.ts
//set the initial value here
amount=new BehaviorSubject(0);
component.ts
ChangeDetectorRef
What it means if there is a case where any thing inside your model (
your class ) has changed but it hasn't reflected the view, you might
need to notify Angular to detect those changes ( detect local changes)
and update the view.
constructor(private ref: ChangeDetectorRef,subj:SubjectService) {
subj.amount.subscribe((data)=>{
this.amount=data;
this.myString = `Hello, you have ${this.amount} dollars.`;
//check for changes then it will update the view
this.ref.markForCheck();
})
setTimeout(() => {
//change amount data after 1 second
subj.amount.next(40);
}, 1000);
}
example here: https://stackblitz.com/edit/changedetection-using-subject?file=src%2Fapp%2Fapp.component.ts
I've seen tons of partial information but nothing that explains the full process of how to add a drag and drop upload to angular2. I've looked at ng2-file-upload and angular2-file-drop and file-droppa. They are all very easy to get the front end working, but with no explanation on the backend its been impossible to fully implement.
My latest attempt has been with file-droppa. I really like the look and feel of this one. (https://github.com/ptkach/fileDroppa)
So I have it deployed in so much as I can drag an image into the drop zone, see the thumbnail show up and click the Upload All Files button. What I'm missing is the magic required to get any information about the file in the backend of my application to be able to actually upload it and do anything with it.
I'm using NodeJS on the backend. The method for /upload gets called, but nothing is being passed into it.
Anyone know how to make this work?
assets.component.ts
import FileDroppa from 'file-droppa/components/Directives/FileDroppa';
#Component({
moduleId: module.id,
selector: 'pc-assets',
templateUrl: 'assets.component.html',
styleUrls: ['assets.component.css'],
directives: [FILE_UPLOAD_DIRECTIVES, NgClass, NgStyle, CORE_DIRECTIVES, FORM_DIRECTIVES, FileDroppa]
})
export class AssetsComponent implements OnInit {
#Input() asset:Asset;
#Input() uploads: Upload[];
constructor(private assetService: AssetService,
private errorService: ErrorService,
private http: Http,
private formBuilder:FormBuilder,
private uploadFileService: UploadFileService) { }
assets: Asset[] = [];
selectedAsset: Asset;
assetFilesToUpload: Array<File>;
assetUploaded = false;
assetUploadFile: any;
assetUploadObject: any;
uploadFile: any;
assetFileChangeEvent(fileInput: any){
this.assetFilesToUpload = <Array<File>> fileInput.target.files;
}
fileUploaded(success, response, file){
success && console.log("uploaded - awesome", response, file);
success || console.log("not uploaded - very bad", response, file);
}
}
assets.component.html
<fileDroppa
[url]="'http://localhost:3000/upload'"
[showFilesList]="true"
(fileUploaded)="fileUploaded($event)"
>
<!--<h1>-->
<!--You can pass anything you want here-->
<!--You can set you own file list here-->
<!--</h1>-->
</fileDroppa>
You can use ngx-file-drop package of node for implement drag and drop functionality :
hear is link for that : ngx-file-drop
documentation is very easy with example.
Trying to use a property to configure a WinJS control from within Angular2, so far I couldn't find a solution, e.g. this code below is throwing 'Can't bind to 'dataWinOptions' since it isn't a known property of the '' element'.
#View({
template: `<div id="rating" data-win-control='WinJS.UI.Rating' [data-win-options]='jsonRating'></div>`
})
class MyRating {
rating: number;
get jsonRating() {
return '{averageRating: ' + this.rating + '}';
}
constructor() {
this.rating = 1.5;
}
}
Any hint?
#ericdes about your last comment I think this would be the best option. Assuming you have Nth WinJS controls
Consider the following code. I'm specifying differents values for the averageRating property in options.
<winjs-control [options]="{averageRating: '1.5', someMoreOptions : 'x'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.4', differentOptionsForThisOne :'Z'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.3'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.2'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.1'}"></winjs-control>
// more and more...
The component will read this options property and will pass it to the view. Forget about the directive, it isn't necessary after all.
We pass options through attr.data-win-options since it isn't a property of div but an attribute.
#Component({
selector : 'winjs-control',
properties : ['options']
})
#View({
template : `<div data-win-control="WinJS.UI.Rating" [attr.data-win-options]="jsonRating"></div>`,
})
class WinJSComponent implements OnInit, AfterViewInit {
constructor() {}
// We specify onInit so we make sure 'options' exist, at constructor time it would be undefined
// And we stringify it or otherwise it will pass an object, we need to convert it to a string
onInit() {
this.jsonRating = JSON.stringify(this.options);
}
// We process WinJS after view has been initialized
// this is necessary or 'data-win-options' won't be fully processed
// and it will fail silently...
afterViewInit() {
WinJS.UI.processAll();
}
}
Here's a plnkr for this case.
That's one option and IMHO I think this is the easiest one. Another one, having the same HTML content, would be to communicate the parent with its children and I haven't tested your case with that approach.