How can I render HTML inside an imported library component Angular - javascript

This is probably a dumb question with a simple answer but I didn't really know how to find the answer.
I'm using Angular with Micro Frontends and created my own UI library. Now I want to use one of my custom components the "Card - < ng-mfe-card >" and insert HTML inside the imported component. Simplified like this ->
<ng-mfe-card>
<div class='someClass'></div>
</ng-mfe-card>
Login component where I import the Card component and want to insert the HTML inside the Card component.
<ng-mfe-card [title]="'Login'">
<form class="login-form" (ngSubmit)="login()">
<label>
Username:
<input type="text" name="username" [(ngModel)]="username" />
</label>
<label>
Password:
<input type="password" name="password" [(ngModel)]="password" />
</label>
<button type="submit">Login</button>
</form>
<div *ngIf="isLoggedIn$ | async">User is logged in!</div>
</ng-mfe-card>
Card UI component imported from Library, tag the importee can use to insert HTML under < div class="card-content" >
<div class="card">
<div class="card-header">
<img class="card-header-image" src="{{ imageURL }}" width="100vw">
<h1 class="card-header-title">{{ title }}</h1>
</div>
<div class="card-content">
<!-- HTML from component that imports this Card component -->
</div>
<div *ngIf="footer != ''" class="card-footer">{{ footer }}</div>
</div>
card.component.ts
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'ng-mfe-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
#Input() imageURL: string = 'https://image.url';
#Input() title: string = 'Title';
#Input() footer: string = 'Footer';
constructor() { }
ngOnInit(): void {
}
}

Related

How can I configure my <li> items with Angular Routing so that it shows the relevant details in a new view?

I am building an Angular Movie DB, the two components I am working with are the "search-movie" component and the "single-view" component. The "search-movie" component displays a list of movies, the "single-view" component shows further details of a given movie.
I would like it so that when I click on a movie from the list in the "search-movie" component, the page renders the "single-view" component for that movie.
I have managed to set this up so that navigating to the "single-view" path with a movie id (i.e. /movie/tt4425200) directly from the URL, correctly loads up the info for that movie by it's ID, so that's all correctly set up.
I just can't seem to connect the clicking of a movie in the "search-movie" component, to then successfully navigate to the correct "single-view" path. So using the example above, clicking on the relevant movie loads up the "single-view" component with the URL path /movie/tt4425200.
I'm sure that this is a case of using #Input to communicate between the two components, but I just can't figure it out.
search-movie.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { FormControl } from '#angular/forms';
import { DataService } from '../data.service';
#Component({
selector: 'app-search-movie',
templateUrl: './search-movie.component.html',
styleUrls: ['./search-movie.component.css']
})
export class SearchMovieComponent implements OnInit {
searchControl = new FormControl('');
movieResults: any = [];
constructor(private data: DataService) { }
ngOnInit() {}
/* GET request */
getData(event) {
const film = event.target.value;
this.data.searchFilm(film)
.subscribe( (res) => {
res = this.movieResults = res;
console.log(res);
});
}
}
search-movie.component.html:
<div class="container">
<h1 class="is-size-3">Find your favorite movies...</h1>
<form (ngSubmit)="onSubmit()">
<input
type="text"
(keyup)="getData($event)"
placeholder="Start typing..."
[formControl]="searchControl" />
</form>
{{searchControl.value}}
</div>
<div class="container">
<ul>
<li id="list-item" *ngFor="let x of movieResults; let i = index;">
<img [src]="x.Poster" onerror="this.src='../assets/images/default-poster.jpg'">
<p id="movie-title" class="is-size-5">{{x.Title}} ({{x.Year}}) </p>
</li>
</ul>
</div>
single-view.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { DataService } from '../data.service';
#Component({
selector: 'app-single-view',
templateUrl: './single-view.component.html',
styleUrls: ['./single-view.component.css']
})
export class SingleViewComponent implements OnInit {
singleDetails: any = {};
constructor(
private data: DataService,
private route: ActivatedRoute) { }
ngOnInit() {
/** GENERATE FILM DETAILS BY movie/id where id is the imdbID */
const id = this.route.snapshot.paramMap.get('id');
this.data.moviebyID(id)
.subscribe( (res) => {
res = this.singleDetails = res;
});
}
}
single-view.component.html:
<div class="container singleDisplayContainer">
<div class="columns is-mobile">
<div class="column" id="poster-column">
<img id="poster" [src]="singleDetails.Poster" [alt]="singleDetails.Title" onerror="this.src='../assets/images/default-poster.jpg'">
</div>
<div id="info-column" class="column">
<div class="columns">
<div class="column is-three-fifths">
<h1 class="is-size-3">{{singleDetails.Title}} <span class="is-size-4">({{singleDetails.Year}})</span> </h1>
<p style="font-size: 14px"> {{singleDetails.Rated}} | {{singleDetails.Runtime}} | {{singleDetails.Genre}} | {{singleDetails.Released}} </p>
</div>
<div class="column">
<div class="columns">
<div class="column is-one-third">
<img id="star-rating" width="50px" src="../../assets/images/star-rating.png" alt="{{singleDetails.Title}}">
</div>
<div class="column">
<p style="font-size: 2em"> {{singleDetails.imdbRating}}<span style="font-size: 12px">/10</span></p>
<p>{{singleDetails.imdbVotes}}</p>
</div>
</div>
</div>
</div>
<br>
<p>Plot: {{singleDetails.Plot}} </p>
<br>
<p>Directed by: {{singleDetails.Director}} </p>
<br>
<p>Writers: {{singleDetails.Writer}} </p>
<br>
<p>Actors: {{singleDetails.Actors}} </p>
<br>
<p onerror="this.style.display='none'">Box office: {{singleDetails.BoxOffice}} </p>
</div>
</div>
</div>
In Your UL -> LI, add routerLink property and pass the movie id as the second param
[routerLink]="['/movie/,x.movieId]">
<ul>
<li id="list-item" *ngFor="let x of movieResults; let i = index;" [routerLink]="['/movie/,x.movieId]">
<img [src]="x.Poster" onerror="this.src='../assets/images/default-poster.jpg'">
<p
id="movie-title"
class="is-size-5">{{x.Title}} ({{x.Year}}) </p>
</li>
</ul>
Looks like you have everything set up, all you need to do is navigate to single-view on click of the movie.
search-movie.component.ts
showDetails(id : string) {
this.router.navigate(['movie',{ id }]);
}
search-movie.component.html
<!-- in showDetails you will pass id of the current movie while looping -->
<li id="list-item" *ngFor="let x of movieResults; let i = index;" (click)="showDetails(x.id)">
<img
[src]="x.Poster"
onerror="this.src='../assets/images/default-poster.jpg'"
>
<p
id="movie-title"
class="is-size-5">{{x.Title}} ({{x.Year}}) </p>
</li>
This will navigate to single-view page. is this what you want right?
or you need to load single-view in the same page as the list?

Bootstrap accordion not working with angular 7

I am building an accordion based on array of data fetched from the server but the clicks are not working.
The accordion is working fine on hard-coded data but not on the data fetched using HttpClient. I even tried using button instead of anchor tags but to no avail.
<div class="accordion" id="chalsAccordion">
<div class="card rounded-0" *ngFor="let chal of chals">
<a data-toggle="collapse" href="#{{ chal._id }}"
><div class="card-header text-dark">
<p class="mb-0">
{{ chal.title }}<small> - {{ chal.author }}</small>
</p>
<p class="mb-0 ml-auto">{{ chal.points }}</p>
</div></a
>
<div id="{{ chal._id }}" class="collapse">
<div class="card-body" [innerHTML]="chal.desc"></div>
<div class="card-footer" *ngIf="!userService.admin">
<form [formGroup]="flagForm" style="width: 100%">
<div class="input-group">
<input type="text" class="form-control rounded-0" placeholder="Flag" formControlName="flag" />
<div class="input-group-append">
<button class="btn btn-primary rounded-0" [disabled]="!flagForm.valid" (click)="submitFlag(chal._id)">Submit</button>
</div>
</div>
</form>
</div>
<div class="card-footer" *ngIf="userService.admin">
<div class="ml-auto">
<button class="btn btn-danger rounded-0"><fa-icon [icon]="['fas', 'trash']"></fa-icon></button>
</div>
</div>
</div>
</div>
</div>
<button class="btn btn-primary add-chal-btn" routerLink="/chals/add" *ngIf="userService.admin"><fa-icon [icon]="['fas', 'plus']"></fa-icon></button>
import { Component, OnInit } from "#angular/core";
import { FormGroup, FormControl, Validators } from "#angular/forms";
import { ToastrService } from "ngx-toastr";
import { UserService } from "../services/user.service";
import { ChalService } from "../services/chal.service";
import { Response } from "../interfaces/response";
import { $ } from "protractor";
#Component({
selector: "app-chals",
templateUrl: "./chals.component.html",
styleUrls: ["./chals.component.scss"]
})
export class ChalsComponent implements OnInit {
chals = [];
flagForm = new FormGroup({
flag: new FormControl("", [Validators.required])
});
constructor(private toast: ToastrService, public userService: UserService, private chalService: ChalService) {}
ngOnInit() {
this.chalService.getAll().subscribe(
(data: Response) => {
this.chals = data.data["chals"];
},
err => {
this.toast.error(err.error.msg, "Oops!!!");
}
);
}
submitFlag(id: string) {}
}
Edit - The response is coming fine and the UI is also rendered correctly, just the problem is that the click does not expand the accordion.
Arrays are Index based so to access an element in an Array you have to provide an Index. To get the results from the response simply try to set the response to your array.
`ngOnInit() {
this.chalService.getAll().subscribe(
(data: Response) => {
this.chals = data.data; // -- updated the code here
},
err => {
this.toast.error(err.error.msg, "Oops!!!");
});
}`
Also please debug chalService to see if you are getting the response of your http call to the api.
I found the problem, it was that the _id field was starting with an int which was the root of the problem.
I solved it by adding an _ to every _id field.

Angular: #Input value available in template, but not in component

So I'm sending a movie object from a parent to a child component. I use #Input from the core module to do this.
Now, I am able to use this data inside the template to display my view, but when I try to access it in for example editReview() then this.movie returns undefined.
Does anyone know how to fix this?
Child Component: the console.log returns undefined
export class ReviewListComponent implements OnInit {
#Input('movie') movie;
constructor(private reviewService: ReviewService){}
editReview() {
console.log(this.movie);
}
}
Child Component Template: I am able to display my name and the content of the review. What's not working unfortunately is when I click the button it says that this.movie is undefined
<ul class="list-group">
<li *ngFor="let review of movie?.reviews" class="list-group-item">
<p>{{review.user.firstName}} {{ review.user.lastName}}</p>
<hr>
<p>{{review.content}}</p>
</li>
</ul>
<button (click)="editReview()">Button</button>
Edit: I added the components and templates of the two parent components: The first one is the movie component where I retrieve the movie object asynchronously, the second is the review component which contains the review-list component that I had already posted above
movie.component.ts
import {Component, OnInit} from "#angular/core";
import { MovieService} from "./movie.service";
import {ActivatedRoute} from "#angular/router";
#Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.css']
})
export class MovieComponent implements OnInit{
rating;
movie;
movieId;
constructor(private movieService: MovieService, private route: ActivatedRoute){
}
checkRating(){
return typeof this.rating === 'number';
}
ngOnInit(){
this.route.params.subscribe(params => {
this.movieId = params.id;
var userId = localStorage.getItem('userId');
this.movieService.getMovie(this.movieId)
.subscribe(movie => {
this.movie = movie.obj;
this.movie.averageRating = Number(this.movie.averageRating);
console.log(this.movie);
});
if (userId){
this.movieService.getRating(userId, this.movieId)
.subscribe( result => {
this.rating = result.rating;
})
}
});
}
addRating(score) {
var userId = localStorage.getItem('userId');
this.movieService.addRating(score, userId, this.movie._id)
.subscribe(data => {
this.rating = data.obj.rating;
});
}
}
movie.component.html: this gives movie object to review.component through element app-review
<div class="container">
<div class="col col-md-4 col-md-offset-2">
<img [src]="movie?.pictureUrl" class="img-responsive" alt="hello">
</div>
<div class=" col col-md-6">
<h2>{{movie?.title}} ({{movie?.year}})</h2>
<h3>Average Score:</h3>
<span class="glyphicon glyphicon-star"></span><span><h1>{{movie?.averageRating}}</h1></span><br>
<h3>My Score:</h3>
<div class="row">
<div class="col">
<div *ngIf="!checkRating()" ngbDropdown class="d-inline-block">
<button class="btn btn-outline-primary" ngbDropdownToggle>Rate The Movie!</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<button value="1" (click)="addRating(1)" class="dropdown-item">1</button>
<button value="2" (click)="addRating(2)" class="dropdown-item">2</button>
<button value="3" (click)="addRating(3)" class="dropdown-item">3</button>
<button value="4" (click)="addRating(4)" class="dropdown-item">4</button>
<button value="5" (click)="addRating(5)" class="dropdown-item">5</button>
</div>
</div>
<div *ngIf="checkRating()">
<h1>{{rating}}</h1>
</div>
</div>
</div>
<h4>{{movie?.director}}</h4>
<h4>{{movie?.actors}}</h4>
<h4>{{movie?.country}}</h4>
<p> {{movie?.description}}</p>
</div>
</div>
<hr class="col-md-8 col-md-offset-2">
<div class="col-md-8 col-md-offset-2">
<ng-container *ngIf="!!movie">
<app-review [movie]="movie"></app-review>
</ng-container>
</div>
review.component.ts
import {Component, ElementRef, Input, OnInit, ViewChild} from "#angular/core";
import {NgForm} from "#angular/forms";
import {ReviewService} from "./review.service";
import {ActivatedRoute} from "#angular/router";
#Component({
selector: 'app-review',
templateUrl: './review.component.html',
styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit{
movieId;
#Input('movie') movie;
#ViewChild('textArea') textArea;
constructor(private reviewService: ReviewService, private route: ActivatedRoute){}
ngOnInit(){
this.route.params.subscribe( params => {
this.movieId = params.id
});
};
onClear(form: NgForm) {
form.resetForm()
}
onSubmit(form: NgForm) {
let content = form.value.review;
let userId = localStorage.getItem('userId');
let movieId = this.movieId;
this.reviewService.addReview(content, userId, movieId)
.subscribe( result => {
console.log(result.obj);
this.movie.reviews.unshift(result.obj);
form.resetForm()
})
}
}
review.component.html: this gives movie object to the review-list component through app-review-list
<ng-container *ngIf="!!movie">
<app-review-list [movie]="movie"></app-review-list>
</ng-container>
<hr>
<form ngNativeValidate (ngSubmit)="onSubmit(f)" #f="ngForm">
<div class="form-group">
<label for="review">Write A Review:</label>
<textarea
#textArea
(keyup.enter)="onSubmit(f)"
rows="4"
cols="50"
type="text"
id="review"
ngModel
class="form-control"
name="review"
required
></textarea>
</div>
<button type="button" class="btn btn-danger" (click)="onClear(f)">Clear</button>
<button class="btn btn-primary" type="submit">Save</button>
</form>
you can access it in ngOnInit() just implement OnInit interface

how to use single Modal dialog Component to show different data or message in Angular 2

I am working on application in Angular 2 and fairly new to it. I want to display Modal dialog on click of a Card. I have integrated angular material Modal popup in my app as instructed in this tutorial. Each of card have different Data and that data i want to display on same modal popup.
My Modal Component is :
import { Component, OnInit } from '#angular/core';
import { MdDialogRef } from '#angular/material';
#Component({
selector: 'confirm-dialog',
template: `
<p>{{ title }}</p>
<p>{{ message }}</p>
<button type="button" md-raised-button
(click)="dialogRef.close(true)">OK</button>
<button type="button" md-button
(click)="dialogRef.close()">Cancel</button>
`,
})
export class ModalComponent {
public title: string;
public message: string;
constructor(public dialogRef: MdDialogRef<ModalComponent>) {
}
}
my Card component is :
import { Component, OnInit } from '#angular/core';
import { ModalService } from '../services/modal.service';
#Component({
selector: 'app-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.css']
})
export class ContentComponent implements OnInit {
private solutions: Array<Object>;
public result: any;
constructor(public dialogsService: ModalService) {
}
public openDialog() {
this.dialogsService
.confirm('Confirm Dialog', 'Are you sure you want to do this?')
.subscribe(res => this.result = res);
}
ngOnInit() {
}
}
and HTML is :
<div class="container self-card-container">
<div class="row lab-work">
<div class="col-12 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<div class="custom-card">
<div class="card-header whatWeDo align-item-center">
<div class="custom-header-image mat-card-avatar d-flex justify-content-center align-self-center" md-card-avatar="">
<img src="./assets/what-we-do.png" class="align-self-center">
</div>
<div class="custom-header-text d-flex align-self-center">
<div class="custom-card-title">What We Do</div>
</div>
</div>
<div class="custom-card-content">
Co-Innovate with customers and partners in a "sandbox" environment to develop proof of concepts. Harness Emerging technologies
to come up with newer solutions around existing problems. Provide an Immersive Experience to our customers of potential
solutions for feel and function.
</div>
<div class="custom-card-action align-items-center">
<button md-button class="read-more" (click)="openDialog()">Read More</button>
</div>
</div>
</div>
<div class="col-12 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<div class="custom-card">
<div class="card-header howWeDo align-item-center">
<div class="custom-header-image mat-card-avatar d-flex justify-content-center align-self-center" md-card-avatar="">
<img src="./assets/how-we-do.png" class="align-self-center">
</div>
<div class="custom-header-text d-flex align-self-center">
<div class="custom-card-title">How We Do</div>
</div>
</div>
<div class="custom-card-content">
We begin with problem identification followed by ideation phase to create an alternate point of view on the problem. This
is followed by building a proof of concept or a prototype which is then handed over to customer for feedback. The
whole process is repeated iteratively as desired.
</div>
<div class="custom-card-action align-items-center">
<button md-button class="read-more" (click)="openDialog()">Read More</button>
</div>
</div>
</div>
<div class="col-12 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<div class="custom-card">
<div class="card-header howWeDone align-item-center">
<div class="custom-header-image mat-card-avatar d-flex justify-content-center align-self-center" md-card-avatar="">
<img src="./assets/how-things-get-done.png" class="align-self-center">
</div>
<div class="custom-header-text d-flex align-self-center">
<div class="custom-card-title">How Things Get Done</div>
</div>
</div>
<div class="custom-card-content">
We follow 'continuous flow' based development as opposed to traditional software development life-cycle to stay lean. An
integrated application life cycle management gives us necessary agility and transparency.
</div>
<div class="custom-card-action align-items-center">
<button md-button class="read-more" (click)="openDialog()">Read More</button>
</div>
</div>
</div>
</div>
Dialog Service is :
import { Observable } from 'rxjs/Rx';
import { ModalComponent } from '../modal/modal.component';
import { MdDialogRef, MdDialog, MdDialogConfig } from '#angular/material';
import { Injectable } from '#angular/core';
#Injectable()
export class ModalService {
constructor(private dialog: MdDialog) { }
public confirm(title: string, message: string): Observable<boolean> {
let dialogRef: MdDialogRef<ModalComponent>;
dialogRef = this.dialog.open(ModalComponent);
dialogRef.componentInstance.title = title;
dialogRef.componentInstance.message = message;
return dialogRef.afterClosed();
}
}
I want to display title and message of card on modal that is being clicked.
How to pass Data respective to card in Modal?
Pass the title and message as parameters to the openDialog() method, eg.
html:
<button md-button class="read-more" (click)="openDialog('My special title', 'My special message')">Read More</button>
component:
public openDialog(title: string, message: string) {
this.dialogsService
.confirm(title, message)
.subscribe(res => this.result = res);
}
EDIT based on question in comment:
The quick and easy way would be to add the message content as string properties of the component and pass these as the parameters, eg.
In component:
export class ContentComponent implements OnInit {
private solutions: Array<Object>;
public result: any;
public dialogOneMessage = `<p>Stuff</p><p>More stuff<p><img src=:/photo.jpg" />
html:
<button md-button class="read-more" (click)="openDialog('My special title', dialogOneMessage)">Read More</button>
I don't really like this approach though - not a good separation of concerns having large amounts of html as component properties. If you have large amounts of complex data for each custom dialog you are probably better off creating a custom component for each of your dialogs and pass your custom component to the this.dialog.open() instead reusing the ConfirmDialog component.

Angular 2 binding: TypeError

Consider the below code, or see this plunk: https://plnkr.co/edit/mwPHfz?p=info.
Master
import {Component} from "angular2/core";
import {DetailComponent} from "./detail.component";
import {User} from "./user";
import {MOCKUSERS} from "./mock-users";
#Component({
directives: [DetailComponent],
selector: "master",
template: `
<h2>Master</h2>
<div>
<ul>
<li *ngFor="#user of users" (click)="selectUser(user)">
{{user.firstName}} {{user.lastName}}
</li>
</ul>
</div>
<div>
<detail [user]="selectedUser"></detail>
</div>
`
})
export class MasterComponent {
users: User[] = MOCKUSERS;
selectedUser: User;
selectUser(user: User) {
this.selectedUser = user;
}
}
Detail
import {Component, Input} from "angular2/core";
import {User} from "./user";
#Component({
selector: "detail",
template: `
<h2>Detail</h2>
<div>
<div>
<input type="text" [(ngModel)]="user.firstName">
</div>
<div>
<input type="text" [(ngModel)]="user.lastName">
</div>
<div>
<button type="submit">Submit</button>
</div>
</div>
`
})
export class DetailComponent {
#Input()
user: User;
}
User
export class User {
firstName: string,
lastName: string
}
When you click on a name in the list (master) the form (detail) should be updated. The first name and last name properties should appear in the firstName and lastName inputs respectively.
However, when you look in the developer tools, you can see the following error when loading the app:
EXCEPTION: TypeError: l_user0 is undefined in [user.firstName in DetailComponent#4:27]
So somehow the binding to user doesn't seem to be accepted. Can anyone tell why?
You are passing an undefined object (before selecting a user) to DetailComponent. You should only render the DetailComponent if there is a selectedUser.
<div *ngIf="selectedUser">
<detail [user]="selectedUser"></detail>
</div>
Working plunker. In index.html, I had to replace the imported angular js file from angular.min.js to angular.dev.js to fix an unrelated issue on your plunker.
<ul class="list-group users-list">
<li class="list-group-item"
*ngFor="let user of users"
(click)="selectUser(user)"
[class.active]="user === activeUser">
{{ user.name }} ({{ user.username }})
</li>
</ul>
you should initilize selectedUser parametes.
export class MasterComponent {
users: User[] = MOCKUSERS;
selectedUser: User = null;
selectUser(user: User) {
this.selectedUser = user;
}
}
<div>
<detail [user]="selectedUser"></detail>
</div>
#Component({
selector: "detail",
template: `
<h2>Detail</h2>
<div *ngIf="user !== null">
<div>
<input type="text" [(ngModel)]="user.firstName">
</div>
<div>
<input type="text" [(ngModel)]="user.lastName">
</div>
<div>
<button type="submit">Submit</button>
</div>
</div>
`
})
export class DetailComponent {
#Input() user: User = null;
}

Categories