Angular 2 (ng2) using the async pipe in [class.whatever]
Asked Answered
T

3

10

I am working on an angular 2 app and in one of my components I have this:

<p class="newNode">
  <input [(ngModel)]="formNode.id" placeholder="id">
  <input [(ngModel)]="formNode.name" placeholder="name">
  <input [(ngModel)]="formNode.type" placeholder="image">
  <button (click)="addNode()">Add</button>
</p>

<app-node-info *ngFor="let node of ((nodesService.observable | async) | immutableMapOfMaps)"
  [node]="node"
  [removeNode]="removeNode.bind(this)"
  [class.active] = "(viewService.observable | async).get('currentNode') === node.id"
  (click) = "viewService.setCurrentNode(node.id)">
</app-node-info>

Works great in the browser but when I try to lint my matching ts file, I get this linting error: "The method "async" that you're trying to access does not exist in the class declaration. (no-access-missing-member)' at: '11,21"

My component code is as follows:

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { clone } from 'ramda';
import { UUID } from 'angular2-uuid';

import { StateService } from '../state.service';

import { D3Node } from '../../non-angular/interfaces';
import { NodesService, ViewService } from '../../non-angular/services-helpers';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-list-of-nodes',
  styleUrls: ['./list-of-nodes.component.css'],
  templateUrl: './list-of-nodes.component.html',
})
export class ListOfNodesComponent implements OnInit {
  formNode: D3Node;
  /**
   * The injected service from ./state.service
   */
  private nodesService: NodesService;
  private viewService: ViewService;

  constructor(state: StateService) {
    this.nodesService = state.twiglet.nodes;
    this.viewService = state.view;
  }

  ngOnInit () {
    this.formNode = {
      id: UUID.UUID(),
      name: (Math.random().toString(36) + '00000000000000000').slice(2, 6),
      type: (Math.random().toString(36) + '00000000000000000').slice(2, 3),
    };
  }

  addNode () {
    this.nodesService.addNode(clone(this.formNode));
    this.formNode = {
      id: UUID.UUID(),
      name: (Math.random().toString(36) + '00000000000000000').slice(2, 6),
      type: (Math.random().toString(36) + '00000000000000000').slice(2, 3),
    };
  }

  removeNode (node: D3Node) {
    this.nodesService.removeNode(node);
  }

}

Is it some type of anti pattern to use the async pipe in something other than the ngFor?

I know that I can subscribe to the observable and set the response = to some local variable and then compare that instead of using the async pipe in the [class.active] but I'd rather not do something in my .ts file that I can just do in my html file.

Is there a way I can get around this error so that my linter doesn't yell at me? I have github pre-commit hooks that check linting so I need a permanent solution. I did figure out that I can put a // tslint:disable-line on the line that talks about change detection (line 11) and that fixes it but I have no idea why.

Thrower answered 19/12, 2016 at 23:7 Comment(4)
for which file does the error show? - I'm not sure if you should run the ts-linter on your template-files, after all an angular-expression is not excactly typescriptB
The error is showing on the name.component.ts, I am not linting my template files. However, if I remove the line [class.active] = "(viewService.observable | async).get('currentNode') === node.id" from my template file, the linting passes. That is what is so weird to me, it is like tslint is importing the template somehow.Thrower
That is indeed strange - when is the linting done? Are you using webpack and the linting is done in a pre-loader? Or sometime during the packaging, ect.? It is also interesting, that it does not highlight your other uses of asyncB
Both my linter in VS code and when I run ` tslint \"src/**/*.ts\ ` in my terminal throw the same error.Thrower
B
3

Not sure if that fixes the issue, but template-expressions can get messy quickly, you could do something like this:

<app-node-info ...
    [class.active]="(currentNode | async) == node.id">
</app-node-info>

The controller:

export class ListOfNodesComponent implements OnInit {
  formNode: D3Node;
  /**
   * The injected service from ./state.service
   */
  private nodesService: NodesService;
  private viewService: ViewService;

  private currentNode: Observable<any>;

  constructor(state: StateService) {
    this.nodesService = state.twiglet.nodes;
    this.viewService = state.view;

    currentNode = this.viewService.observable
      .map(val => val.get('currentNode'));
  }
B answered 20/12, 2016 at 15:44 Comment(4)
Appreciate it. I thought about just pulling it into the controller but then I lose OnPush advantages I have working with observables and immutable. I'll probably pull the service itself out of the component and just push down a variable from the subscription with an @Input(), I just couldn't figure out why it was having trouble linting it.Thrower
So moving parts of the async logic to the controller did not change anything? I have not seen that before, if you find a solution please post it here. - Maybe as a last shot: You are using "===" in the template, does "==" make any difference?B
No, it worked fine when I moved the async into the controller as a subscribe. I am just curious why I can't do it the way I have it.Thrower
In my answer I did not move the subscribe into the controller - i just moved some parts of the stream there, the async is still in the template - not sure why that works with tslint but it is cleanerB
N
1

I had the same issue and have been able to get rid of linter complain using safe navigation operator like this. For you it might look like this.

(viewService.observable | async)?.get('currentNode') === node.id

Edit: Found a bug report in codelyzer version 2.0.0-beta.1 with no-access-missing-member config set to true. This seems to be a case for me. Seems to be fixed in later versions, though I did not try yet.

Neddra answered 30/1, 2017 at 9:9 Comment(0)
W
0

I think there is an open issue for this which needs to be fixed in codelyzer. https://github.com/angular/angular-cli/issues/4351

But until then as a workaround you can do this in your component to fix the lint issue:

// TODO: fix this later
async: any;

constructor(..) {..}
Weed answered 12/7, 2017 at 17:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.