47

In angular 1 we could do one time binding in this way: {{ ::myFunction() }}.

In angular 2 this is throwing:

EXCEPTION: Template parse errors:
Parser Error: Unexpected token : at column 2 in [{{ ::consent(false, undefined, box) }}] in CookieConsent@5:29 ("ull-right" href="" (click)="consent(true, $event, box)">De acuerdo</a>
        <span class="hidden">[ERROR ->]{{ ::consent(false, undefined, box) }}</span>

How can we do one time binding in angular2?

Tony Brasunas
  • 4,012
  • 3
  • 39
  • 51
Miquel
  • 8,339
  • 11
  • 59
  • 82
  • @GünterZöchbauer, see https://docs.angularjs.org/guide/expression#one-time-binding – Mark Rajcok Dec 19 '15 at 22:34
  • 6
    See if `ChangeDetectionStrategy.CheckOnce` is what you are looking for: https://angular.io/docs/ts/latest/api/core/ChangeDetectionStrategy-enum.html – Mark Rajcok Dec 19 '15 at 22:43
  • angular2 is one way by default. If you want two-way binding you have to use the syntax `[()]`. – Eric Martinez Dec 19 '15 at 23:44
  • 4
    @EricMartinez he means "one TIME" binding, not "one WAY" – alexpods Dec 20 '15 at 09:50
  • 1
    @Miquel AFAIK this functionality doesn't exist in angular2 and most likely never will (see [this github issue](https://github.com/angular/angular/issues/2449)) – alexpods Dec 20 '15 at 09:50
  • 2
    ` :: ` stands for >> The ability to render data once and let it persist without being affected by future Model updates. It was a feature in angular 1 – ErvTheDev Jun 02 '16 at 07:46
  • As a non-Angular solution but performance improvement, check out `_.memoize`, Lodash's memoize function. This would prevent a particular function from being run more than once, and after that would just send the computed result back each and every time. Not a perfect solution, but improves performance a little without having to do any significant Angular workarounds. – ryanm May 18 '18 at 21:06
  • 1
    To follow @ryanm suggestion, you may compute the result once for all in the constructor or in the ngOnInit (depends if it requires an Input value), and store it in a component attribute. It makes angular check a variable instead of calling a full method. As ryanm says, it's just an improvement, it's not as good as "::" was, but better than nothing ;) – Random Mar 29 '19 at 07:54
  • @ryanm, there are many implementations of a memoize pipe out there for running functions on computed/input properties only when the inputs to the function have really changed (https://github.com/ArtemLanovyy/ngx-pipe-function). ChangeDetection will only run the function hiding behind the | memoize pipe if the inputs to the pipe change. If you set up a component method that returns whatever you want one-time bound you can achieve one-time binding by putting it behind the memoize pipe and making sure none of the pipe's arguments ever change. – jcairney May 31 '19 at 22:52

7 Answers7

20

I found solution for one time binding in angular 2 here: https://github.com/angular/angular/issues/14033

I created this directive:

 import { Directive, TemplateRef, ViewContainerRef, NgZone } from "@angular/core";

@Directive({
    selector: '[oneTime]',
})
export class OneTimeDirective {
    constructor(template: TemplateRef<any>, container: ViewContainerRef, zone: NgZone) {
        zone.runOutsideAngular(() => {
            const view = container.createEmbeddedView(template);
            setTimeout(() => view.detach());
        })
    }
}

And used it:

  <some-selector *oneTime [somePropertyToOneTimeBinding]="someValueToOneTimeBinding"></some-selector>

For example:

     <iframe *oneTime [src]="myUrl"></iframe>
M Barzel
  • 739
  • 7
  • 9
15

ChangeDetectionStrategy.CheckOnce is the solution for this problem.

Some info here:

Adam
  • 877
  • 2
  • 10
  • 24
Miquel
  • 8,339
  • 11
  • 59
  • 82
  • 41
    No, it's not. It may be if you want to treat all your bindings inside components this way. The `::` syntax from first Angular was used per single bind, not whole controller. – Namek Jun 08 '16 at 07:54
  • If you need more control over what you want to be `CheckOnce` and what you don't want, then you need to use directives. Commonly it will make sense if you need some component to just be updated one time to encapsulate it in a separate controller/directive. – Miquel Jun 09 '16 at 15:08
  • I still had to find out how to actually use it. Here it is. In your component declaration, add: changeDetection: ChangeDetectionStrategy.OnPush – LeFex Dec 14 '16 at 06:36
  • 1
    @LeFex has Angular changed `CheckDetectionStrategy`? At the time this question was asked, `CheckOnce` was the correct response, but right now angular docs seems confusing: `OnPush means that the change detector's mode will be set to CheckOnce during hydration.` Maybe @MarkRajcok who firstly proposed the solution could help – Miquel Feb 01 '17 at 17:02
  • I want to use it instead of ngx-translate or Angular i18n which are both very clumsy. I have a dictionary reference in my components that contains the texts and I want to bind only one time to each text inside the template. Is there any way to achieve this in a simple way like it is in AngularJs ? – Cesar Jul 02 '19 at 13:45
5

Currently, you cannot do one time binding with Angular 2. However, you can know when your binding changes and reset your inputs.

Angular 2 provides OnChanges lifecycle hook for the same. You need to implement OnChanges interface to get the changes.

See the code example below where, I am storing the data-bound property in a private variable when OnInit is called.

export class Footer implements OnInit, OnChanges {
  @Input() public name: string;
  private privname: string;

  constructor() { }

  ngOnInit() {
    this.privname = this.name;
  }


  ngOnChanges(changes: { [key: string]: SimpleChange }): void {
    if (!changes["name"].isFirstChange()) {
        this.name = this.privname;
    }
  }
}

Later when other changes occur, I am setting the value to its old value on subsequent changes.

This mechanism works like One-time binding.

Alternate solutions: You could also use a setter function to capture the changes.

Community
  • 1
  • 1
Jigar
  • 544
  • 1
  • 8
  • 28
4

ChangeDetectionStrategy.CheckOnce is the solution for this problem.

This has been updated to OnPush see also comment in code:

export declare enum ChangeDetectionStrategy {
    /**
     * `OnPush` means that the change detector's mode will be set to `CheckOnce` during hydration.
     */
    OnPush = 0,
    /**
     * `Default` means that the change detector's mode will be set to `CheckAlways` during hydration.
     */
    Default = 1,
}
eav
  • 2,123
  • 7
  • 25
  • 34
3

Use ngOnInit()

In Angular 2+ we have ngOnInit() which will generally only run one time, exactly when the component is initialized. This is the simplest and usually best solution to the issue of one-time binding.

Binding to a function can lead to dozens of unneeded calls to that function and slow down your app.

Instead of {{ ::myFunction() }}, create a property on the component and set its value in ngOnInit():

export class MyComponent implements OnInit {
  myValue: any;

  constructor() { }

  ngOnInit() {

    this.myValue = /* CALL FUNCTIONS OR CALCULATE VALUE HERE */

  }
}

And then in the template, simply use:

 {{ myValue }} 

Your calculation will run just once.

Tony Brasunas
  • 4,012
  • 3
  • 39
  • 51
  • 1
    As far as I know, this still creates eventListeners which will run on every digest cycle, won't it? For example, the variable `myValue` won't change/update but on every click/mouseover/keypress angular will still re-evaluate the `myValue` variable for changes? Just something to keep in mind if whomever is attempting one-time binding for performance. – KeaganFouche May 22 '22 at 13:24
  • 1
    There is no digest cycle in Angular, that was an AngularJS routine based on $scope and watchers. Change Detection in Angular will never run more than once. You can further reduce it by setting `ChangeDetectionStrategy.onPush` and `ChangeDetectionStrategy.checkOnce` on your component. – Tony Brasunas Jun 07 '22 at 06:14
0

Since one time read/bind isnt possible by default in angular, I think writing a public getter function will solve the issue.

For example

public getValue():number {
 return mynumber ? mynumber : 25; // if mynumber is not undefined the mynumber else return 25
}

//In html template
<input type="range" min="getValue()" max="100">

This will work perfectly if the getter function is able to reply back before the template rendering takes place. So the initialization of mynumber would be great if done in ngOnInit() function

Abdeali Chandanwala
  • 8,449
  • 6
  • 31
  • 45
0

Angular doesn't provide any syntactic sugar to control the binding frequency of inputs.

However, you can control when to react when inputs are updated. There are two ways, and either or both could be used to get the desired behaviour:

  1. Using a setter for Input and conditionally forward the update to the component body or component's template.
  2. Switching the change detection in the component on or off using ChangeDetectorRef when inputs are satisfied.

Note that turning the change detection off doesn't mean that the inputs won't be updated. Inputs will always update, and so ngOnChanges will be called whether or not change detection is off or on. However, the template won't update against updated inputs if change detection is off. See the codesandbox to understand this effect.

Example 1

To explain the point 1 above, consider the following:

  _input1: number;
  @Input() set input1(input: number) {
    if (this._input1 === undefined || this._input1 === null) {
      this._input1 = input;
    }
  }

Consuming in template

  <div>Bound Input1: {{ _input1 }}</div>

_input1 is component's local copy and is only updated when desired i.e. in the above particular case, the local copy in only updated if it's previously null or undefined. input1 is the input property. Here we assume that an undefined or null value will never be considered a valid value and unless input1 is passed a non-null non-undefined value, we will consider that the "bind once" hans't occurred.

Example 2

ChangeDetectorRef excerpt from docs:

Base class that provides change detection functionality. A change-detection tree collects all views that are to be checked for changes. Use the methods to add and remove views from the tree, initiate change-detection ...

So ChangeDetectorRef's method detach could be used to detach a component from change detection tree. To demonstrate point 2 above, the following example waits until all inputs are satisfied and then turns off the change detection:

import {
  ChangeDetectorRef,
  Component,
  Input,
  OnChanges,
  SimpleChanges
} from "@angular/core";

@Component({
  selector: "bind-until-all",
  template: `
    <div>Input1: {{ input1 }}</div>
    <div>Input2: {{ input2 }}</div>
    <div>Input3: {{ input3 }}</div>
  `
})
export class BindUntillAllComponent implements OnChanges {
  /**
   * We assume that a null or undefined value is not an empty.
   * And until a non-empty value is passed to an input, it'll be considered as not-assigned.
   *
   * Based on the use case, define what is a suitable "empty" value
   * and assign that value as the default value to the inputs.
   */
  @Input() input1: number;
  @Input() input2: number;
  @Input() input3: number;

  private isDetached: boolean = false;

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnChanges(changes: SimpleChanges) {
    // Check whether all inputs are satisfied i.e. they have non-empty values assigned
    // For our case, type === "number" satisfies that the value is non-empty
    const areAllInputsSatisfied = [this.input1, this.input2, this.input3].every(
      (n) => typeof n === "number"
    );

    // Stop change detection after triggering the manual change detection once
    if (areAllInputsSatisfied && !this.isDetached) {
      this.cdr.detectChanges();
      this.cdr.detach();

      this.isDetached = true;
    }
  }
}

Live example in the above mentioned codesandbox.

abdul-wahab
  • 2,182
  • 3
  • 21
  • 35