Submitting a form on a different component - forms

I have created a model based form on a component, however the submit button is placed on a different component.
right now when you press the button this function is triggerd:
#Input()
form: any;
...
if(this.form.valid)
this.saveDefinition();
however I want to "send a signal" back to my component with the form if the form is invalid. If the form is invalid it should highlight all the invalid fields (Like it would when you would submit the form on the same component).
I've searched for a while and I can't even find out if this is possible, if it is possible can you guys help me get on my way?
thanks

you can use services, as follow:
form.service.ts file
#Injectable()
export class FormService {
onFormSubmitted = new EventEmitter<any>();
}
then register this service in the appModule providers appModue.ts file
providers: [
...
FormService
...
]
firstComponent.ts file
#Component({
...
});
export class FirstComponent implements{
constructor( public formService:FormService) {}
onFormSubmit(formData:any) {
this.formService.onFormSubmitted.emit(formData);
}
}
secondComponent.ts file
export class SecondComponent implements OnInit{
constructor ( public formService:FormService ) {}
ngOnInit() {
this.formService.onFormSubmitted.subscribe( (formData : any ) => {
this.onFormSubmit(formData);
})
}
onFormSubmit(formData : any) {
// your logic for submitting the form
}
}
the first and the second component have the same instance of FormService, so they have the same instance of the eventEmitter, the first component emitting the event when the form is submitting and the second component is listening to it.
its called cross-component communication using services.

Related

Ionic 4 Angular 7 - passing object/data to another page [duplicate]

This question already has answers here:
Ionic 4. Alternative to NavParams
(8 answers)
Closed 4 years ago.
I would like to pass a JSON object to another page. What I've tried is to pass the JSON string using Angular router ActivatedRoute like this:
this.router.navigate(['details', details]);
and then retrieve it like this:
import { ActivatedRoute } from '#angular/router';
constructor(private activatedRoute: ActivatedRoute) {
}
ngOnInit() {
this.activatedRoute.params.subscribe(extras => {
console.log(extras);
this.JSONObject = extras;
});
}
It is possible to do it this way but what happened was the nested JSON objects becomes inaccessible. It turns into this string:
"[object Object]"
The stringified JSON object is fine and accessible before I pass it. Another problem is that it appends the JSON string to the url so it doesn't look that nice. From what I read as well, it is not a good practice to pass something more than just an id this way.
I am thinking of something like passing objects as intent extras between activities in Android. I've searched the documentations, forums, and previous stackoverflow questions but I didn't found any solution that enables me to achieve this. Is there any other way of passing objects between pages using Angular router in Ionic4?
I solved it using a service with a simple setter and getter just like in this question that I later found:
Ionic 4. Alternative to NavParams
First, I create a service with setter & getter:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class NavExtrasService {
extras: any;
constructor() { }
public setExtras(data){
this.extras = data;
}
public getExtras(){
return this.extras;
}
}
Let's say I'm navigating from page A to page B, in page A:
this.navExtras.setExtras(extras)
this.router.navigateByUrl('page-b');
Then in Page B, I retrieve the extras this way:
this.location = navExtras.getExtras();
It works so far although I'm still not sure if there are better ways to do it..

Paths/links with unique ID's angular2

I'm creating a quiz making/sharing website using angular2 but am not sure how to share the quizzes. i was thinking of using each of my quizzes identifiers as URLs. The quizzes are made using forms and are saved on a docmentdb as JSON. they have unique ID's to identify them. Any ideas as to how i could do this?
Those URLs must be dynamically created, as new quizzes can be submitted and thereafter accessed.
You can use the same base url for your page with quiz, but differentiate quizzes by path parameter like:
quizsite.com/#/quiz/12 (12 being quiz id)
Inside your component you can read what path parameters are located in your url and their values by accessing ActivateRoute object:
Component
import { Component } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
templateUrl: 'quizComponent.html',
})
export class QuizComponent {
constructor(private activatedRoute: ActivatedRoute){}
ngOnInit() {
// Reason for this being a observable is that you can watch
parameter changing (manually in url, or programmatically)
and without any page refresh, read new parameter and change you quiz
this.activatedRoute.params.subscribe(params => {
console.log(params.quizId);
// With this parameter you can make call to your REST API and return
data for that quiz
});
}
}

How do I declare a model class in my Angular 2 component using TypeScript?

I am new to Angular 2 and TypeScript and I'm trying to follow best practices.
Instead of using a simple JavaScript model ({ }), I'm attempting to create a TypeScript class.
However, Angular 2 doesn't seem to like it.
My code is:
import { Component, Input } from "#angular/core";
#Component({
selector: "testWidget",
template: "<div>This is a test and {{model.param1}} is my param.</div>"
})
export class testWidget {
constructor(private model: Model) {}
}
class Model {
param1: string;
}
and I'm using it as:
import { testWidget} from "lib/testWidget";
#Component({
selector: "myComponent",
template: "<testWidget></testWidget>",
directives: [testWidget]
})
I'm getting an error from Angular:
EXCEPTION: Can't resolve all parameters for testWidget: (?).
So I thought, Model isn't defined yet... I'll move it to the top!
Except now I get the exception:
ORIGINAL EXCEPTION: No provider for Model!
How do I accomplish this??
Edit: Thanks to all for the answer. It led me to the right path.
In order to inject this into the constructor, I need to add it to the providers on the component.
This appears to work:
import { Component, Input } from "#angular/core";
class Model {
param1: string;
}
#Component({
selector: "testWidget",
template: "<div>This is a test and {{model.param1}} is my param.</div>",
providers: [Model]
})
export class testWidget {
constructor(private model: Model) {}
}
I'd try this:
Split your Model into a separate file called model.ts:
export class Model {
param1: string;
}
Import it into your component. This will give you the added benefit of being able to use it in other components:
Import { Model } from './model';
Initialize in the component:
export class testWidget {
public model: Model;
constructor(){
this.model = new Model();
this.model.param1 = "your string value here";
}
}
Access it appropriately in the html:
#Component({
selector: "testWidget",
template: "<div>This is a test and {{model.param1}} is my param.</div>"
})
I want to add to the answer a comment made by #PatMigliaccio because it's important to adapt to the latest tools and technologies:
If you are using angular-cli you can call ng g class model and it will generate it for you. model being replaced with whatever naming you desire.
The problem lies that you haven't added Model to either the bootstrap (which will make it a singleton), or to the providers array of your component definition:
#Component({
selector: "testWidget",
template: "<div>This is a test and {{param1}} is my param.</div>",
providers : [
Model
]
})
export class testWidget {
constructor(private model: Model) {}
}
And yes, you should define Model above the Component. But better would be to put it in his own file.
But if you want it to be just a class from which you can create multiple instances, you better just use new.
#Component({
selector: "testWidget",
template: "<div>This is a test and {{param1}} is my param.</div>"
})
export class testWidget {
private model: Model = new Model();
constructor() {}
}
export class Car {
id: number;
make: string;
model: string;
color: string;
year: Date;
constructor(car) {
{
this.id = car.id;
this.make = car.make || '';
this.model = car.model || '';
this.color = car.color || '';
this.year = new Date(car.year).getYear();
}
}
}
The || can become super useful for very complex data objects to default data that doesn't exist.
.
.
In your component.ts or service.ts file you can deserialize response data into the model:
// Import the car model
import { Car } from './car.model.ts';
// If single object
car = new Car(someObject);
// If array of cars
cars = someDataToDeserialize.map(c => new Car(c));
In your case you are having model on same page, but you have it declared after your Component class, so that's you need to use forwardRef to refer to Class. Don't prefer to do this, always have model object in separate file.
export class testWidget {
constructor(#Inject(forwardRef(() => Model)) private service: Model) {}
}
Additionally you have to change you view interpolation to refer to correct object
{{model?.param1}}
Better thing you should do is, you can have your Model Class define in different file & then import it as an when you require it by doing. Also have export before you class name, so that you can import it.
import { Model } from './model';
my code is
import { Component } from '#angular/core';
class model {
username : string;
password : string;
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
username : string;
password : string;
usermodel = new model();
login(){
if(this.usermodel.username == "admin"){
alert("hi");
}else{
alert("bye");
this.usermodel.username = "";
}
}
}
and the html goes like this :
<div class="login">
Usernmae : <input type="text" [(ngModel)]="usermodel.username"/>
Password : <input type="text" [(ngModel)]="usermodel.password"/>
<input type="button" value="Click Me" (click)="login()" />
</div>
You can use the angular-cli as the comments in #brendon's answer suggest.
You might also want to try:
ng g class modelsDirectoy/modelName --type=model
/* will create
src/app/modelsDirectoy
├── modelName.model.ts
├── ...
...
*/
Bear in mind:
ng g class !== ng g c
However, you can use ng g cl as shortcut depending on your angular-cli version.
I realize this is a somewhat older question, but I just wanted to point out that you've add the model variable to your test widget class incorrectly. If you need a Model variable, you shouldn't be trying to pass it in through the component constructor. You are only intended to pass services or other types of injectables that way. If you are instantiating your test widget inside of another component and need to pass a model object as, I would recommend using the angular core OnInit and Input/Output design patterns.
As an example, your code should really look something like this:
import { Component, Input, OnInit } from "#angular/core";
import { YourModelLoadingService } from "../yourModuleRootFolderPath/index"
class Model {
param1: string;
}
#Component({
selector: "testWidget",
template: "<div>This is a test and {{model.param1}} is my param.</div>",
providers: [ YourModelLoadingService ]
})
export class testWidget implements OnInit {
#Input() model: Model; //Use this if you want the parent component instantiating this
//one to be able to directly set the model's value
private _model: Model; //Use this if you only want the model to be private within
//the component along with a service to load the model's value
constructor(
private _yourModelLoadingService: YourModelLoadingService //This service should
//usually be provided at the module level, not the component level
) {}
ngOnInit() {
this.load();
}
private load() {
//add some code to make your component read only,
//possibly add a busy spinner on top of your view
//This is to avoid bugs as well as communicate to the user what's
//actually going on
//If using the Input model so the parent scope can set the contents of model,
//add code an event call back for when model gets set via the parent
//On event: now that loading is done, disable read only mode and your spinner
//if you added one
//If using the service to set the contents of model, add code that calls your
//service's functions that return the value of model
//After setting the value of model, disable read only mode and your spinner
//if you added one. Depending on if you leverage Observables, or other methods
//this may also be done in a callback
}
}
A class which is essentially just a struct/model should not be injected, because it means you can only have a single shared instanced of that class within the scope it was provided. In this case, that means a single instance of Model is created by the dependency injector every time testWidget is instantiated. If it were provided at the module level, you would only have a single instance shared among all components and services within that module.
Instead, you should be following standard Object Oriented practices and creating a private model variable as part of the class, and if you need to pass information into that model when you instantiate the instance, that should be handled by a service (injectable) provided by the parent module. This is how both dependency injection and communication is intended to be performed in angular.
Also, as some of the other mentioned, you should be declaring your model classes in a separate file and importing the class.
I would strongly recommend going back to the angular documentation reference and reviewing the basics pages on the various annotations and class types:
https://angular.io/guide/architecture
You should pay particular attention to the sections on Modules, Components and Services/Dependency Injection as these are essential to understanding how to use Angular on an architectural level. Angular is a very architecture heavy language because it is so high level. Separation of concerns, dependency injection factories and javascript versioning for browser comparability are mainly handled for you, but you have to use their application architecture correctly or you'll find things don't work as you expect.
create model.ts in your component directory as below
export module DataModel {
export interface DataObjectName {
propertyName: type;
}
export interface DataObjectAnother {
propertyName: type;
}
}
then in your component import above as,
import {DataModel} from './model';
export class YourComponent {
public DataObject: DataModel.DataObjectName;
}
your DataObject should have all the properties from DataObjectName.

How to manually create and wire a service at runtime to a component in typescript Angular 2

Here's my use case:
I have an interface with 4 different implementations WorkflowA, WorkflowB, WorkflowC and WorkflowD.
export interface IWorkflow {
getQuestions();
validateQuestions();
getWorkflowSteps();
}
I have a WorkflowManager class that returns the appropriate workflow based on an input variable (Factory pattern?)
export class WorkflowManager {
workflow:IWorkflow;
getWorkflow(workflow){
switch (workflow) {
case 'workflowA':
return new workflowA();
case 'workflowB':
return new workflowB();
case 'workflowC':
return new workflowC();
case 'workflowD':
return new workflowD();
default:
throw new Error(`Unrecognized workflow: ${workflow}`);
}
}
}
I also have a component WorkflowComponent that gets loaded
Example:
/workflow=WorkflowA loads WorkflowComponent. The component then extracts the routeparam workflow and based on the value, it then passes it to the WorkflowManager which returns the appropriate workflow.
Here's a snippet of my component
#Component({
...
})
export class Workflow implements OnInit {
workflowInstance: IWorkflow;
constructor(
private route: ActivatedRoute,
private workflowManager: WorkflowManager,
) {
}
ngOnInit() {
this.route.params.subscribe(params => {
let regType: string = params['workflow'];
this.workflowInstance = this.workflowManager.getWorkflow(workflow);
});
}
}
But I don't like the idea of manually instantiating Workflow* classes. How can I improve this so I wire it using Angular2 supplied API so that it can be managed/injected in to other components?
Also in my current implementation, I will have to make ajax calls outside of Angular framework which I don't think is the best thing to do.
Any ideas on how to take advantage of the Angular framework and improve this?
One way is to inject an injector and acquire the workflow instance imperatively like:
#Injectable()
export class WorkflowManager {
constructor(private injector:Injector) {}
workflow:IWorkflow;
getWorkflow(workflow){
switch (workflow) {
case 'workflowA':
return this.injector.get(workflowA);
case 'workflowB':
return this.injector.get(workflowB);
case 'workflowC':
return this.injector.get(workflowC);
case 'workflowD':
return this.injector.get(workflowD);
default:
throw new Error(`Unrecognized workflow: ${workflow}`);
}
}
}
Alternatively you can inject workflowA, workflowB, workflowC, workflowA and just return the right one depending on the workflow but I think using the injector is a better fit for this use case.
Ensure you provide workflowA, workflowB, workflowC.

Write own Angular2 Annotation

Today, I trying to make my own annotation to use it in Angular2 project.
This annotation must add body class on specific component.
So, I've search on Angular source code, but it's so difficult to see where and how annotation was created.
For the moment, I've tried this :
export function BodyClass(classes: any): ClassDecorator{
classes = classes || {};
if(typeof classes === 'string'){
classes = {classes};
}
return function changeBodyClass(){
console.log('ici');
}
}
And my component :
import {Component} from "angular2/core";
import {RouterOutlet} from "angular2/router";
import {BodyClass} from "../core/annotations/body_class";
#Component({
selector: 'my-component',
template: `
<router-outlet></router-outlet>
`,
})
#BodyClass('test')
export class MyComponent{
}
My console log in the annotation was correctly fired, but I want to use "DOM" class from angular2/src/platform/dom/dom_adapter to add my classes, but DOM in undefined when on console log it (not need to instanciate it).
However, the DOM class works well directly in my component.
I add classes in ngOnInit function, and remove them on ngOnDestroy.
But, I want this behavior on many component, and I think a new annotation is the best way.
Maybe you have a better idea for this ? Or to work with DOM class on a annotation ?
Thanks !
It's not so obvious since you want to work at the component instance level and not at the component class one. So you need to wrap the corresponding constructor function.
export function MyComponentDecorator(value: string) {
return function (target: Function) {
var original = target;
function construct(constructor, args) {
var c : any = function () {
// Call the target constructor
var ret = constructor.apply(this, args);
// Add your additional processing
return ret;
}
c.prototype = constructor.prototype;
return new c();
}
// The new constructor
// Don't forget to add a name at this function
// since Angular requires it for the template compilation
var f : any = function WrappedComponent(...args) {
return construct(original, args);
}
f.prototype = original.prototype;
return f;
}
}
At this point, you wrap the component instance but you lose the component metadata. You need to copy them by hand:
f.prototype = original.prototype;
var annotations = Reflect.getMetadata('annotations', original));
Reflect.defineMetadata('annotations', annotations, f);
var properties = Reflect.getMetadata('propMetadata', original));
Reflect.defineMetadata('propMetadata', properties, f);
return f;
To use this decorator simply add it before or after the #Component one:
#MyComponentDecorator()
#Component({
selector: 'sub',
template: `
<div (click)="showMessage()">Test</div>
`
})
export class SubComponent {
(...)
}
You can notice that this decorator only copies the metadata at the component level and not the other ones like properties (with #Input)...
See this plunkr: https://plnkr.co/edit/PrAvliIpWTNFAtH33bHA?p=preview.