Using Angular 2 I've created an application that needs to load an external html, to achieve this I did a simple node api to serve the external html and finally render this external file into my angular 2 application. This is what I want, that works perfectly.
app.component.html
<main>
<h1>Hi, from the container</h1>
<test-component></test-component> <!-- The external html -->
<main>
myExternalFile.html
<main>
<h2>Hi, Im the external file</h2>
</main>
test.component.ts
import { Component, Input, Pipe } from '#angular/core';
import { Http } from '#angular/http';
import { BrowserModule, DomSanitizer } from '#angular/platform-browser';
#Component({
selector: 'test-component',
template: `<div [innerHTML]="myExternalHTML"></div>`
})
export class TestComponent {
myExternalHTML: any = "";
constructor(http: Http, private sanitizer: DomSanitizer ) {
http.get('http://localhost:5000/api/v1/todos') // my basic node app
.subscribe((response: any) => {
const externalHTML=
this.sanitizer.bypassSecurityTrustHtml(response.text());
this.myExternalHTML= externalHTML;
}, (error: any) => {
console.log('Error: ' + error);
})
}
}
So this works, after that I can see the html loaded without any problem. Now I need to add a button with an action that will be processed on the angular2 container.
Like adding a button in the external html (myExternalFile.html)
<main>
<h2>Hi, Im the external file</h2>
<button (click)="hi()">say hi!</button>
</main>
And adding the method (test.component.ts)
import { Component, Input, Pipe } from '#angular/core';
import { Http } from '#angular/http';
import { BrowserModule, DomSanitizer } from '#angular/platform-browser';
#Component({
selector: 'test-component',
template: `<div [innerHTML]="myExternalHTML"></div>`
})
export class TestComponent {
myExternalHTML: any = "";
constructor(http: Http, private sanitizer: DomSanitizer ) {
http.get('http://localhost:5000/api/v1/todos') // my basic node app
.subscribe((response: any) => {
const externalHTML=
this.sanitizer.bypassSecurityTrustHtml(response.text());
this.myExternalHTML= externalHTML;
}, (error: any) => {
console.log('Error: ' + error);
})
}
// New method
hi() {
console.log('we made connection!')
}
}
But, I'm not getting any message on my console. How can I stablish this kind of connection? since everything is already compiled... adding and external file in this ways makes me thing more about this kind of communication.
<main>
<h2>Hi, Im the external file</h2>
<button id="mybtn" (click)="hi()">say hi!</button>
</main>
app.component.ts
import { Component, Input, Pipe } from '#angular/core';
import { Http } from '#angular/http';
import { BrowserModule, DomSanitizer } from '#angular/platform-browser';
#Component({
selector: 'test-component',
template: `<div [innerHTML]="myExternalHTML"></div>`
})
export class TestComponent {
myExternalHTML: any = "";
constructor(http: Http, private sanitizer: DomSanitizer ) {
http.get('http://localhost:5000/api/v1/todos') // my basic node app
.subscribe((response: any) => {
const externalHTML=
this.sanitizer.bypassSecurityTrustHtml(response.text());
this.myExternalHTML= externalHTML;
}, (error: any) => {
console.log('Error: ' + error);
})
}
ngAfterViewInit(){
let el = document.getElementById('mybtn') as HTMLElement;
el.click();
}
// New method
hi() {
console.log('we made connection!')
}
}
Related
I am using angular 5 and java 8 for my web application. I am having a java service which return html content with angular click event. The same i am binding to angular component. The html content is working but click event is not working.
Below is the sample code from Java
#RequestMapping(value="/hitSample",method = RequestMethod.GET)
public String hitSample() {
StringBuilder sb = new StringBuilder();
sb.append("<a (click)=\"callSampleFunction()\"><p>This is a paragraph.</p> A Tag end
</a>");
sb.append("<p>This is a paragraph. 2 </p>");
return sb.toString();
}
SampleComponent.ts
import { Component, OnInit } from '#angular/core';
import {ViewReviewService} from '../../services/view-review-service';
import {Response} from '#angular/http';
#Component({
selector: 'app-sample-hit',
templateUrl: './sample-hit.component.html',
styleUrls: ['./sample-hit.component.css']
})
export class SampleHitComponent implements OnInit {
sampleData: any;
constructor(private viewReviewService: ViewReviewService) { }
ngOnInit() {
this.hitSample();
}
hitSample() {
this.viewReviewService.hitSample().subscribe((res: Response) => {
console.log(res['_body']);
this.sampleData = res['_body'];
});
}
callSampleFunction() {
alert('got call');
}
}
sampleComponent.html
<div [innerHTML]="sampleData" > </div>
I want callSampleFunction() to be triggered on click.
You can use the same way you've used <div [innerHTML]="sampleData" > </div> but you need to sanitise the string template for security reasons.
<div [innerHtml]="sampleData | safeHtml">
SafeHtmlPipe.ts
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
import DOMPurify from 'dompurify';
#Pipe({
name: 'safeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
public transform(value: any, type: string): any {
const sanitizedContent = DOMPurify.sanitize(value);
return this.sanitizer.bypassSecurityTrustHtml(sanitizedContent);
}
}
I have a component called NewCustomerComponent and I want to load and display it through a modal popup in another page/component when a button is clicked. I have written the relevant bit of code [or so it seems]. But I am getting the following error --
this._childInstance.dialogInit is not a function
at ModalDialogComponent.dialogInit (modal-dialog.component.js:65)
at ModalDialogService.openDialog (modal-dialog.service.js:26)
at OrderNewComponent.newCl (order-new.component.ts:85)
My code is pretty simple too, in the component where I am trying to open the modal popup.
I'll just post the relevant portions --
import { Component, Inject, ViewContainerRef, ComponentRef } from
'#angular/core';
import { Http, Headers } from '#angular/http';
import { Router } from '#angular/router';
import { Observable, Subject } from 'rxjs';
import 'rxjs/add/operator/map';
import { CustomerSearchService } from '../../../shared/services/customer-
search.service';
import { ICustomer, Customer, CustomerD } from
'../../../shared/models/customer';
import { ModalDialogModule, ModalDialogService, IModalDialog } from 'ngx-
modal-dialog';
import { NewCustomerComponent } from
'../../../components/popups/customer/customer-new.component';
#Component({
selector: 'order-new',
templateUrl: './order-new.component.html'
})
export class OrderNewComponent {
public reference: ComponentRef<IModalDialog>;
constructor(private cusService: CustomerSearchService, private http:
Http, private modalService: ModalDialogService, private viewRef:
ViewContainerRef) {
}
ngOnInit(): void {
}
** this is where I am trying to load the newcustomercomponent and open it
in the popup. not working.
newCl() {
this.newC = true;
this.exiC = false;
this.modalService.openDialog(this.viewRef, {
title: 'Add New Customer',
childComponent: NewCustomerComponent
});
}
}
** edits. NewCustomerComponent code added for reference.
import { Component, Input, Output, EventEmitter, OnInit,
ChangeDetectorRef, Directive, ElementRef, Renderer, AfterViewInit }
from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { NgFor } from '#angular/common';
import { Observable } from 'rxjs/Rx';
import { BehaviorSubject } from 'rxjs/Rx';
import { PlatformLocation } from '#angular/common';
import { Http } from '#angular/http';
import { ICustomer, Customer } from '../../../shared/models/customer';
import { UserService } from '../../../shared/services/UserService';
import { IModalDialog, IModalDialogOptions, IModalDialogButton } from
'ngx-modal-dialog';
#Component({
selector: 'new-customer',
templateUrl: './customer-new.component.html'
})
export class NewCustomerComponent implements IModalDialog {
model: Customer = new Customer();
errors: any;
submitResponse: any;
actionButtons: IModalDialogButton[];
constructor(private userService: UserService, private http: Http) {
this.actionButtons = [
{ text: 'Close', onAction: () => true }
];
}
ngOnInit() {
}
dialogInit(reference: ComponentRef<IModalDialog>, options:
Partial<IModalDialogOptions<any>>)
{
// no processing needed
}
createCustomer() {
this.userService.createCustomer(this.model)
.take(1)
.subscribe(
(response: any) => {
this.submitResponse = response;
if (response.success) {
console.log('New customer added!');
}
else {
console.log('Unable to add customer!');
}
},
(errors: any) => this.errors = errors
);
return false;
}
cancelClicked() {
}
}
What did I do wrong here? Has it got something to do with the element reference I added in terms of the viewRef? Which portion is erroneous? What about that child component? Does it require to have some specific configuration/markup/component for this to work? I am very new to angular; I am not sure whatever the reason is.
Kindly help me rectify this scenario.
Thanks in advance,
Can you please ensure that the NewCustomerComponent is implementing the IModalDialoginterface. Also, if this is not the case can you please share the code of NewCustomerComponent as well.
edits
Looks like you have not defined the dialogInit method in the NewCustomerComponent and it didn't pop up before as you have not implemented the interface IModalDialog. I would request you to define the dialogInit method in the component class as suggested on the link.
I have a problem with Angular 2 routing. When I click on my link to get the team details, it takes the right route and loads the component specified (TeamComponent). But, immediately "gets back" to the previous component (TeamsComponent), which is the teams list.
This is the structure of my project:
/app
|_shared
|_team
|_team.component.css
|_team.component.html
|_team.component.ts
|_team.model.ts
|_team.service.ts
|_team-list
|_team-list.component.css
|_team-list.component.html
|_team-list.component.ts
|_teams
|_teams.component.css
|_teams.component.html
|_teams.component.ts
|_teams.module.ts
|_teams-routing.module.ts
First, I set the routes on teams-routing.module.ts:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { TeamsComponent } from './teams.component';
import { TeamComponent } from '../shared/team/team.component';
const teamsRoutes: Routes = [
{
path: 'team/:id',
component: TeamComponent
},{
path: 'teams',
component: TeamsComponent
}
];
#NgModule({
imports: [
RouterModule.forChild(teamsRoutes)
]
})
export class TeamsRoutingModule { }
Load the team list from the teamService on teams.component.ts and send it to team-list on teams.component.html:
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import { TeamService } from '../shared/team/team.service';
import { Team } from '../shared/team/team.model';
#Component({
selector: 'app-teams',
templateUrl: './teams.component.html',
styleUrls: ['./teams.component.css']
})
export class TeamsComponent implements OnInit {
teamList: Team[] = [];
constructor(private teamService: TeamService) { }
ngOnInit() {
this.teamService.getTeams().subscribe(teams => {
Observable.from(teams).subscribe(team => {
this.teamList.push(team);
});
});
}
}
teams.component.html
<section class="teams">
<app-team-list [teams]="teamList"></app-team-list>
</section>
Then, with my teams list, I set the HTML list on team-list.component.html:
<section *ngFor="let team of teams" class="team_list">
<div class="card" style="width: 20rem;">
<img class="card-img-top" src="/assets/logos/{{team.logo}}" alt="Team Logo">
<div class="card-block">
<h4 class="card-title">{{team.name}}</h4>
<p class="card-text">{{team.location}}</p>
<a routerLink="/team/{{team.id}}" class="btn btn-primary">Team Info</a>
</div>
</div>
</section>
Finally, I get the team info from param "id" and the service in team.component.ts:
import { Component, Input, OnInit } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { Team } from './team.model';
import { TeamService } from "./team.service";
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'app-team',
templateUrl: './team.component.html',
styleUrls: ['./team.component.css']
})
export class TeamComponent implements OnInit {
team: Team = null;
constructor(private teamService: TeamService,
private activatedRoute: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
let teamId: number = this.activatedRoute.snapshot.params['id'];
console.log("Vamos a buscar el equipo");
this.teamService.getTeamById(teamId).subscribe(team => this.team = team);
}
}
It loads the TeamComponent HTML with the team data, but gets back to /teams direction (and doesn't print the team list). I tried to change the routes names (/detail/:id for example) but still doesn't work. Any suggestions? Thanks in advance.
Ok, got it. Your request will be exuted async, so at the creation-time of your component, team is null. I think you have a binding like this in your TeamComponent:
{{ team.name }}
If team is null, name cannot be accessed and it crashes. To be sure the html will be rendered without errors, use the elvis-operator like this:
{{ team?.name }}
This will only access name if team is not null or undefined
Update: The getTeamById service
getTeamById(id: number): Observable<Team> {
let team: Team = null;
return this.http.get(this.urlTeams+'/'+id)
.map(response => {
let dbTeam: any = response.json();
for(let i in dbTeam) {
team = new Team(dbTeam[i].teamId,dbTeam[i].teamName,dbTeam[i].teamLocation,dbTeam[i].teamFoundation,dbTeam[i].teamDivision,dbTeam[i].teamConference,dbTeam[i].teamStadium,dbTeam[i].teamAttendance,dbTeam[i].teamLogo,dbTeam[i].teamStadiumPhoto);
}
return team;
})
.catch(this.handleError);
}
I'm 9 hours a day on Angular trying to make some little projects mainly with services. Today, I'm trying to make a service that loops on data's fetching and the components update themselves according to new data. I've like 6 components using the service and the standard way to do it makes 6 times more requests that only one component does.
I heard about IntervalObservable but I don't know how to implement it on the component side. (And maybe I failed in the service too ...)
Here is some code.
app.module.ts :
import { FormsModule } from '#angular/forms';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { ROUTING } from './app.routes';
import {HardwareService} from "./services/hardware.service";
import {AfficheurComponent} from "./components/hardware/afficheur.component";
import {HardwareListComponent} from "./views/hardwarelist/hardwarelist.component";
#NgModule({
imports: [ BrowserModule, ROUTING, HttpModule, FormsModule],
declarations: [
AppComponent,
AfficheurComponent,
HardwareListComponent
],
bootstrap: [ AppComponent ],
providers: [ HardwareService ]
})
export class AppModule { }
hardware.service.ts :
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/interval'
#Injectable()
export class HardwareService{
private apiUrl = 'http://10.72.23.11:5000'; // URL to web API
constructor (private http: Http) {}
getHardware(){
return Observable.interval(5000)
.flatMap(() => {
return this.http.get(this.apiUrl)
.map(this.extractData)
.catch(this.handleError);
});
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real-world app, you might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
afficheur.component.ts :
import { Component } from '#angular/core';
import {HardwareService} from "../../services/hardware.service";
#Component({
selector: 'afficheur',
templateUrl: 'app/components/hardware/afficheur.component.html'
})
export class AfficheurComponent{
public state: Boolean;
constructor(private service: HardwareService){
this.service
.getHardware()
.subscribe(data => (console.log(data), this.state = data.afficheur),
error => console.log(error),
() => console.log('Get etat afficheur complete'))
}
}
I took the information about IntervalObservable here (SO thread)
As always, I hope you'll be able to help me find my way through this problem :).
ERROR TypeError: Observable_1.Observable.interval is not a function
Regards, Jérémy.
(PS: English is not my mother language, don't hesitate to tell me if i told something you don't understand)
The solution would look something like:
// create an observable which fetch the data at intervals of 1 second
this._data$ = Observable
.timer(0, 1000)
.switchMap(() => this.getData())
// if an error is encountered then retry after 3 seconds
.retryWhen(errors$ => {
errors$.subscribe(error => this.logError(error));
return errors$.delay(3000);
})
.share();
timer(0, 1000) - produce the first value after 0ms and then at intervals of 1 second. Using interval(1000) instead is ok but the first value will come with a delay of 1 second.
switchMap(() => this.getData()) - switch to the observable provided by the callback which queries the actual resource
retryWhen(...) - if an error is encountered then logs the error and then retries
share() - shares a single subscription among the subscribers. This has the effect of calling getData() only once, instead of calling it for as many subscribers we might have.
Example - emit current dates, when getData() is called 5th time in a row then an error is thrown in order to test also the error situation.
Here is the working Plunker.
HardwareService
import { Injectable } from '#angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/timer';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/retry';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/switchMap';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class HardwareService {
private _fetchCount = 0;
private _fetchCount$ = new Subject<number>();
private _data$: Observable<Date>;
public get data$(): Observable<Date> {
return this._data$;
}
public get fetchCount$(): Observable<number> {
return this._fetchCount$;
}
constructor() {
// create an observable which fetch the data at intervals of 1 second
this._data$ = Observable
.timer(0, 1000)
.switchMap(() => this.getData())
// if an error is encountered then retry after 3 seconds
.retryWhen(errors$ => {
errors$.subscribe(error => this.logError(error));
return errors$.delay(3000);
})
.share();
}
private logError(error) {
console.warn(new Date().toISOString() + ' :: ' + error.message);
}
private getData(): Observable<Date> {
this._fetchCount++;
this._fetchCount$.next(this._fetchCount);
// from time to time create an error, after 300ms
if (this._fetchCount % 5 === 0) {
return Observable.timer(300).switchMap(() => Observable.throw(new Error('Error happens once in a while')));
}
// this will return current Date after 300ms
return Observable.timer(300).switchMap(() => Observable.of(new Date()));
}
}
AfficheurComponent
import {Component, Input, OnInit} from '#angular/core';
import {HardwareService} from '../services/hardware.service';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-afficheur',
templateUrl: './afficheur.component.html',
styleUrls: ['./afficheur.component.css']
})
export class AfficheurComponent implements OnInit {
#Input()
public label: string;
public data$: Observable<string>;
constructor(private hardwareService: HardwareService) {
this.data$ = hardwareService.data$.map(item => this.label + ' - ' + item.toISOString());
}
ngOnInit() {
}
}
AfficheurComponent template
<div style="margin-top: 10px;">{{ data$ | async }}</div>
Usage
<app-afficheur label="afficheur 1"></app-afficheur>
<app-afficheur label="afficheur 2"></app-afficheur>
<app-afficheur label="afficheur 3"></app-afficheur>
<app-afficheur label="afficheur 4"></app-afficheur>
<app-afficheur label="afficheur 5"></app-afficheur>
<app-afficheur label="afficheur 6"></app-afficheur>
<div style="margin-top: 10px">
Times called: {{ hardwareService.fetchCount$ | async }}
</div>
Today i'm facing a new problem with services.
I'm trying to make an http service but when I try to store, in my service, the Observable object returned by http.get.map - my app crashs.
I wanted to achieve a "system" where the service loops to update datas and the components which subscribed to the observable update its data according to the service's data.
Here is the code :
afficheur.component.ts :
import { Component } from '#angular/core';
import {HardwareService} from "../../services/hardware.service";
#Component({
selector: 'afficheur',
templateUrl: 'app/components/hardware/afficheur.component.html'
})
export class AfficheurComponent{
public state: Boolean;
constructor(private service: HardwareService){
this.service
.getHardware()
.subscribe(data => (console.log(data), this.state = data.afficheur),
error => console.log(error),
() => console.log('Get etat afficheur complete'))
}
}
hardware.service.ts :
import { Injectable, OnInit } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
#Injectable()
export class HardwareService implements OnInit{
private apiUrl = 'http://10.72.23.11:5000'; // URL to web API
private ressources: Observable<any>;
constructor (private http: Http) {}
ngOnInit() {
this.loopupdate()
}
loopupdate(): void {
setInterval(() => {
this.update();
}, 5000);
}
update(): void {
this.ressources = this.http.get(this.apiUrl)
.map(this.extractData)
.catch(this.handleError);
}
getHardware(){
return this.ressources;
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real world app, you might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
app.module.ts :
import { FormsModule } from '#angular/forms';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { ROUTING } from './app.routes';
import {HardwareService} from "./services/hardware.service";
import {AfficheurComponent} from "./components/hardware/afficheur.component";
import {HardwareListComponent} from "./views/hardwarelist/hardwarelist.component";
#NgModule({
imports: [ BrowserModule, ROUTING, HttpModule, FormsModule, HttpModule],
declarations: [
AppComponent,
AfficheurComponent,
HardwareListComponent
],
bootstrap: [ AppComponent ],
providers: [ HardwareService ]
})
export class AppModule { }
Thanks again for being here :D
EDIT :
I got an error when i try to launch my app :
ERROR TypeError: Cannot read property 'subscribe' of undefined
I think it's related to the this.ressources initialization, any idea ?
EDIT 2 :
In my service :
initializePolling(){
return IntervalObservable.create(5000)
.flatMap(() => {
return this.getHardware()
});
}
getHardware(): Observable<any> {
return this.http.get(this.apiUrl)
.map(this.extractData)
.catch(this.handleError);
}
How can i subscribe to this with my component ? I don't know what method i should call in my component to fetch datas without make multiple calls if i have multiple components.
The problem is that ngOnInit(), like the one in your Injectable class, is a Lifecycle hook which only works with Directives and Components. You should try calling this.loopUpdate() from within the Injectable class' constructor. You can know more about this on another thread/question.
If you want to set an interval in fetching the data, do that in the component class, not in the service. In the service you should just have methods that return Observables (in your case) from calling http.get().... In that way you wouldn't have an undefined object returned and a more reusable service.
Also, here's another SO link for you to have look at.