In angular1, we call the function ng-init based on ng-if condition. The following code represents If first div match with date && time then it will check the second condition, checked or not checked. If checked then it's automatically call play() function and if it's not check then call the pause function.
<div ng-if="ramadan.date === date && ramadan.time === clock">
<div ng-if="ramadan.checked" ng-init="play()"></div>
<div ng-if="!ramadan.checked" ng-init="pause()"></div>
</div>
I want to convert this code by angular2 typescript. Is there any way to automatically call the function based on condition in angular 2?
There is no ng-init in Angular2. You can easily create such a directive yourself though.
import { Directive, Input } from '#angular/core';
#Directive({
selector: '[ngInit]'
})
export class NgInit {
#Input() ngInit;
ngOnInit() {
if (this.ngInit) {
this.ngInit();
}
}
}
and then use it like
<div *ngIf="ramadan.date === date && ramadan.time === clock">
<div *ngIf="ramadan.checked" [ngInit]="play"></div>
<div *ngIf="!ramadan.checked" [ngInit]="pause"></div>
</div>
Working code,
Custom directive:
import { Directive, Input } from '#angular/core';
#Directive({ selector: '[myCondition]' })
export class ngInitDirective {
constructor() { }
#Input() set myCondition(condition: boolean) {
if (!condition) {
console.log("hello")
} else {
console.log("hi")
}
}
}
In template:
<ion-label *ngIf="setting1.date === current_date && setting1.time === current_time">
<div *myCondition="setting1.checked === condition" >Play</div>
<div *myCondition="!setting1.checked === !condition">pause</div>
</ion-label>
You can use ngOnInit method in your component class but remember to implements the OnInit interface.
Here's an example:
#Component({
selector: 'ramadan',
template: `<div>Ramadan</div>`
})
class Ramadan implements OnInit {
ngOnInit() {
this.play();
}
}
You can fin more about ngOnInit here.
Related
i have an issue while click binding on dynamic html.I tried setTimeout function but click event not binding on button.i have also tried template referance on button and get value with #ViewChildren but #ViewChildren showing null value.
Typscript
export class AddSectionComponent implements OnInit {
sectionList: any = [];
constructor(private elRef: ElementRef,private _httpService: CommonService ,private sanitized: DomSanitizer) { }
ngOnInit(): void {
this.getSectionList();
}
ngAfterViewInit() {
let element = this.elRef.nativeElement.querySelector('button');
if (element) {
element.addEventListener('click', this.bindMethod.bind(this));
}
}
bindMethod() {
console.log('clicked');
}
sanitizeHtml(value: string): SafeHtml {
return this.sanitized.bypassSecurityTrustHtml(value)
}
getSectionList() {
//API request
this._httpService.get('/Section/GetSectionList').subscribe(res => {
if (res) {
this.sectionList = res.json();
//sectionList is returning below HTML
//<div class="wrapper">
// <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
//</div>
}
})
}
}
Template
<ng-container *ngFor="let item of sectionList">
<div [innerHTML]="sanitizeHtml(item?.sectionBody)">
</div>
//innerHTML after rendering showing this
//<div class="wrapper">
// <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
//</div>
</ng-container>
Short Answer, you are binding functions inside your templates, which means you have a new html content every time change detection runs, and change detection runs everytime a function is called, which means your button keeps on being updated infinitely, that's why it never works, Read more here please.
Now on how to do this, I would listen to ngDoCheck, and check if my button has a listener, if not, I will append the listener. I will also make sure to use on Push change detection, because if not, this will ngDoCheck will be called a lot, and maybe the button will be replaced more often, not quite sure about it.
Here is how the code would look like.
html
<!-- no more binding to a function directly -->
<div #test [innerHTML]='sanitizedHtml'></div>
component
import { HttpClient } from '#angular/common/http';
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, ElementRef, OnDestroy, ViewChild } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements DoCheck {
name = 'Angular';
people: any;
//now we are getting the div itself, notice the #test in the html part
#ViewChild('test')
html!: ElementRef<HTMLDivElement>;
//a property to hold the html content
sanitizedHtml!: SafeHtml;
constructor(private _http: HttpClient, private sanitized: DomSanitizer,private change: ChangeDetectorRef ) {}
ngDoCheck(): void {
//run with every change detection, check if the div content now has a button and attach the click event
if (this.html != undefined) {
let btn = this.html.nativeElement.querySelector('button');
if (btn && btn.onclick == undefined) {
btn.onclick = this.bindMethod.bind(this);
}
}
}
ngOnInit() {
this.peoples();
}
peoples() {
this._http.get('https://swapi.dev/api/people/1').subscribe((item: any) => {
const people = `<div class="wrapper">
<p>${item['name']}</p>
<button type='button' class='btn btn-primary btn-sm'>Click Me</button>
</div>`;
//assign the html content and notify change detection
this.sanitizedHtml = this.sanitized.bypassSecurityTrustHtml(people);
this.change.markForCheck();
});
}
bindMethod() {
console.log('clicked');
}
}
I don't like the approach because of the need to listen to ngDoCheck, this can run a lot, especially if you don't use onpush change detection.
I hope this helped.
in this tutorial
https://www.sitepoint.com/practical-guide-angular-directives/
i am learning how to create a customised directive. i followed the steps as shown in the code posted below, but despite added the exact code as explained in the aforemenrtioned website, when i run the command
ng serve --open
i get something as shown in the image posted below.
please let me know why myCustomIf is not working. i say that myCustomIf is not working because what i got on the localhost:4200 is something as shown in the image posted
please let me know how to make the myCustomIf works as explained in the tutorial in the above posted link
app.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'ngDirective1';
name = 'Angular';
condition = false;
}
app.myCustomeIfDirective.ts:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[myCustomIf]'
})
export class MyCustomeIfDirective{
constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef){ }
#Input()
setMyCustomIf(condition : boolean) {
if(condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
app.module:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[myCustomIf]'
})
export class MyCustomeIfDirective{
constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef){ }
#Input()
setMyCustomIf(condition : boolean) {
if(condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
app.component.html:
<h1 my-error>Hello {{name}}</h1>
<h2 *myCustomIf="condition">Hello {{name}}</h2>
<button (click)="condition = !condition">Click</button>
image:
If you open console it should show smth like:
NG0303: Can't bind to 'myCustomIf' since it isn't a known property of
'h2'
An Angular structural directive, that is written in a short syntax(with *) and that takes one input or more inputs, must have an #Input with the same name as directive's attribute selector(other inputs follow another rule described here What is the exact grammar for Angulars structural directives), e.g.:
#Directive({
selector: '[anyAttr]'
})
export class MyCustomeIfDirective{
#Input()
anyAttr: any;
or
#Directive({
selector: '[anotherAttr]'
})
export class MyCustomeIfDirective{
#Input()
set anotherAttr(val: any) {}
Why is it so?
That's because *ngIf is just a shortcut for expanded version:
<ng-template [ngIf]="...">...
or
*anyAttr => <ng-template [anyAttr]="...">...
Now, let's look at your code:
#Directive({
selector: '[myCustomIf]'
})
export class MyCustomeIfDirective{
#Input()
setMyCustomIf(condition : boolean) {
Several things to notice:
setMyCustomIf is just a method in your case
if you convert it to a setter set MyCustomIf then MyCustomIf doesnt match myCustomIf because js is case-sensitive.
The solution is:
#Input()
set myCustomIf(condition : boolean) {
Ng-run Example
in your directive (app.myCustomeIfDirective.ts), you need to match the name of your input to the name of the directive (because the condition is passed with that attribute):
#Input("myCustomIf")
set myCustomIf(condition : boolean) {
if(condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
(note you can also change the name of the function to match the directive name)
stackblitz demo
Here i wrote a popup function and one Boolean flag is their in my component class. My flag will return true or false based on my conditions. In template class popup function needs to fire when flag becomes true, then immediately my popup dialog box will come. But i am not aware to call right approach, If any one knows please help me the correct approach.
<ng-template #sessionSuccessModal let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Include Criteria Error</h4>
<button type="button" class="close" aria-label="Close" (click)="closeModel()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" class="bg-light text-dark">
<p>{{ alertMessage }}!</p>
</div>
<div style="text-align: center" class="bg-light text-dark">
<button type="button" (click)="closeModel()">Ok</button>
</div>
</ng-template>
intially commaSeparation will be false, this.commaSeparation = this.genericValidator.validateMultiComma(this.orderUnitForm); this function is returning either true or false. If it is true then i need to call my displayModel() alert method. Now popup is working fine, calling in ngAfterViewInit() but getting error in console like.
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-untouched: true'. Current value: 'ng-untouched: false'
ngAfterViewInit() {
let controlBlurs: Observable<any>[] = this.formControls
.map((formControl: ElementRef) => Observable.fromEvent(formControl.nativeElement, 'blur'));
// debounceTime(1000)/
Observable.merge(this.orderUnitForm.valueChanges, ...controlBlurs).subscribe(value => {
this.displayMessage = this.genericValidator.processMessages(this.orderUnitForm);
// this.valid = this.genericValidator.validateControls(this.orderUnitForm);
});
this.orderUnitForm.valueChanges.debounceTime(1000).subscribe(value => {
this.valid = this.genericValidator.validateControls(this.orderUnitForm);
this.commaSeparation = this.genericValidator.validateMultiComma(this.orderUnitForm);
if(this.commaSeparation == true){
this.displayModel();
}
});
}
This is my dispalyModel() function:
displayModel() {
this.alertMessage = 'You cannot enter more than one multiple at the same time ';
this.successErrorModalBlock = this.modalService.open(this.sessionSuccessModalref);
}
You can achieve this implementing the interface OnChanges that is a lifecycle hook of the Angular.
import { Component, OnChanges, SimpleChanges } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnChanges {
// the flag property
commaSeparation: boolean;
ngOnChanges(changes: SimpleChanges){
if (changes['commaSeparation'] && changes['commaSeparation'].currentValue){
this.showPopup();
}
}
public showPopup(){
alert('Replace this alert by the code that shows your popup');
}
}
Reference: https://angular.io/guide/lifecycle-hooks#onchanges
In general, it's a bad idea to call functions with side effects inside an angular expression. Angular is free to call those functions however often it likes to make sure the result is still the same. You should use functions in these expressions to return a result, not to cause an action.
I would suggest instead calling popupAlert from your controller, e.g.
$scope.$watch('commaSeparation', function(commaSeparation) {
// The value of commaSeparation has just changed. Alert if it changed to true!
if (commaSeparation) {
popupAlert();
}
});
If commaSeparated is an input property, you can add a property change watcher using the ngOnChanges lifecycle hook and then call popupAlert if commaSeparation was changed (link to docs).
#Component({
...
})
export class MyComponent implements OnChanges {
// ...
#Input()
commaSeparation = false;
// ...
ngOnChanges(changes: SimpleChanges) {
const commaSeparationChanges = changes.commaSeparation;
if (commaSeparationChanges && commaSeparationChanges.currentValue) {
this.popupAlert();
}
}
}
If commaSeparated is only changed inside the component, then you can just make it private and work with a getter/setter pair to trigger the popup:
#Component({
...
})
export class MyComponent implements OnChanges {
// ...
private _commaSeparation = false;
// ...
get commaSeparation() {
return this._commaSeparation;
}
set commaSeparation(value) {
this._commaSeparation = value;
if (value) {
this.popupAlert();
}
}
}
I want to call a function with an argument when an element is loaded.
Just like nginit in angualrjs. Can we do it in Angular 4 and above?
<div *ngFor="let item of questionnaireList"
(onload)="doSomething(item.id)" >
</div>
My Typescript function:
doSomething(id) {
console.log(id);
}
You need to write a directive
import {Directive, Input, Output, EventEmitter} from '#angular/core';
#Directive({
selector: '[ngInit]'
})
export class NgInitDirective {
#Input() isLast: boolean;
#Output('ngInit') initEvent: EventEmitter<any> = new EventEmitter();
ngOnInit() {
if (this.isLast) {
setTimeout(() => this.initEvent.emit(), 10);
}
}
}
Using in html
<div *ngFor="let quetionnaireData of questionnairelist ; let $last = last" [isLast]='$last'
(ngInit)="doSomething('Hello')"></div>
Also you declare your directive in app.module
#NgModule({
declarations: [
..
NgInitDirective
],
......
})
Use ngOnInit() and the #Input directive.
For example, in your child component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'my-component',
template: `
<h3>My id is: {{itemId}}</h3>
`
})
export class MyComponent implements OnInit
{
#Input() itemId: string;
//other code emitted for clarity
public ngOnInit(): void
{
// Now you can access to the itemId field e do what you need
console.log(this.itemId);
}
}
In your parent component
<div *ngFor="let item of questionnairelist">
<my-component itemId='{{item.Id}}'></my-component>
</div>
Your Function:
ExecuteMyFunction(value:any):void{
console.log(value);
}
If you wants to pass parameter which declared in component itself and set from component then try as below:
notificationMessage:string='';
#Input() message:string;
ngAfterViewInit(){
this.ExecuteMyFunction(this.notificationMessage);
}
If you set variable as Input parameter and set from other component then try as below: ngOnChanges will fire every time when your Input variable value is changed.
import { Component, OnChanges, Input } from '#angular/core';
ngOnChanges(changes: any) {
if (changes.message != null && changes.message.currentValue != null) {
this.ExecuteMyFunction(this.message);
}
}
HTML:
<ng-container *ngFor="let item of items">
<div *ngIf="doSomething(item.id)"></div>
</ng-container>
TS:
doSomething(value){
//logic
return true;
}
import { Router,NavigationEnd } from '#angular/router';
constructor( private router: Router ) {
this.router.events.subscribe((e) => {
if (e instanceof NavigationEnd) {
// Function you want to call here
}
});
}
I'm looking to update an Angular 1 app to Angular 2 and am having an issue with one of my old directives.
The idea is simple. When an input field is focused a class should be added (md-input-focus) and another be removed (md-input-wrapper). Then this process should be reversed on "blur" event - i.e. focus lost.
My old directive simply included the lines
.directive('mdInput',[
'$timeout',
function ($timeout) {
return {
restrict: 'A',
scope: {
ngModel: '='
},
link: function (scope, elem, attrs) {
var $elem = $(elem);
$elem.on('focus', function() {
$elem.closest('.md-input-wrapper').addClass('md-input-focus')
})
.on('blur', function() {
$(this).closest('.md-input-wrapper').removeClass('md-input-focus');
})
}
etc...
I obviously have the classic start to my directive but have run out of.....skill
import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
#Directive({
selector: '.mdInput',
})
export class MaterialDesignDirective {
constructor(el: ElementRef, renderer: Renderer) {
// Insert inciteful code here to do the above
}
}
Any help would be appreciated.
UPDATE:
The HTML would look like (before the input element was focused):
<div class="md-input-wrapper">
<input type="text" class="md-input">
</div>
and then
<div class="md-input-wrapper md-input-focus">
<input type="text" class="md-input">
</div>
after.
The input element is the only one which can receive a focus event (and therefore the target for the directive) however the parent <div> requires the class addition and removal.
Further help
Please see Plunker for help/explanation - would be great if someone could help
Update
#Directive({selector: '.md-input', host: {
'(focus)': 'setInputFocus(true)',
'(blur)': 'setInputFocus(false)',
}})
class MaterialDesignDirective {
MaterialDesignDirective(private _elementRef: ElementRef, private _renderer: Renderer) {}
setInputFocus(isSet: boolean): void {
this.renderer.setElementClass(this.elementRef.nativeElement.parentElement, 'md-input-focus', isSet);
}
}
Original
This can easily be done without ElementRef and Renderer (what you should strive for in Angular2) by defining host bindings:
import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
#Directive({
selector: '.mdInput',
host: {
'(focus)':'_onFocus()',
'(blur)':'_onBlur()',
'[class.md-input-focus]':'inputFocusClass'
}
})
export class MaterialDesignDirective {
inputFocusClass: bool = false;
_onFocus() {
this.inputFocusClass = true;
}
_onBlur() {
this.inputFocusClass = false;
}
}
or a bit more terse
#Directive({
selector: '.mdInput',
host: {
'(focus)':'_setInputFocus(true)',
'(blur)':'_setInputFocus(false)',
'[class.md-input-focus]':'inputFocusClass'
}
})
export class MaterialDesignDirective {
inputFocusClass: bool = false;
_setInputFocus(isFocus:bool) {
this.inputFocusClass = isFocus;
}
}
I tried it only in Dart where it works fine. I hope I translated it correctly to TS.
Don't forget to add the class to the directives: of the element where you use the directive.
In addition to previous answers, if you don't want to add a directive, for the specific component (you already have a directive for a parent component, you are using Ionic 2 page or something else), you inject the renderer by adding private _renderer: Renderer in the page constructor and update the element using the event target like this:
html:
(dragstart)="dragStart($event)"
TS:
dragStart(ev){
this._renderer.setElementClass(ev.target, "myClass", true)
}
Edit: to remove the class just do:
dragEnd(ev){
this._renderer.setElementClass(ev.target, "myClass", false)
}
The name of the selector has to be inside "[ ]", as shown below
#Directive({
selector: '[.mdInput]',
host: {
'(focus)':'_setInputFocus(true)',
'(blur)':'_setInputFocus(false)',
'[class.md-input-focus]':'inputFocusClass'
}
})
If you want to catch the focus / blur events dynamiclly on every input on your component :
import { AfterViewInit, Component, ElementRef } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {
name = 'Angular focus / blur Events';
constructor(private el: ElementRef) {
}
ngAfterViewInit() {
// document.getElementsByTagName('input') : to gell all Docuement imputs
const inputList = [].slice.call((<HTMLElement>this.el.nativeElement).getElementsByTagName('input'));
inputList.forEach((input: HTMLElement) => {
input.addEventListener('focus', () => {
input.setAttribute('placeholder', 'focused');
});
input.addEventListener('blur', () => {
input.removeAttribute('placeholder');
});
});
}
}
Checkout the full code here : https://stackblitz.com/edit/angular-wtwpjr