skip to Main Content
<div>
  @if (user.name as userName) {
    {{ userName }}
  }
</div>



<div>
 @if (user.name) {
   {{ user.name }}
 }
</div>

I am wondering what is the difference between this two approaches? just a syntax? or something else? For example when using as does it creates variable? What is happening under the hood?

2

Answers


  1. The "as" keyword generates code that assigns the value to a temporary variable.

    // Simplified representation of generated code
    if (user.name) {
      let userName = user.name;
      // ... rest of the template block using userName
    }
    

    Use "as" for:
    Cleaner template logic by introducing meaningful aliases.
    Avoiding repetitive property access.

    Avoid "as" when:
    Not re-referencing the property within the block.
    Unnecessary for clarity.

    Login or Signup to reply.
  2. I don’t know how deep you want such an explaination to go.
    if you really want to go down to each line of execution, I’m sorry.. I will not put hours into this question.
    Basically this happens:

    First of all, you have to keep in mind, what you are writing there, is a dialect that does resemble js, but isn’t.

    So to not go into too much depth and keep this somewhat reasonable, let’s start at the point, where the parser will encounter a @ symbol:
    https://github.com/angular/angular/blob/c213a4e15a594ff141cf312ad301128e7ed4127c/packages/compiler/src/ml_parser/lexer.ts#L206-L207

    } else if (this._tokenizeBlocks && this._attemptCharCode(chars.$AT)) {
      this._consumeBlockStart(start);
    

    basically what you see there, is that when encountering an @, it will consume the continuing data as block.

    the consumption of that block will parse as follows:
    https://github.com/angular/angular/blob/c213a4e15a594ff141cf312ad301128e7ed4127c/packages/compiler/src/ml_parser/lexer.ts#L245-L277

    private _consumeBlockStart(start: CharacterCursor) {
      this._beginToken(TokenType.BLOCK_OPEN_START, start);
      const startToken = this._endToken([this._getBlockName()]);
    
      if (this._cursor.peek() === chars.$LPAREN) {
        // Advance past the opening paren.
        this._cursor.advance();
        // Capture the parameters.
        this._consumeBlockParameters();
        // Allow spaces before the closing paren.
        this._attemptCharCodeUntilFn(isNotWhitespace);
    
        if (this._attemptCharCode(chars.$RPAREN)) {
          // Allow spaces after the paren.
          this._attemptCharCodeUntilFn(isNotWhitespace);
        } else {
          startToken.type = TokenType.INCOMPLETE_BLOCK_OPEN;
          return;
        }
      }
    
      if (this._attemptCharCode(chars.$LBRACE)) {
        this._beginToken(TokenType.BLOCK_OPEN_END);
        this._endToken([]);
      } else {
        startToken.type = TokenType.INCOMPLETE_BLOCK_OPEN;
      }
    }
    
    private _consumeBlockEnd(start: CharacterCursor) {
      this._beginToken(TokenType.BLOCK_CLOSE, start);
      this._endToken([]);
    }
    

    after all the blocks of your code are parsed, it will be thrown into angulars expression parser. (since @ refers to an expression)

    this will also go through, and parse the as bindings.
    https://github.com/angular/angular/blob/c213a4e15a594ff141cf312ad301128e7ed4127c/packages/compiler/src/expression_parser/parser.ts#L1145-L1180

    /**
     * Parse a directive keyword, followed by a mandatory expression.
     * For example, "of items", "trackBy: func".
     * The bindings are: ngForOf -> items, ngForTrackBy -> func
     * There could be an optional "as" binding that follows the expression.
     * For example,
     * ```
     *   *ngFor="let item of items | slice:0:1 as collection".
     *                    ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
     *               keyword    bound target   optional 'as' binding
     * ```
     *
     * @param key binding key, for example, ngFor, ngIf, ngForOf, along with its
     * absolute span.
     */
    private parseDirectiveKeywordBindings(key: TemplateBindingIdentifier): TemplateBinding[] {
      const bindings: TemplateBinding[] = [];
      this.consumeOptionalCharacter(chars.$COLON);  // trackBy: trackByFunction
      const value = this.getDirectiveBoundTarget();
      let spanEnd = this.currentAbsoluteOffset;
      // The binding could optionally be followed by "as". For example,
      // *ngIf="cond | pipe as x". In this case, the key in the "as" binding
      // is "x" and the value is the template key itself ("ngIf"). Note that the
      // 'key' in the current context now becomes the "value" in the next binding.
      const asBinding = this.parseAsBinding(key);
      if (!asBinding) {
        this.consumeStatementTerminator();
        spanEnd = this.currentAbsoluteOffset;
      }
      const sourceSpan = new AbsoluteSourceSpan(key.span.start, spanEnd);
      bindings.push(new ExpressionBinding(sourceSpan, key, value));
      if (asBinding) {
        bindings.push(asBinding);
      }
      return bindings;
    }
    

    which will then go there:
    https://github.com/angular/angular/blob/c213a4e15a594ff141cf312ad301128e7ed4127c/packages/compiler/src/expression_parser/parser.ts#L1202-L1223

    /**
     * Return the binding for a variable declared using `as`. Note that the order
     * of the key-value pair in this declaration is reversed. For example,
     * ```
     *   *ngFor="let item of items; index as i"
     *                              ^^^^^    ^
     *                              value    key
     * ```
     *
     * @param value name of the value in the declaration, "ngIf" in the example
     * above, along with its absolute span.
     */
    private parseAsBinding(value: TemplateBindingIdentifier): TemplateBinding|null {
      if (!this.peekKeywordAs()) {
        return null;
      }
      this.advance();  // consume the 'as' keyword
      const key = this.expectTemplateBindingKey();
      this.consumeStatementTerminator();
      const sourceSpan = new AbsoluteSourceSpan(value.span.start, this.currentAbsoluteOffset);
      return new VariableBinding(sourceSpan, key, value);
    }
    

    and will then do a variable binding as seen here:
    https://github.com/angular/angular/blob/c213a4e15a594ff141cf312ad301128e7ed4127c/packages/compiler/src/expression_parser/ast.ts#L343-L374

    /**
     * Return the binding for a variable declared using `as`. Note that the order
     * of the key-value pair in this declaration is reversed. For example,
     * ```
     *   *ngFor="let item of items; index as i"
     *                              ^^^^^    ^
     *                              value    key
     * ```
     *
     * @param value name of the value in the declaration, "ngIf" in the example
     * above, along with its absolute span.
     */
    private parseAsBinding(value: TemplateBindingIdentifier): TemplateBinding|null {
      if (!this.peekKeywordAs()) {
        return null;
      }
      this.advance();  // consume the 'as' keyword
      const key = this.expectTemplateBindingKey();
      this.consumeStatementTerminator();
      const sourceSpan = new AbsoluteSourceSpan(value.span.start, this.currentAbsoluteOffset);
      return new VariableBinding(sourceSpan, key, value);
    }
    

    afterwards the bindings and other entities that drop out the AST compiler will be rendered here:
    https://github.com/angular/angular/blob/c213a4e15a594ff141cf312ad301128e7ed4127c/packages/compiler/src/render3/view/template.ts#L2875-L2876

    const {nodes, errors, styleUrls, styles, ngContentSelectors, commentNodes} = htmlAstToRender3Ast(
      rootNodes, bindingParser, {collectCommentNodes: !!options.collectCommentNodes});
    

    if you then go further into that function there, called htmlAstToRender3Ast, you will encounter a call to HtmlAstToIvyAst which then goes further into roughly 600 more lines of code that do yet another AST to AST convertion..

    I could go further ofc, but as I said at the start, going deep will take a long time.

    I hope you get a gist of how this somewhat works now.

    essentially it parses this as expression as a variable expansion, like let [foo, bar] = [1, 5] in vanilla js. except in this case it’s more like [1, 5][0] as foo

    also a small reminder: all this code is ran every time you compile a template.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search