I am new to angular and I am writing dummy app to teach myself angular.
My idea was to have 2 inputs where player1 and player2 write down their names. When they press the button game starts and shows their names and health.
I am doing that with *ngIf else, but when I click the button to change boolean nothing is shown.
What is the problem and is there a better way to do that?
app.component.html
<p>This will not change!</p>
<ng-template *ngIf="startGame;
else noStart">
<p> Render other components for health and names. Did game start: {{startGame}}</p>
</ng-template>
<ng-template #noStart>
<h1> Input player names to start the game!</h1>
<input [(ngModel)]="player1">
<br>
<input [(ngModel)]="player2">
<br>
<button (click)="onStartGame()">Start</button>
</ng-template>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
startGame: boolean = false;
player1: string = '';
player2: string = '';
username = '';
setPlayer1(event) {
this.player1 = event.target.value;
}
setPlayer2(event) {
this.player2 = event.target.value;
}
onStartGame() {
if(this.player1 !== '' && this.player2 !== '')
this.startGame = true;
}
}
I want to render other component or HTML if boolean changes in if statment.
The ng-template just create a template and which won't show by default, use ng-container for that purpose which helps to group and
add multiple elements to DOM.
<p>This will not change!</p>
<ng-container *ngIf="startGame;
else noStart">
<p> Render other components for health and names. Did game start: {{startGame}}</p>
</ng-container>
<ng-template #noStart>
<h1> Input player names to start the game!</h1>
<input [(ngModel)]="player1">
<br>
<input [(ngModel)]="player2">
<br>
<button (click)="onStartGame()">Start</button>
</ng-template>
You can event use *ngIf directive with the p tag since there is only one element(p tag) and use ng-container whenever you want to group multiple elements.
<p>This will not change!</p>
<p *ngIf="startGame;
else noStart"> Render other components for health and names. Did game start: {{startGame}}</p>
<ng-template #noStart>
<h1> Input player names to start the game!</h1>
<input [(ngModel)]="player1">
<br>
<input [(ngModel)]="player2">
<br>
<button (click)="onStartGame()">Start</button>
</ng-template>
Change *ngIf and write [ngIf] but is not recommendable
Related
I am trying to add a conditional field through content projection.
The projected input field is toggling correctly in accordance to its sibling checkbox field. When the checkbox is "checked" the conditional field displays. When the checkbox is "unchecked", the input field disappears.
However, when I move the checkbox into its "checked" state, I'm receiving an error:
ERROR Error: Cannot find control with name: 'detail'
I want the projected detail field to be apart of the _formGroup in the child component, not the parent component. How can I achieve this?
The error seems to suggest that the formControlName="detail" is not visible to the _formGroup in the child component. How can I rectify this?
Here is the parent component with the outer parent Form Group.
Parent Component
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
#Component({
selector: 'ch-parent-comp',
templateUrl: './parent-comp.component.html',
styleUrls: ['./parent-comp.component.scss']
})
export class ParentComponent implements OnInit {
formGroup: FormGroup;
constructor() {
this.formGroup = new FormGroup({
//detail: new FormControl('') --> Adding this will solve the issue, but I don't want this field here. I want it in the child
});
}
ngOnInit(): void {
}
}
Parent Component HTML Template
<form>
<ch-check-box-group [parentFormGroup]="formGroup" controlName="new_property" controlLabel="New Property" [hasDetail]="true" [detailRequired]="true">
<!-- Projected Input Field -->
<ng-template contentHandle>
<mat-form-field>
<mat-label>Purchase Date</mat-label>
<input type="text" matInput name="detail" formControlName="detail" id="">
</mat-form-field>
</ng-template>
</ch-check-box-group>
</form>
Child Component(ch-check-box-group)
export interface CheckboxGroupForm {
value: FormControl<boolean>,
detail?: FormControl<any>
}
#Component({
selector: 'ch-check-box-group',
templateUrl: './check-box-group.component.html',
styleUrls: ['./check-box-group.component.scss']
})
export class CheckBoxGroupComponent implements OnInit, OnDestroy {
private _ks: Subject<void> = new Subject<void>();
_formGroup: FormGroup
_showDetailInput: boolean = false;
#Input() controlLabel!: string;
#Input() controlName!: string;
#Input() parentFormGroup!: FormGroup;
#Input() hasDetail: boolean = false;
#Input() detailRequired: boolean = false;
#ContentChild(ContentHandleDirective) content!: ContentHandleDirective;
constructor() {
this._formGroup = new FormGroup<CheckboxGroupForm>({
value : new FormControl(false, {nonNullable: true}),
});
}
ngOnInit(): void {
if(this.hasDetail){
this._formGroup.addControl('detail', new FormControl(''));
}
this.parentFormGroup.addControl(this.controlName, this._formGroup);
this._formGroup.setParent(this.parentFormGroup);
this._formGroup.valueChanges
.pipe(takeUntil(this._ks))
.subscribe((change) => {
// Toggle visibility of detail input
if(this.hasDetail && this._showDetailInput != change.value){
this._showDetailInput = change.value;
}
// Toggle Validation(if necessary) of detail input
if(this.hasDetail && this.detailRequired && change.value){
setTimeout(() => {
this._formGroup.get('detail')?.addValidators(Validators.required);
});
} else {
this._formGroup.get('detail')?.clearValidators();
if(this._showDetailInput != change.value){
this._formGroup.get('detail')?.updateValueAndValidity();
}
}
console.log(this._formGroup.controls)
});
}
ngOnDestroy(){
this._ks.next();
this._ks.complete();
}
}
Child Component Template
<div class="row">
<form class="col-12" [formGroup]="_formGroup">
<mat-checkbox formControlName="value" i18n>{{ controlLabel }}</mat-checkbox>
<button mat-icon-button color="primary">
<mat-icon>help</mat-icon>
</button>
<div class="conditional-input-box-container" *ngIf="_showDetailInput">
<ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>
</div>
</form>
</div>
To solve this, pass the child components _formGroup to the [ngTemplateOutletContext] like so:
<div class="row">
<form class="col-12" [formGroup]="_formGroup">
<mat-checkbox formControlName="value" i18n>{{ controlLabel }}</mat-checkbox>
<button mat-icon-button color="primary">
<mat-icon>help</mat-icon>
</button>
<div class="conditional-input-box-container" *ngIf="_showDetailInput">
<ng-container [ngTemplateOutlet]="content.templateRef" [ngTemplateOutletContext]="{ $implicit: _formGroup}"></ng-container>
</div>
</form>
</div>
Then use that new variable in the parent component like this:
<form>
<ch-check-box-group [parentFormGroup]="formGroup" controlName="new_property" controlLabel="New Property" [hasDetail]="true" [detailRequired]="true">
<!-- Projected Input Field -->
<ng-template contentHandle let-_formGroup>
<div [formGroup]="_formGroup">
<mat-form-field>
<mat-label>Purchase Date</mat-label>
<input type="text" matInput name="detail" formControlName="detail" id="">
</mat-form-field>
</div>
</ng-template>
</ch-check-box-group>
</form>
I have Parent component with 2 different child, inquiryForm and inquiryResponse, I got situation when I need to hide and show this 2 component based on condition:
If user had click submit on inquiryForm, it will hide inquiryForm component and show inquiryResponse component
On inquiryResponse component, there are button display inquiry form, where user clicked it and will hide inquiryResponse component and show inquiryForm.I cant solved this.
I know it can be solved using router but I want different solution like using service or subject
this is demo I created using stackblitz, this is what I had tried;
inquiry-response.ts
getReceivedSummons() {
this.inquiryStore.summons$.subscribe(result => {
this.receivedSummon = result;
this.addCheckboxes();
this.isShowResponse = true;
});
}
showInquiryForm() {
// do something
}
inquiry-response.html
<div *ngIf="isShowResponse">
<p>Inquiry Response</p>
<form [formGroup]="form" (ngSubmit)="submitSelectedCheckboxes()">
<ng-container formArrayName="receivedSummons" *ngFor="let summon of formReceivedSummons.controls; let i = index">
<ng-container [formGroup]="summon">
<ng-container formArrayName="items" *ngFor="let item of formReceivedSummonsItems(i).controls; let j = index">
<ng-container [formGroup]="item">
<input type="checkbox" formControlName="isChecked"> {{item.value.name}}
</ng-container>
</ng-container>
</ng-container>
<div *ngIf="!summon.valid">At least one order must be selected</div>
</ng-container>
<br>
<span class="button">
<button [disabled]="!form.valid">submit</button>
</span>
<button (click)="showInquiryForm()"> ( change ID number ) display inquiry form</button>
</form>
</div>
As AJT_82 say, you app.component can be like
<app-inquiry-form *ngIf="step==1" (submit)="step=2"></app-inquiry-form>
<br>
<app-inquiry-response *ngIf="step==2" (click)="step=1"></app-inquiry-response>
//you has a variable
step:number=1;
And in each component
#Output() submit=new EventEmitter<any>()
..in somewhere...
this.submit.emit()
#Output() click=new EventEmitter<any>()
..in somewhere...
this.click.emit()
I have a component whose template allows for 2 content areas: Text and "read more" text. If the consumer of the component adds the area for the "read more" text, I want to show the "read more" link the end-user would click to show the text. If they don't include/need any "read more" text I don't want to show the link.
How do I detect the presence of the template area, and act accordingly with an ngIf?
For example, the html might be:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<div readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</div>
</app-promohero-message-unit>
Obviously, they might not need readmoretext, so if they've omitted it I should not show the readmore link.
The component code is, so far:
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
readMore = false;
showReadMore() {
this.readMore = true;
}
}
In Angular 8 you dont have to use the ngAfterViewInit life cycle hook. You can use the ngOnInit as long as you set the "static" value of the viewchild to true.
import { Component, OnInit, ViewChild, TemplateRef, ElementRef } from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
#ViewChild('content', { read: ElementRef, static: true }) content: ElementRef;
constructor() { }
ngOnInit() {
console.log(!!this.content.nativeElement.innerHTML); // return true if there is a content
}
}
Note that you must wrap the ng-content directive with html tag (such as div, span etc) and to set the templateRef on this outer tag.
<div #content>
<ng-content></ng-content>
</div>
I putted it on stackblitz: https://stackblitz.com/edit/angular-8-communicating-between-components-mzneaa?file=app/app.component.html
You can get a reference to the ng-content (Template Variable) and then access that variable in your component to check the length on the content of that ng-content using ViewChild
Then you can use the ngAfterViewInit life cycle hook to check for ng-content length
Your code will be like this:
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-promohero-message-unit',
template: `
<div>
<h3 class="text-white">{{ title }}</h3>
<p class="text-white">
<ng-content select="[description]"></ng-content>
</p>
<p class="text-white" *ngIf="readMore">
<ng-content #readMoreContent select="[readmoretext]"></ng-content>
</p>
</div>
<p>
<a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
</p>
`
})
export class PromoheroMessageUnitComponent {
#Input()
title: string;
#ViewChild('readMoreContent') readMoreContent: ElementRef;
readMore = false;
ngAfterViewInit() {
if (this.readMoreContent.nativeElement.childNodes.length.value == 0){
this.readMore = false
}
}
showReadMore() {
this.readMore = true;
}
}
You can use the ContentChild decorator for this, but will need to use an ng-template with a defined id as your content:
<app-promohero-message-unit title="Title for messaging module">
<div description>
Include a short, informative description here.
</div>
<ng-template #readmoretext>
If you need to add more detail, include another sentence or two it in this section.
</ng-template>
</app-promohero-message-unit>
Then in your component, you can use the ContentChild annotation like this:
export class PromoheroMessageUnitComponent {
#ContentChild('readmoretext')
readMoreContent: TemplateRef<any>;
// ...snip
}
Then finally in the HTML for your component:
<!-- snip -->
<p class="text-white" *ngIf="readMoreContent">
<ng-container *ngTemplateOutlet="readMoreContent"></ng-container>
</p>
I've added jQuery in script and used
import * as $ from 'jquery';
as well. I've also added jQuery in the HTML file.
But my toggleClass() function is not working. When I checked the console, it didn't show any error. It was just empty.
Below is my HTML and ts code:
HTML Code:
<div class="web" (click) = "myFunc()">
<p>
Web Development
$300
</p>
</div>
<div class="design" (click) = "myFunc()">
<p>
Design
$400
</p>
</div>
<div class="integration" (click) = "myFunc()">
<p>
Integration
$20
</p>
</div>
<div class="training" (click) = "myFunc()">
<p>
Training
$500
</p>
</div>
</div>
<div class="total">
Total
$0
</div>
ts Code:
import { Component } from '#angular/core';
import * as $ from 'jquery';
//declare var jquery:any;
//declare var $ :any;
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
myFunc() {
$(this).toggleClass("active");
}
}
You don't need to use jquery, just change your click binding on view to (click)="myFunc($event)", and on myFunc change to:
myFunc(e) {
const target: HTMLElement = e.target;
target.classList.toggle('active');
}
It may not be working because $(this) is referring to the current class instance.
you need to refer the source element in the jquery to work properly. Pass '$event' object to your function from the template and get the target object inside the function:
<div class="web" (click) = "myFunc($event)">
myFunc(event: any) {
$(event.target).toggleClass("active");
}
See this stackblitz: https://stackblitz.com/edit/angular-dsely1
I'm writing angular components for the foundation css framework. I am working on the tabs component, and want to be able to pass some HTML to the <ng-content> of this.
The problem is, I also need to pass html which a user can put bindings on, like this:
PARENT TEMPLATE
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
TABS COMPONENT
<ul class="tabs" #tabs>
<li *ngFor="let item of data | async" (click)="tabClick($event)">
<a>{{item.name}}</a>
</li>
</ul>
<div>
<ng-content></ng-content>
</div>
TABS TYPESCRIPT
#Component({
selector: 'tabs',
templateUrl: './tabs.component.html'
})
export class TabsComponent {
#Input('data') data:any;
#ViewChild('tabs') tabs: ElementRef;
}
Where item is a reference to an object in the example array.
However, I get this error:
Cannot read property 'name' of undefined
as item is being evaluated before it is inserted into the <ng-content> directive.
Is there a way to get around this limitation, or am I going about this the wrong way?
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
ngTemplateOutlet or ngForTemplate can be used for that use case:
<tabs [data]='example'>
<ng-template let-item>
<div> Age <br> {{item.age}} </div>`
</ng-template>
</tabs>
#Component({
...
template: `
<ul class="tabs" #tabs>
<li *ngFor="let item of data | async" (click)="tabClick($event)">
<a>{{item.name}}</a>
</li>
</ul>
<div>
<ng-template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{$implicit: (data | async)}"></ng-template>
</div>
`
})
class TabsComponent {
#ContentChild(TemplateRef) templateRef:TemplateRef;
}
See also Angular 2 bind transcluded content to loop variable
You should be using this way instead,
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
Component typescript
#Component({
selector: 'tabs',
templateUrl: './tabs.component.html'
})
export class TabsComponent {
#Input() data:any;
item:any{};
}
In your content projection define a selector as
<div class="tabs-body">
<ng-content select=".tabs-body"> </ng-content>
</div>
As your passing with bindings
<tabs [data]='example'>
<div> Age <br> {{item.age}} </div>`
</tabs>
DEMO
You need to pass the item object to the ng-content component.
<ng-content [item]="selectedTab></ng-content>
I am not certain on what lies behind the tab click event but you can assign that item object to selectedTab which will be passed to the component.
The component that will control the tab view can have the following:
#Input() item: Item;
And this will pass that object when you click. I might be attacking this from the wrong angle but maybe it will help you in some way.