I'm trying to get a bi-directional service working and can't figure out where I"m missing a step and or did something wrong. There's no errors in the console, yet nothing happens when I attempt to send data through it. (Here's) a plunker to what I have so far.
The process I'm trying to accomplish goes like this
Load this body of data into app.ts
and iterate with input.copmonent.ts
. (done successfully).
{
"questions":[
{
"question": "How tall are you?",
"name": "height",
"id": "heightCtrl",
"template": "smInput"
},
{
"question": "What is your favorite color?",
"name": "color",
"id": "colorCtrl",
"template": "smInput"
},
{
"question": "What kind of car do you drive?",
"name": "car",
"id": "carCtrl",
"template": "smInput"
}
]
}
Load data to new variable and use ngModel
in template to update user response. (done successfully)
//Input Component (omitted for relevance)
@Component({
template:`
<ng-container *ngIf="Qdata.template == 'smInput'">
<p>{{Qdata.question}}</p>
<input type="text"
id="Qdata.id"
name="Qdata.name"
[(ngModel)]="Response.response"
>
</ng-container>
`
})
export class InputComponent implements OnInit{
@Input() Qdata: any; //from app.ts
Response: FormResponse={}; // new variable
ngOnInit(){ this.prepResponse(); }
prepResponse(){
let a = this.Qdata;
let b = this.Response;
b.question = a.question;
b.id = a.id;
b.name = a.name;
}
}
// Note: FormResponse is a custom class I made that's shaped the same as
// the json data with the addition of a "response:string='';" field.
Create service to receive data from the Response
variable in input.component.ts
and make available for app.ts
to subscribe to. (done successfully.... I think... for the most part)
//Imports Observable and Subject from proper places
@Injectable()
export class AnswerService {
private FormList = new Subject<FormResponse[]>(); // so far I think this is contributing
to the problem somehow.
addResponse(data:FormResponse){
this.FormList.next(data);
}
getFormList(): Observable<FormResponse[]>{
return this.FormList.asObservable();
}
}
Create function in Input.component.ts
to send data to addResponse()
function in service. (done successfully.... I think)
import { AnswerService } from './answer.service';
@Component({
template:`
<ng-container *ngIf="Qdata.template == 'smInput'">
<!--previous HTML -->
<button (click)="sendResponse()">send</button>
<!-- plan to ultimately use OnChanges, using this to test -->
</ng-container>
`,
providers: [AnswerService]
})
export class InputComponent implements OnInit{
@Input() Qdata: any;
Response: FormResponse={};
constructor( private _ans : AnswerService ) {}
ngOnInit(){ ... } prepResponse(){...}
sendResponse(): void{
let a = this.Response;
let grp = new FormResponse({
question:a.question,
response:a.response,
id:a.id,
name:a.name
});
this._ans.addResponse(grp);
}
}
Create subscription to FormList
in app.ts
. (done successfully)
//app.ts (omitted for relevance)
import { AnswerService } from './answer.service';
@Component({
providers: [ AnswerService ]
})
export class App implements OnInit{
FormData: Array<FormResponse>=[];
constructor( private _ans : AnswerService ){}
ngOnInit(){
this._ans.getFormList().subscribe( list => { list = this.FormData; });
}
}
After reaching this point everything loads up fine without any errors, but when I type something in the input fields and click send, nothing shows up in the binding I made in the app.ts
template to show when a new object is added to the array. When I try different methods, such as push()
instead of next()
I get errors telling me this.FormList.push() is not a function
. What do I need to do to get this to work?
I also want to point out that the reason I'm "copying data" in the input.copmonent.ts
file is because in the real world usage of this the Qdata
variable will have a lot more than just 4 properties, though it will only be sending those 4 to the Response
variable to send back up to the parent component.
From this point forwards I plan to use the FormData
variable to iterate a Template Driven Form with hidden pre-filled inputs something like this
<form #myForm="ngForm">
<div *ngFor="let a of FormData">
<div [attr.data-blank]="a.id" class="form-group">
<!-- To force Angular to iterate a wrapper for each group -->
<input type="hidden" class="form-control"
[attr.name]="a.id" [(ngModel)]="a.question"
>
<input type="hidden" class="form-control"
[attr.name]="a.name" [(ngModel)]="a.response"
>
</div>
</div>
</form>
The reason I'm trying to do it this way is because the types of questionnaires I make have tons of optional questions nested within questions for multiple tiers which you can see here. I'm hoping this will be a good way to collect the user's answers and put them all in one place without all the headaches. As long as the answer.service
is imported into the input.component
it will always be able to send/update the list no matter what direction the user goes and what circumstance of inputs it requires. What do I need to do to make sure the answer.service
can deliver for this use?
There's a few issues here, the first issue is that your child component
is also flagging the answer service
as a provider, so it gets a new instance of AnswerService instead of getting the same instance of the service as the parent.
@Component({
selector: 'input-component',
templateUrl: 'src/input.component.html'
// No providers
})
export class InputComponent implements OnInit{
// ...
/** Gets same instance of AnswerService as the parent component */
constructor(private _ans: AnswerService){}
sendResponse(): void{
this._ans.addResponse(this.Response);
}
}
Also, the getter
for your FormList
was incorrect, I suggest using this approach as in the official example.
Your service should look like this.
@Injectable()
export class AnswerService {
private _formList = new Subject<any>();
public FormList = this._formList.asObservable();
addResponse(event: FormResponse){
this._formList.next(event);
}
}
Now on your app.component
you need to subscribe to the events like so:
@Component({
selector: 'my-app',
templateUrl:'src/app.html',
providers: [ QuestionService, AnswerService ]
})
export class App implements OnInit{
FormData: FormResponse[] = [];
// ...
answerSubscription = this._ans.FormList.subscribe(a => {
console.log('gets here '+JSON.stringify(a, null, '\t') )
this.FormData.push(a)
})
// ...
}
Note that in the subscription I'm appending the result to the FormData
Array.
The QuestionService
code probably needs some touching up but you get the idea.
Here's a plunker working!