subscribe to HTTP-service to pass an object to a form in a child component with Angular 4 Router with Observable - angular2-routing

I apologize if this question seems to be a double, but I have searched the resource up and down and upon reading many threads and the Angular.io tutorial, I still cannot figure out how exactly to achieve my goal, although I have a pretty good understanding of the possibilities now.
My parent component (which is nothing more but a header + ) an object with an HTTP-service, which I would like to subscribe to from a child-component, in order the form in the child component gets pre-filled with object data.
Here is my code:
(ProjectsService)
#Injectable()
export class ProjectsService {
private projectsUrl = environment.apiurl + 'projects';
constructor(private http: Http) { }
private extractData(res:Response) {
let body = res.json();
return body || [];
}
getProjects(): Observable<Project[]> {
return this.http.get(this.projectsUrl)
.map(response => response.json() as Project[])
.catch(this.handleError);
}
getProject(id: String): Observable<Project> {
const url = `${this.projectsUrl}/${id}`;
return this.http.get(url)
.map(response => response.json() as Project)
.catch(this.handleError);
}
parent (ProjectComponent)
export class ProjectComponent implements OnInit {
project: Project;
constructor(
private projectsService: ProjectsService,
private route: ActivatedRoute,
) {}
ngOnInit(): void {
this.route.params
.switchMap((params: Params) => {
return this.projectsService.getProject(params.id);
}).
subscribe(project => this.project = project);
}
}
(routing module)
{ path: '', redirectTo: 'projects', pathMatch: 'full' },
{
path: 'project/:id',
component: ProjectComponent,
children: [
{
path: '',
redirectTo: 'settings', pathMatch: 'full'
},
{
path: 'settings',
component: SettingsComponent
},
child (SettingsComponent):
export class SettingsComponent {
#Input() project: Project;
constructor(
private route: ActivatedRoute,
private projectsService: ProjectsService
) { }
ngOnInit(): void {
***I think it's here that I need a subscription to the HTTP-service but I cannot figure out how to ****
this.project = {
(...some porject properties to fill the data in the form)
}
}
}
Many thanks in advance for a reply.

I have solved my issue by subscribing to the HTTP-service from within the child component but using this.route.parent.params instead of this.route.params

Related

How to extend EntityCollectionReducerMethods NgRx Data

I need to parse the data field before adding it to the store.
I was hoping to parse the data field from the override getAll().
This code doesn't work can someone explain why ?
export interface Alert {
id: string;
data: any;
}
const entityMetadata: EntityMetadataMap = {
Alert: {}
};
#Injectable({providedIn: 'root'})
export class AlertService extends EntityCollectionServiceBase<Alert> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Alert', serviceElementsFactory);
}
getAll(options?: EntityActionOptions): Observable<Alert[]> {
return super.getAll(options)
.pipe(
map(alerts => {
alerts = alerts.map((alert: any) => ({...alert, data: JSON.parse(alert.data)}));
return alerts;
})
);
}

Angular 6 - a few components using the same randomised data that's called only once - how to avoid 'undefined'

I'm trying to use the same data in ngOnInit of several components, the problem is, the data are random every time I subscribe to it. I can only subscribe one time in the entire run of the app. That means, if I subscribe twice, it won't have any use because each component will have something else.
I want to subscribe once in a service and to create a global variable.
but when you try to use that global variable in all the components (on ngOnInit) it is always undefined.
How can I make the components wait for that same thing that can only be called once in the app?
export class UsersService {
public allUsers: Array<Object> = []
public allUsersUrl: string = 'https://glacial-escarpment-40412.herokuapp.com/users/'
constructor(private http: HttpClient) {
this.getAllUsers().subscribe(data => {
this.allUsers = data;
console.log(this.allUsers)
})
}
getAllUsers(): Observable<any> {
return this.http.get<any>(this.allUsersUrl);
}
getUser(id: number): Observable<any> {
return this.http.get<any>(this.allUsersUrl + id);
}
}
My components:
export class FirstComponent {
constructor(private usersService: UsersService){}
ngOnInit() {
console.log(this.usersService.allUsers) //undefined
}
export class SecondComponent {
constructor(private usersService: UsersService){}
ngOnInit() {
console.log(this.usersService.allUsers) //undefined
}
Please help me :)
You have a synchronism problem. This is your scenario
1- create first component
1.1 - injecting UsersService
1.2 - UsersService request for ASYNC data (execution continues)
2- FirstComponent get and print this.usersService.allUsers (not still populated because of async request)
3- this.usersService.allUsers is still undefined
You need Subject
Something like this:
UsersService
export class UsersService {
private _allUsersSource = new Subject<Array<Object>>();
private _allUsers$ = this._allUsersSource.asObservable();
public allUsersUrl: string = 'https://glacial-escarpment-40412.herokuapp.com/users/'
constructor(private http: HttpClient) {
this.getAllUsers();
}
getAllUsers(): Observable<any> {
return this.http.get<any>(this.allUsersUrl).subscribe(
data => this._allUsersSource.next(data)
);
}
get allUsers$(): Observable<Array<Object>> {
return this._allUsers$;
}
// OTHERS
}
FirstComponent
export class FirstComponent {
subscription: Subscription;
allUsers: Array<Object>;
constructor(private usersService: UsersService){}
ngOnInit() {
this.subscription = this.usersService.allUsers$.subscribe(users => {
this.allUsers = users;
console.log(this.allUsers);
});
}
Some thing for SecondComponent

anguar2 nativescript error "TypeError: undefined is not an object (evaluating 'this.router')"

I am new to Angular2 Native script Programming... i need to navigate one page to another. i am stuck with the typeError:"undefined is not an object (evaluating 'this.router')" plz help me out..
my code is given..
//page1.ts
public constructor(private router: Router) { }
getMyDrawing(args) {
let pad = this.DrawingPad.nativeElement;
let img: Image = this.signImage.nativeElement;
let drawingImage;
pad.getDrawing().then
(
(data) => {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
}
);
}
//routing.ts
import { DrawingPadExample } from "./app.component";
import { Page2Component } from "./app.page2";
export const routes = [
{ path: "drawing-pad-example", component: DrawingPadExample},
{ path: "page2", component: Page2Component }
];
export const navigatableComponents = [
DrawingPadExample,
Page2Component
];
//module.ts
import { NgModule, NO_ERRORS_SCHEMA } from "#angular/core";
import { NativeScriptModule } from "nativescript-
angular/nativescript.module";
import { DrawingPadExample } from "./app.component";
import { routes, navigatableComponents } from "./app.routing";
import { NativeScriptRouterModule } from "nativescript-
angular/router";
#NgModule({
bootstrap: [
DrawingPadExample
],
imports: [
NativeScriptModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes)
],
declarations: [
DrawingPadExample,
...navigatableComponents
],
schemas: [
NO_ERRORS_SCHEMA
]
})
export class AppModule { }
When you say navigation is not working, do you nothing at all happens and no errors are generated?
I would advice you try putting the callback within a zoneCallback.
public constructor(private ngZone: NgZone, private router: Router){
}
public getMyDrawing(args) {
let pad = this.DrawingPad.nativeElement;
let img: Image = this.signImage.nativeElement;
let drawingImage;
pad.getDrawing().then((data) => {
this.ngZone.run(() => {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
});
});
}
Add instance in constructor for router.
constructor(private router : Router){}
because as error stated typeError:"undefined is not an object (evaluating 'this.router')" you are trying to access
this.router which is not initilized yet
#JBR, No, I mean dependency injection using constructor, for example,
constructor(private router: Router) {}
add that line to you page1 component.
Update
replace this
function(data) {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
}
with arrow function
(data) => {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
}
I got this bug when I was passing a function between two components.
In the first component, I imported Router, but in the second I didn't. Make sure that you import all necessary libraries when you are using callback functions.

Http doesn't work in Angular 2 custom asynchronous validation

I'm trying to create a custom asynchronous validator, that goes to the server and checks if an email is already registered.
Unfortunately, it seems that the get request is never fired, because nothing happens. I've tried multiple console.logs inside subscribe, but they didn't run.
I've checked if that request works outside of the validator, and it does, so that's not the problem.
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '#angular/forms';
import { Response, Http } from '#angular/http';
#Component({
templateUrl: 'build/pages/welcome/signup/signup.html',
providers: [AuthService, CustomValidators]
})
export class Signup {
signupForm: FormGroup;
constructor(private formBuilder: FormBuilder, private http: Http) {
this.signupForm = formBuilder.group({
'email': ['', Validators.required, this.checkEmail],
}):
}
checkEmail(control: FormControl): Promise<any> {
const promise = new Promise<any>(
(resolve, reject) => {
this.http.get('/sharealead/main.php?action=checkEmail').subscribe(
(res: Response) => {
console.log('it never gets here');
console.log(res)
if (res.text() == 'already there') {
resolve({'emailTaken': true});
} else {
resolve(null);
}
},
(err) => {
console.log('it never gets here');
console.log(err);
}
)
}
);
return promise;
}
}
It's because you reference the function and you lose the this context. You can use the bind method or a wrapping arrow function to fix that (link the function to the component instance):
this.signupForm = formBuilder.group({
'email': ['', Validators.required, this.checkEmail.bind(this) ],
});
or
this.signupForm = formBuilder.group({
'email': ['', Validators.required, (control:Control) => {
return this.checkEmail(control);
} ],
});
In your case, this doesn't contain an http property...

Fill form after http response in angular2

I'm wondering what is a best way to load a form after getting the response from server. I wrote some code where it is getting data from server and in my component I am subscribing to the response, but My UI is loading before even I get the response.
I want to use this component for both adding and editing.
Component:
#Component({
selector: 'gate',
templateUrl: '/public/app/views/gate.html',
directives: [GateFormComponent, StrategyComponent],
providers : [MyService]
})
export class MyComponent {
private id:any;
constructor(private _routeParams:RouteParams, #Inject(MyModel) private myModel,
private myService : MyService) {
}
ngOnInit() {
this.id = this._routeParams.get("id");
if (this.id) {
this.gateDataModel.unique_display_id = parseInt(this.id);
this.myService.loadData(this.id)
.subscribe(response => console.log(response));
}
}
In my component, I am loading 2 components one of which has a form into which I have to load data once I get the response. And all this should only happen if I have an id available.
Service:
#Injectable()
export class MyService extends HTTPServices {
constructor(http:Http) {
super(http);
}
loadData(id:number) {
return this.query(url)
.map(res => res.json())
.catch(this.handleError)
}
private handleError(error:Response) {
console.log("Error : ", error);
return Observable.throw(error.text());
}
HTTPServices
export class HTTPServices {
private headers:Headers;
private http:Http;
defaultOptionsArgs:RequestOptionsArgs;
constructor(http:Http) {
this.http = http;
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.defaultOptionsArgs = {
'headers': this.headers
};
}
create(servicePath:string, model:any, options?:RequestOptionsArgs) {
var url = this.getUrl(servicePath);
var options = options ? options : this.defaultOptionsArgs;
return this.http.post(url, JSON.stringify(model), options);
}
query(servicePath:string, options?:RequestOptionsArgs) {
var options = options ? options : this.defaultOptionsArgs;
return this.http.get(servicePath, options);
}
}
----Edited-----
Finally, I was able to add #CanActivate and it is working.
#Component({
selector: 'gate',
templateUrl: '/public/app/views/gate.html',
directives: [ROUTER_DIRECTIVES, GateFormComponent, StrategyComponent]
})
#CanActivate(
(next: ComponentInstruction, prev: ComponentInstruction) => {
return new Promise((resolve) => {
let id = next.params["id"];
let injector = ReflectiveInjector.resolveAndCreate([HTTP_PROVIDERS]);
let http = injector.get(Http);
if(id){
http.get(URL)
.subscribe((response) => {
console.log(response)
next.routeData.data["response"] = response;
// continue
resolve(true);
}, (error) => {
resolve(false);
});
} else {
resolve(true);
}
});
}
)
export class MyComponent{
private id:any;
constructor(private _routeParams:RouteParams, #Inject(MyModel) private myModel, routeData: RouteData) {
console.log(routeData.get("response"));
}
}
The component is loading up and then I am getting the response
Thanks
In you component you can just use
template: `
<div *ngIf="data">
<!-- form goes here -->
</div>
`
where data is a property that is set to some value when the response from the server arrived.
If you leverage Angular2 routing (and it seems to be the case), you could use leverage the OnActivate interface and its routerOnActivate:
Defines route lifecycle method routerOnActivate, which is called by the router at the end of a successful route navigation.
For a single component's navigation, only one of either OnActivate or OnReuse will be called depending on the result of CanReuse.
The routerOnActivate hook is called with two ComponentInstructions as parameters, the first representing the current route being navigated to, and the second parameter representing the previous route or null.
If routerOnActivate returns a promise, the route change will wait until the promise settles to instantiate and activate child components.
You could return a promise that will be resolved when your data will be there. Here is a sample:
#Component({ ... })
export class MyComponent {
private id:any;
constructor(private _routeParams:RouteParams,
#Inject(MyModel) private myModel,
private myService : MyService) {
}
routerOnActivate() {
this.id = this._routeParams.get("id");
return new Promise((resolve, reject) => {
if (this.id) {
this.gateDataModel.unique_display_id = parseInt(this.id);
this.myService.loadData(this.id)
.subscribe(response => {
console.log(response);
resolve();
});
} else {
resolve();
}
});
}
(...)
}
I had a similar question, but since the solution can be used for your usecase too, i would recommend to have a look at the accepted answer: How to manipulate a component on specific routes in Angular2
The basic idea is to extend the router-outlet directive and override the activate() function which will be called before the next route is activated and waits for a promise to resolve.
For example you could do something like this:
#Directive({
selector: 'custom-router-outlet'
})
export class CustomRouterOutlet extends RouterOutlet {
private parentRouter:Router;
constructor(_elementRef: ElementRef,
_loader: DynamicComponentLoader,
_parentRouter: Router,
#Attribute('name') nameAttr: string,
private _myRoutingService:MyRoutingService) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.parentRouter = _parentRouter;
}
activate(nextInstruction: ComponentInstruction): Promise<any> {
let someRouteSpecificData = nextInstruction.routeData.data['someRouteData'];
if(someRouteSpecificData) {
_myRoutingService.beforeRoute(someRouteSpecificData).subscribe( () => {
// go on after this has been resolved
return super.activate(nextInstruction);
// or maybe cancel the route:
return false;
// or maybe do something crazy:
nextInstruction.componentType = MyOtherComponent;
return super.activate(nextInstruction);
}
}
return super.activate(nextInstruction);
}
}
I think you could easily change this for your purposes. You could utilize your #RouteConfig for example to hold some information on what should happen or be checked on a route change.
Another approach would be to use the #CanActivate decorator like mentioned here already, but its a bit harder to accomplish. It just feels a bit hacky at this point. I could add this later if you're interested.
I have been able to implement this using the resolve functions of the router (https://angular.io/docs/ts/latest/guide/router.html#!#resolve-guard). This enables the http calls to be made and the route only completes when the http call observable returns.
There are good examples here: https://angular.io/resources/live-examples/router/ts/plnkr.html

Resources