12

Basically I'm looking for a way to implement a counterpart to Angular 1.x ngInit directive.

I'm aware of ngOnInit hook and the fact that it is the recommended place for initialization code. I consider ngInit directive a quick, declarative way to prototype or fix a component that shouldn't be generally used in well-written production code (although a developer has the right to choose what's best for him/her).

Doing something like that in init dummy directive

<p [init]="foo = 1; bar()"><p>

evaluates the expression more than one time and causes

Template parse errors:

Parser Error: Bindings cannot contain assignments

error.

In Angular 1.x it could be done just with

$parse($attrs.init)($scope)

How can Angular 2 parser be used and possibly extended to evaluate foo = 1; bar() template expression on component initialization?

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Ng-init has gone the way of the dinosaur. Like you said, ngOnInit is the proper place to put initialization code, so why would they keep ng-init? What you are doing in that code is trying to *bind* init to a value, that is what square brackets indicate. But the value is an expression which, as Angular tells you, isn't supported. – aaronofleonard Aug 29 '16 at 15:51
  • I missed ng-init so much too but he is gone. RIP – tom10271 Aug 29 '16 at 15:54
  • @Amleonard As the question says, ng-init has its uses, even if it is misused often. I know that bracket code binds the expression which is not the desired behaviour, this is just the code that I have now. The question is how it should be done to match the desired behaviour. – Estus Flask Aug 29 '16 at 16:04

2 Answers2

4

Just a Workaround ( Plunker Demo ), see estus's answer for a solution

init Directive:

@Directive({
  selector: '[init]',
  inputs: ['init']
})
export class InitDir {
  init;

  ngOnChanges() {     // `ngOnInit` if you want it to run just once
    if(this.init){
      let iife = function(str){ return eval(str); }.call(this.init[0], this.init[1]);
    }
  }
}

Usage:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2 [init]="[this, 'this.name = 1; this.bar();']">Hello {{name}}</h2>
    </div>
  `,
})
export class App {
  constructor() {
    this.name = 'Angular2 (Release Candidate!)'
  }

  bar() {
    alert('Yo Bar!');
  }
}
Ankit Singh
  • 24,525
  • 11
  • 66
  • 89
  • eval is evil. It is is supposed that this will be implemented with Angular's own parser, that's what it is for. Just not sure how. – Estus Flask Sep 05 '16 at 11:57
  • There is obviously a thing like that in A2, just because it does expression parsing. `@angular/compiler` [contains a class that is responsible for that](https://github.com/angular/angular/blob/master/modules/%40angular/compiler/src/expression_parser/parser.ts). It may be still undocumented but it is there. – Estus Flask Sep 05 '16 at 12:56
1

This can be achieved with a directive:

@Directive({ selector: '[initialize]' })
class InitializeDirective {
  @Output() initialize = new BehaviorSubject();
}

The expected use is:

<div (initialize)="initViaMethodCall(); foo = 'init via assignment'"></div>
<ng-template (initialize)="bar = 'init with no extra markup'"></template>
{{ foo }}
{{ bar }}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • More than 3 words of English, please. All I see beside code is "It is" and "and". – Cody Jun 26 '18 at 21:17
  • It is ready to use solution for the question, and the code is pretty much self-documenting. If you have specific questions about it, feel free to ask. – Estus Flask Jun 26 '18 at 21:28
  • thanks for the update, but I still can't get mine to work. I have the exact same `.ts` as you alongside a `div`: import { Directive, Output } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Directive({ selector: '[initialize]' }) export class InitDirective { @Output() initialize = new BehaviorSubject(100); } **AND**
    ++++{{foo}}++++
    – Cody Jun 26 '18 at 22:34
  • How is the `@Output` property `initialize` firing/`.emit()`ing? – Cody Jun 26 '18 at 22:36
  • Here's a working example: https://stackblitz.com/edit/angular-initialize-directive – Cody Jun 27 '18 at 02:46
  • Glad it worked for you. It doesn't need `emit`, that's the reason why `BehaviourSubject` was used instead of `EventEmitter`. `initialize` is dummy event, it's never fired. It's there only to evaluate an expression. – Estus Flask Jun 27 '18 at 08:51