Skip to content

setDisabledState() never called when receiving disabled state from master fieldset #13856

Closed

Description

@moniuch

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

The setDisabledState() method is never called when disabled state change is propagated from the master fieldset.

Expected behavior
The method should be called when a change in disabled state occurs. This should not require feeding the state to a control's @Input()

Minimal reproduction of the problem with instructions
https://plnkr.co/edit/gRRl9KGxEYArNLgqShBS?p=preview

What is the motivation / use case for changing the behavior?
The hook should be fired in order to provide parallel render and behavior to what is happening on the browser level, as per: https://www.w3.org/wiki/HTML/Elements/fieldset

Please tell us about your environment:
Plnkr

  • Angular version: 2.4.2

  • Browser: [MacOS Chrome & FF]

  • Language: [all | TypeScript X.X | ES6/7 | ES5]
    Plnkr

  • Node (for AoT issues): node --version =
    Plnkr

Activity

  1. added
    featureLabel used to distinguish feature request from other issues
    and removed on Jan 5, 2018
  2. moniuch commented on Jan 23, 2018

    @moniuch
    Author

    Why is this ticket flagged as a feature? The fieldsets cannot be used according to W3C specs. It is a bug. It breaks intrinsic in-browser behavior.

  3. added this to the Backlog milestone on Jan 23, 2018
  4. RedBati commented on Feb 12, 2018

    @RedBati

    I am running into this same issue now. Any idea when it might be fixed? or if there is a suggested work around to make ControlValueAccessor respond to fieldsets disabled state as described above?

  5. moniuch commented on Feb 12, 2018

    @moniuch
    Author
  6. RedBati commented on Feb 13, 2018

    @RedBati

    I was able to get this working (although its not ideal) by itself:

    import { Directive, EventEmitter, HostBinding, Input, OnInit, Optional, Self, SkipSelf } from '@angular/core';
    import { NgControl, NgForm } from '@angular/forms';
    
    /**
     * Directive for forcing synchronization of disabled states between form elements suchs as NgForm, fieldset, and NgModel
     */
    @Directive({
        selector: 'form,fieldset,[formControlName],[formControl],[ngModel]'
    })
    export class DisabledSynchronizationDirective implements OnInit {
    
        /**
         * Indicates that this fieldset is disabled
         */
        @Input()
        @HostBinding('class.disabled')
        @HostBinding('attr.disabled')
        @HostBinding('attr.aria-disabled')
        public get disabled(): boolean {
            return this.selfIsDisabled;
        }
        public set disabled(value: boolean) {
            // translate false to 'null'. Why: attr bindings will remove the attribute if it returns null, but false will result in 
            // [disabled="false"] in the case of disabled attr, we actually do want to remove them.
            // see thread: https://github.com/angular/angular/issues/2869
            this.selfIsDisabled = value || null;
            this.updateDisabledState();
        }
    
        /**
         * Indicates that this instance or its parent instance is disabled
         */
        private get isDisabled() {
            return !!this.selfIsDisabled || this.parentIsDisabled;
        }
    
        /**
         * Indicates that the parent directive is disabled
         */
        private get parentIsDisabled() {
            return !!this.parent && this.parent.isDisabled;
        }
    
        /**
         * EventEmitter for disabled state changes
         */
        @Output()
        public disabledChanges: EventEmitter<boolean> = new EventEmitter<boolean>();
    
        /**
         * Placeholder for disabled accessor
         */
        private selfIsDisabled: boolean;
    
        /**
         * Indicates if this was already disable on the previous update (in which case we do not need to fire again)
         */
        private previousIsDisabled: boolean;
    
        /**
         * Constructs a new instance of DisabledSynchronizationDirective
         */
        constructor(
            @Optional() @SkipSelf() private parent: DisabledSynchronizationDirective,
            @Optional() @Self() private ngControl: NgControl,
            @Optional() @Self() private ngForm: NgForm
        ) { }
    
        /**
         * Implementation of angular OnInit interface
         */
        public ngOnInit() {
            if (this.parent) {
                this.subscriptions.push(this.parent.disabledChanges.subscribe(() => this.updateDisabledState()));
            }
        }
    
        /**
         * updates the disabled state of the form controls attached to this element
         */
        private updateDisabledState() {
            // dont fire if nothing has really changed
            if (this.previousIsDisabled === this.isDisabled) {
                return;
            }
    
            // notify others that state has changed
            this.disabledChanges.next(this.isDisabled);
            // either disable or enable related controls
            let method = this.isDisabled ? 'enable' : 'disable';
    
            // handle ngForm if this element has a form reference
            if (this.ngForm) {
                this.ngForm.control[method]();
            }
    
            // handle ngControl if this element has a reference to it
            if (this.ngControl) {
                this.ngControl.control[method]();
            }
    
            // Save disabled state for next time
            this.previousIsDisabled = this.isDisabled;
    
        }
    
    }

    Now I have a problem that this doesn't work in one particular use case where I have a template for the content of my fieldset defined inside of my ControlValueAccessor. This looks like:

    ... irrelevant content ...
    <fieldset [disabled]="someCondition">
        <ng-container *ngTemplateOutlet="myContentTemplateRef;context:{$implicit: data}"></ng-container>
    </fieldset>
    ... irrelevant content ...

    but when i use it like this:

    <myControlValueAccessor [(ngModel)]="val1" >
         <input type="text" [(ngModel)]="val2"/>
    </myControlValueAccessor>

    The inputs instance of DisabledSynchronizationDirective will find the DisabledSynchronizationDirective instance on the myControlValueAccessor as 'parent' and not the fieldset.

  7. MattHoneycutt commented on Apr 19, 2018

    @MattHoneycutt

    I disagree with this being labeled a "feature" as well as the sev rating. As you can see from the linked issues, it's resulting in bug reports in other projects. It flat-out doesn't work the way one would expect, and breaks established semantics that apply to forms.

  8. brndusic commented on May 16, 2018

    @brndusic

    I lost hours trying to find out problems in my implementation. Please fix this and flag it as a bug. This also worked in AngularJS.

  9. milosdanilov commented on May 16, 2018

    @milosdanilov

    I can confirm that this issue persists on angular 5.2.10. I agree with @MattHoneycutt that this should be labeled as a bug, because it breaks usage in angular/material.

    @kara do you agree, that this should be labeled as a bug and not feature?

  10. kolkov commented on Jun 9, 2018

    @kolkov

    Not working for me too in Angular 6.0.4 :((

  11. kolkov commented on Jun 9, 2018

    @kolkov

    Sorry... changing [attr.disabled] to [disabled] worked for me now.

  12. LuqJensen commented on Mar 30, 2020

    @LuqJensen

    This is causing problems for me on Angular 9 in 2020 too

  13. wendellestradairely commented on Sep 4, 2020

    @wendellestradairely

    I'm also having this issue in Angular 10. Man it's already version 10 and in pandemic year 2020 and still hasn't been fixed.

  14. AndrewKushnir commented on Nov 17, 2020

    @AndrewKushnir
    Contributor

    Hi, it looks like this issue has the same root cause as #35309, so I'm closing this PR in favor of #35309 (so all similar issues are grouped). Thank you.

  15. angular-automatic-lock-bot commented on Dec 19, 2020

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  16. locked and limited conversation to collaborators on Dec 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions